アプリケーションモダナイゼーションとは
現在のWindows Formsの原型は、Windows 95が登場した1995年に登場しました。本稿の執筆時点である2020年より、およそ25年前のテクノロジーということになります。
25年の間には、ハードウェアやネットワークは大きな進化や発展を繰り返しました。その環境の変化に合わせて、Windowsもさまざまな新機能の追加や改修を繰り返し、稼働するPC環境や利用方法も大きく変化しました。
同じように、WindowsアプリのプラットフォームもWindows Formsだけではなく、Windows Presentation Foundation(WPF)やUniversal Windows Platoform(UWP)などのテクノロジーが登場し、より高度な表現が行えるクライアントアプリケーションを構築できるようになりました。
そして現在、世界では7億を超えるWindowsデバイスが稼働しています。240万のアプリケーションが開発されているマーケットになっており、今現在も利用され続けています。
しかし、前述の通り利用されている環境は変化しています。私たち開発者はその変化に対応するべく既存のデスクトップアプリを進化させる必要があります。
このように、アプリケーションの機能を現代のニーズにマッチしたものへと進化させることを「アプリケーションの近代化」「モダナイゼーション」と呼びます。
モダナイゼーションにおけるアプローチ
モダナイゼーションを行うとなると、開発当時から今までの間に広まったその間に多くの機能や公開されている新しいAPI、Open Sourceのライブラリなどの機能追加もありますが、まず現在のアプリケーション自体をモダンなものにする必要があります。
モダンにするために検討すべき項目には以下があります。
- セキュアで信頼性の高い環境の実現
- 新しい環境に対する相互運用性に対応
- UXの強化
セキュアで信頼性の高い環境の実現(モダンなパッケージングの仕組み)
セキュアで信頼性の高い環境でアプリケーションを配布・実行するためには、その方法がシンプルであり、セキュアで信頼性のある仕組みによりパッケージングされて、配信されることが重要と言われています。
Windows Vista以降の一般ユーザー向けWindowsでは、OSの重要部分へのアクセスを行う場合に「User Access Controlにより一時的な管理者権限を付与する」ということを強制します。
この機能が加わることによって、システムの重要部分にユーザーの許可なく暗黙的な操作を行うことができなくなりました。
しかし、この方法には欠点があります。それは「セットアップパッケージ自体が不正な操作を行うアプリケーションである」という場合です。この場合、ユーザーが誤って管理者権限に昇格してしまうと、そのPCには不正な操作が行われることになります。
このような問題に対処するため、現在新しいパッケージ方法として、MSIXという形式が公開されています。
MSIXでは、パッケージを行う際、アプリケーションはシステムの重要箇所に対してアクセスする場合、事前にマニフェストを定義することを強制します。
これにより、インストール時にアプリがアクセスする箇所を事前に通知し、不正検知を行うきっかけを作ります。
また、アプリのインストール箇所は通常のWindowsファイルシステムとは異なる場所に行い、アプリケーション内で利用するファイルの展開や削除を適切に行う仕組みが用意されています。
- 参考リンク:MSIXとは
新しい環境に対する相互運用性
前述の通り、.NET Frameworkは最終バージョンが4.8であることを発表しました。加えて、今後登場する新しい機能は.NET Coreで実現することが公言されています。
.NET Coreは.NET Foundationでソースが管理されており、Microsoftをはじめとする多くのベンダーが、この環境を支援しています。
つまり、今後登場する新しいサービスや機能は、.NET Coreをベースに開発されていくことになるため、引き続きアプリケーションをバージョンアップしたり、新しい環境に対応させたりするのであれば、既存の.NET Frameworkベースのアプリケーションも.NET Coreをベースにするほうがスムーズに進化させることができます。
配布方式の進化
.NET Coreの配布方式は、実行環境にインストールされている.NET Coreランタイムに依存して実行するFramework dependent deployment(FDD)という方式に加えて、アプリケーションの実行に必要となるライブラリを同梱して配布するSelf contained deployment(SCD)が追加されました。
後者の場合、アプリケーションが依存する.NET Coreのバージョンがインストールされているかどうかを気にすることなく、アプリケーションを配布することができるようになります。
- 参考リンク:.NET Core アプリケーションの展開
UXの強化
Windows 10のバージョン1903以降には、WinFormsやWPF、Win32アプリに対して、Fluent Design Systemを用いた現代的なデザインをサポートするUniversal Windows Platform(UWP)のコントロールを追加することができるXAML Islandsというシナリオがサポートされるようになりました。
これらのコントロールを用いることで従来UWPのみで利用できたUI機能を活用できるようになりました。
ただし、これらの機能の多くは前述のMSIXによるパッケージを行い、パッケージIDを付与する必要があります。
- 参考リンク:デスクトップ アプリの現代化
しかし、多くの既存アプリは、すでに現在のプラットフォームにおけるインストールエクスペリエンスを持つものや、作成済みのUIが存在します。そのため、既存機能はそれらを極力利用したくなることでしょう。
本稿ではマイグレーションと併せ、それらのUIに対するモダナイズのアプローチとして「ComponentOne」のThemes for Windowsを用いて、統一的なデザインの適用方法やTiles for WPFを利用してUIをモダナイズする方法をご紹介します。
対象読者
- C#/.NET Frameworkを使用してクライアントアプリを開発する開発者
開発環境
今回使用した環境やアプリケーション、SDK、ライブラリは以下の通りです。
- Windows 10 Version 1909
-
Visual Studio 2019 16.4.4
- .NETデスクトップ開発ワークロード
- .NET Coreクロスプラットフォームの開発ワークロード
- .NET Core 3.0 SDK
- ComponentOne 2019J v3
- Reactive Property v6.2
ComponentOneについて
ComponentOneはグレープシティが開発する.NET環境のWindows、Web、Mobileアプリ開発に利用できるコントロールセットです。新元号に対応済みであることはもちろんのこと、.NET環境にあるUIを便利にする新機能やコントロールが今なお機能追加されています。
- 参考リンク:ComponentOne
ComponentOneの特徴
300種類を超えるコントロール
標準コントロールで実現できなかった細やかな機能により、直感的な入力や操作感といったユーザーエクスペリエンスの向上に貢献します。
Platform間で共通的なAPI
ComponentOneは、提供されている各プラットフォーム間で極力共通のAPIとなるようライブラリが構成されています。たとえばWinFormsで利用したComponentOneのChartコントロールを、WPFでも利用したい場合、プラットフォーム独自の制約があることを除けば、同じような手順で再利用できます。
WPFによるChartコントロール(BarChart)
WinFormsによるChartコントロール(BarChart)
最新の開発環境で利用できる
ComponentOneは常に最新の開発環境におけるアップデートが行われています。利用者はサブスクリプションモデルとして利用することで、常に最新のモジュールを導入できるため、最新の開発環境をいち早く導入することが可能です。
Themes for WinFormsについて
WPFやUWPなどのXAMLには、ThemeやStyle、ResourceDictionaryの仕組みを用いて統一的なインターフェイスを作成する機能があります。
これはXAMLで記述された各UI要素が持つプロパティの初期値を適用するものですが、ComponentOneにはWinFormsアプリでも同様に統一されたデザインを適用することができる「Themes for WinForms」という機能があります。
「Themes for WinForms」はアプリに統一的なデザイン適用を支援するコンポーネント群です。このコンポーネントを使用することで、共通的なデザインの作成・適用が簡単に行えます。
本稿では、マイグレーションに使用するサンプルのWinFormsアプリに「Themes for WinForms」の主要コンポーネントである「C1ThemeController」を利用したデザインの適用方法を紹介します。
Tiles for WPFについて
Tiles for WPFは、Windows 8で登場したインターフェイス「タイル」を用いたUIを簡単に作成することができるコントロールです。
本稿で利用するマイグレーション対象のWPFアプリには、Windows標準のListBoxコントロールを使用した簡単なアプリケーションに対して、Tiles for WPFを使用してモダンな表現を適用したいと思います。
ComponentOneの.NET Core対応について
ComponentOneの.NET Framework 4.5.2用アセンブリは.NET Framework互換モードにより.NET Coreプロジェクトで参照が行えます。
.NET Framework互換モードとは.NET Standard 2.0以降に導入されたモードで、.NET Frameworkのライブラリが.NET Standardや.NET Coreのプロジェクトから参照可能となるモードです。
このモードでアセンブリを参照すると、パッケージが正しく動作しているかどうかをテストする必要があるため、ビルド時にパッケージフォールバック警告NU1701が表示されます。
つまり、プロジェクトに設定されているTargetFrameworkと非互換のアセンブリが参照されていることの警告を表示します。
アプリケーションをテストして問題ないことが確認できた場合、そしてこの警告が気になる場合は、プロジェクトファイル内にあるPackageReferenceを検索し、NoWarn属性を追加することで警告を消すことができます。
<ItemGroup> <PackageReference Include="Huitian.PowerCollections" Version="1.0.0" NoWarn="NU1701" /> </ItemGroup>
NoWarn属性を追加した場合、以降は警告表示が行われないので、.NET Framework互換モードでライブラリ追加を行う場合は、動作に問題がないかきちんとテストするようにしましょう。
.NET Frameworkから.NET Coreへ(1)
Microsoftが推奨している.NET Frameworkから.NET Coreへの移行のプロセスは以下の通りです。
- 対象となるプロジェクトを.NET Framework 4.7.2以降をターゲットにするよう修正する。
- .NET Portability Analyzerを使用して移行が可能かどうかアセンブリの分析を行う。
- .NET API アナライザーを使用し、.NET Core上でAPIが利用できるかどうかを評価する。
- Windows互換機能パックの利用を検討する。
- Visual Studioの機能を利用して依存関係の記述をpackage.configからPackageReferenceへ変換する。
- プロジェクトファイルの変換を行う。
1.対象となるプロジェクトを.NET Framework 4.7.2以降をターゲットにするよう修正する。
.NET Frameworkと.NET CoreのAPIの違いを評価する場合、移行対象となるプロジェクトをすべて.NET Framework 4.7.2以降のバージョンに再ターゲットします。これによって、後述する各種ツールを使用して利用しているAPIの評価を行うことができるようになります。
2..NET Portability Analyzerを使用してアセンブリの分析を行うことで移行が可能かどうかを検出する。
.NET Portability Analyzerは、Visual Studioの機能拡張のひとつで、プロジェクトを分析し、移行先バージョンのフレームワークへの移植可能性を評価することができるツールです。
.NET Portability Analyzerは、Visual Studio 2017以降で動作します。
上記のリンク、もしくはVisual Studioのメインメニューにある[拡張機能]からインストールすると、メインメニューの[ツール]→[オプション]に[.NET Portability Analyzer]の項目が追加されます。
このオプションで移行対象となるフレームワークを選択したり、評価結果の出力先、ファイル名、フォーマットを指定できたりします。
なお、このオプションはプロジェクトのコンテキストメニューから[Portability Analyzer Settings]を選択する方法でも呼び出せます。
続けて、ソリューションエクスプローラーからプロジェクトを右クリックし、表示されたコンテキストメニューから[Analyze Project Portability]を選択すると分析が開始されます。
分析の実行中は[出力]ウインドウで分析の進捗状況を確認できます。
分析が完了すると[Portability Analysis Results]ウインドウが開き、分析結果のファイルにアクセスできます。
分析結果には、分析対象となったプロジェクト単位の評価結果一覧と、プロジェクトに含まれる型の単位の詳細や移植可能性の情報を確認することができます。
分析結果はオプションの指定によって、Excel、HTML、JSONの形式で出力できます。以下の図はExcelファイルで出力されたサンプルです。Portability Summery(移植可能性の概要)とDetails(詳細)シートが追加されていることが確認できます。
.NET Frameworkから .NET Coreへ(2)
3..NET API アナライザーを使用して、.NET Coreで利用できないAPIを検出して、APIが利用できるかどうかを評価する。
.NET API アナライザーは、コード上に含まれる互換性リスクや非推奨APIの検出などを行うRoslynベースのコード分析アナライザーです。NuGetパッケージで公開されており、Visual Studioでプロジェクトに追加すると自動的にコードの監視が行われます。
前述した.NET Portability Analyzerと異なり、移行の分析目的だけではなく、以下のようなAPIの検出を行います。
- .NET Standardで利用することができない NET Framework 4.6.1移行のAPI
- .NET Standardで実行するとPlatformNotSupportExceptionをスローしてしまうAPI
- Windows以外のクロスプラットフォームの実行時に問題が発生する可能性のあるAPI
- 現在は非推奨となっているAPI
実行するためにはNuGetパッケージMicrosoft.DotNet.Analyzers.Compatibilityをプロジェクトに追加します。
パッケージマネージャーコンソールから行う場合、以下を実行します。
Install-Package Microsoft.DotNet.Analyzers.Compatibility -Version 0.2.12-alpha
追加するとコードエディターの問題の箇所にライトが表示されて警告内容を確認できます。
また、警告の一覧は[エラー一覧]ウインドウの中にも表示されます。
以下のリンクも参考にしてください。
4.Windows互換機能パックの利用を検討する。
.NET Portability Analyzerや.NET API アナライザーで検出された.NET Core未対応の機能のうち、.NET Framework(つまり、Windows)のみ存在するテクノロジーについては、Windows互換機能パックを利用できる場合があります。
Windows互換機能パックは.NET Standard 2.0の拡張機能であり、.NET Frameworkにのみ存在していた多くのWindows依存機能を利用できるようにするためのパッケージです。NuGetパッケージMicrosoft.Windows.Compatibilityとして提供されています。
ただし、このパッケージを使用する箇所についてはWindows上でしか利用できません。そのため、クロスプラットフォームで実行するモジュールを作成する場合、これらの機能が利用できないよう判定を行う必要があります。判定は以下のコードで行うことができます。
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { // Windows上でのみ実行するコード }
Windows互換機能パックを使用することで利用可能となる機能については、以下のページで確認できます。
5.Visual Studioの機能を利用して依存関係の記述をpackage.configからPackageReferenceへ変換する。
NuGetパッケージへの依存がある場合、package.configからPackageReferenceへ変換する必要があります。
従来、.NET Frameworkでは依存関係のあるパッケージをpackage.configで管理していましたが、.NET Coreでは依存関係のあるライブラリは、すべてプロジェクトファイル内で直接管理するようになりました。
Visual Studio 2017 Version 15.7以降には、package.config形式からPackageReference形式へのプロジェクト移行を行う機能が追加されました。
この機能を使用することでソリューション内のpackagesフォルダーで管理されていたパッケージがグローバルパッケージフォルダーで管理するように変更されます。
また、package.configに対する新機能の開発は今後継続されないと表明されています。
package.configからPackageReferenceへの移行は、プロジェクトエクスプローラーから[package.config]を右クリックしてコンテキストメニューから[package.configをPackageReferenceに移行する]を選択します。(※1)
※1:現在、既知の問題として[package.configをPackageReferenceに移行する]のメニューを表示しない場合があるとMicrosoftのdocsに記載されています。docsに回避策が記されているので、表示されない場合はお試しください。
package.configの解析によって、[最上位レベルの依存関係]と[遷移的な依存関係]に分類されます。
[最上位レベルの依存関係]は、プロジェクトに直接インストールしたNuGetパッケージを示しています。[遷移的な依存関係]は、プロジェクトに直接インストールしたパッケージの依存関係として導入されたNuGetパッケージと判定されたパッケージです。
[遷移的な依存関係]と判定されたパッケージは[最上位]にチェックを行うことで[最上位レベルの依存関係]として扱うことができるようになります。
以上で問題なければ、[OK]ボタンをクリックすることで移行が行われます。移行の結果、問題があるようであれば、ロールバックすることもできます。
ロールバックする場合の手順は以下の通りです。
- 移行されたプロジェクトを閉じます。
-
package.configのバックアップ(通常は
<solution_root>\MigrationBackup\<unique_guid>\<project_name>\
に作成される)からプロジェクトフォルダーにコピーします。 - プロジェクトを開きます。
- メインメニューから[ツール]>[NuGetパッケージマネージャー]>[パッケージマネージャーコンソール]を開き、以下のコマンドを実行します。
update-package -reinstall
移行が正常に行われたら、NuGet.Build.Tasks.Packパッケージの追加を行います。
6.プロジェクトファイルの変換を行う
.NET Coreで使用するプロジェクトファイルは、.NET Frameworkと同様の「*.csproj」という拡張子のテキストファイルです。
このファイルはプロジェクト内の構成ファイルや依存関係、ビルド方法などを記述し、MSBuildを使用してビルドされます。しかし、以前のプロジェクトファイルは、記述形式すべき項目が多く、可読性も低いことから、現在の.NET Coreで使用されるプロジェクトファイルでは、より軽量な記述が行えるよう改良が加えられました。
例えば、この後作成するサンプルアプリのプロジェクトファイルは以下のように記述されています。
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop"> <PropertyGroup> <OutputType>WinExe</OutputType> <TargetFramework>netcoreapp3.1</TargetFramework> <UseWPF>true</UseWPF> </PropertyGroup> <ItemGroup> <PackageReference Include="C1.Win.C1TileControl.Ja" Version="4.5.20193.393" /> </ItemGroup> </Project>
見ての通り、構成ファイルやビルドオプションなどの細かな記述はなく、アプリをビルドするために必要最低限の情報を記述することで、アプリの種類に合わせた参照やビルドオプションなどの初期設定は既定値のものがそのまま適用されます。それ以外にも、以前よりも簡素な記述ができるように、さまざまな機能追加が行われており、以前の形式から進化しています。
すでに新しいプロジェクトファイルで作成されている場合は、TargetFrameworkを.NET Coreで利用できるFrameworkに変更するだけで問題ありません。ですがプロジェクトファイルが以前の形式である場合、プロジェクトファイルの移行を行う必要があります。
プロジェクトファイルの移行は、.NET Coreの新しいプロジェクトを作成してソースコードを移行するか、ツールを利用してプロジェクトファイルの変換を試みます。
小規模なソリューションや小さなプロジェクトであれば、dotnet try-convertという変換ツールを利用できる場合があります。このツールはプロジェクトファイルの形式を新形式に変換を試みてくれます。ただし、必ずしも正しく変換できることを保証するものではありません。また、ツールを利用したことで動作に変化が発生する可能性もあるため、あくまで補助的なツールとして利用します。
いずれの場合でも、できあがったプロジェクトに対して、しっかりとしたテストを行うことが必要です。
.NET Coreで使用できない.NET Frameworkテクノロジー
.NET Coreでマルチプラットフォーム対応になったことで、Windows独自の機能をサポートするためのライブラリが公開されました。
このライブラリを利用することで.NET Core環境でも多くの機能が継続して利用できるようになりますが、Frameworkの最適化や最新化を行う中で加えられた破壊的変更も存在します。
代表的なものとして以下が挙げられます。
- AppDomain
- .NET Remoting
- Code Access Security(CAS)
- 透過的セキュリティコード
- System.EnterpriseServices
他にも、どのような破壊的変更が存在するのかを確認するページが用意されています。ターゲットバージョンと移行バージョンを選択すると、バージョン間で行われた破壊的変更を確認できます。
- 参考リンク:破壊的変更のセレクター
WinFormsアプリのマイグレーションとモダナイゼーション
サンプルアプリ
WinFormsアプリのケースでサンプルとして使用するのは以下のアプリです。
実行し、Buttonをクリックすると以下のように表示されます。
WinFormsアプリのマイグレーション手順
マイグレーション時の注意点
現在、WinFormsのフォームデザイナーは、VS2019のPreviewでのみ利用可能であり、製品版のVS2019では利用することができません。Previewは、評価目的の用途に限定されており、業務における開発時には利用不可となっています。
現在はPreviewですが、時期がくれば利用できるようになると思われます。そこまでの暫定処置として以下の回避策を用いる必要があります。
デザイナーを使った開発を行いたい場合は、.NET Frameworkベースのプロジェクトを残しておき、ソースを.NET Coreのプロジェクトにポーティングするなどの工夫が必要です。
移行は前述した通り、以下の流れで実施します。
- .NET Portability Analyzerや.NET API アナライザーなどを使用して互換性の問題がないか確認する。
- Windows互換機能パックの利用を検討する。
- NuGetパッケージの更新を行う。
- プロジェクトの移行を行う。
それでは、流れに沿ってWinFormsプロジェクトの移行とモダナイゼーションを行います。
- C1ThemeControllerを追加する。
- .NET Portability Analyzerを使用して、移行可能かどうかの判定を行う。
- NuGetからMicrosoft.DotNet.Analyzers.Compatibilityパッケージを追加し、.NET API アナライザーを実行する。
- 判定結果で問題が出た場合、Windows互換機能パックMicrosoft.Windows.Compatibilityを使用して回避可能かどうかを検討する。回避可能である場合はパッケージを追加する。
- プロジェクトファイルを移行する。
上記のうち、1と5以外の手順については説明済みであるため、C1ThemeControllerを追加する手順、プロジェクトファイルの移行手順について紹介します。
ツールボックスからC1ThemeControllerを追加
.NET Framework版のWinFormsアプリプロジェクトにC1ThemeControllerを追加します。ツールボックスから対象のフォームにC1ThemeControolerをドラッグアンドドロップすると以下のウインドウが表示されます。
テーマは[アプリケーション全体][デザイン中のフォーム内][デザイン中のフォーム内にある特定のコントロール]の3つのスコープで適用することができます。
コントロールが配置済みであるフォームにドラッグアンドドロップすると、フォーム内にあるコンポーネントを検出し、どのようにテーマを適用するか編集することができます。
今回、ウインドウ内の各項目は以下の通り設定します。
- アプリケーションテーマを(none)
- c1ThemeController1テーマをExpressionDark
- 各コンポーネントのテーマはすべてデフォルトボタンをクリックして、(default)を適用
編集画面で[OK]ボタンをクリックすると「設定ファイルに内容を格納するか」の確認ダイアログが表示されるので[Yes]ボタンをクリックします。
デザイナー上でThemesが適用されたことを確認することができます。
プロジェクトの移行手順
プロジェクトは以下の手順で移行を行います。
- ソリューションにプロジェクトを追加
- プロジェクトにファイルを複写
- NuGetからパッケージを取得
- テスト
互換性に関する手順を済ませたのち、ソリューションに移行先となる.NET CoreのWindows Form Appを追加します。
追加したプロジェクトにファイルをコピーします。このとき、ComponentOneのライセンスファイルlicense.licxファイルがある場合、忘れずにコピーしてプロジェクトに追加します。
パッケージが存在する場合は、Update Packageを実行します。ここでは、C1ThemeControlが追加されているので、NuGetでGrapeCityのレポジトリからパッケージをダウンロードします。
.NET Coreプロジェクトを右クリックして、コンテキストメニューから[NuGetパッケージの管理]を選択します。
パッケージソースにGrapeCityを指定します。続けて、キーワードをC1Themesで絞り込みを行い、インストールします。
もし、パッケージソースにGrapeCityが追加されていない場合、ギアのアイコンをクリックします。
利用可能なパッケージソースの上にある[+]ボタンをクリックします。
項目 | 設定値 |
---|---|
名前 | GrapeCity |
ソース | http://nuget.c1.grapecity.com/nuget |
[ライセンスへの同意]ウインドウが表示されるので、[同意]ボタンをクリックします。
正しく移行ができると、以下の通り起動します。
WPFアプリのマイグレーションとモダナイゼーション
サンプルアプリ
WPFアプリのケースでサンプルとして使用するのは以下のアプリです。
実行すると以下の通り表示されます。
UIとしてはListBoxにItemTemplateを適用し、表示をカスタマイズしています。
ソースコードについて
ViewModelBase.cs
ViewModelの基底クラスとしてViewModelBase.csという抽象クラスを定義しています。
using System.ComponentModel; using System.Runtime.CompilerServices; namespace FxWpfApp { public abstract class ViewModelBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public void NotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
StatusItem.cs
項目格納用クラスとしてStatusItem.csを追加しています。
namespace FxWpfApp { public class StatusItem : ViewModelBase { private string header; public string Header { get => header; set { header = value; NotifyPropertyChanged(); } } private string body; public string Body { get => body; set { body = value; NotifyPropertyChanged(); } } public StatusItem() { } public StatusItem(string header, string body) { Header = header; Body = body; } } }
MainWindowViewModel.cs
そして、MainWindowのViewModelクラスを定義しました。
using System; using System.Collections.ObjectModel; using System.Runtime.InteropServices; namespace FxWpfApp { public class MainWindowViewModel : ViewModelBase { public ObservableCollection<StatusItem> StatusItems { get; set; } = new ObservableCollection<StatusItem>(); public MainWindowViewModel() { StatusItems.Clear(); StatusItems.Add(new StatusItem("User", Environment.UserName)); StatusItems.Add(new StatusItem("Framework", RuntimeInformation.FrameworkDescription)); StatusItems.Add(new StatusItem("Assembly Location", typeof(string).Assembly.Location)); } } }
MainWindow.cs
メインとなるXAMLには、ListBoxを使用します。ItemTemplateにStatusItem.csが表示できるようDataTemplateをカスタマイズしています。
<Window x:Class="FxWpfApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:FxWpfApp" mc:Ignorable="d" Title="FxDemoApp" Height="245.492" Width="800"> <Window.Resources> <DataTemplate x:Key="DataTemplate1"> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Header}" Width="200"/> <TextBlock Text="{Binding Body}" Width="400"/> </StackPanel> </DataTemplate> </Window.Resources> <Window.DataContext> <local:MainWindowViewModel/> </Window.DataContext> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="5"/> <ColumnDefinition Width="649*"/> <ColumnDefinition Width="5"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> <RowDefinition Height="3*"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Rectangle Fill="#FF2B4FFB" Grid.ColumnSpan="3" Margin="0,0,0.667,0" /> <TextBlock Text="Fx WPF App" Foreground="White" FontSize="30" Margin="5" Grid.ColumnSpan="3"/> <!-- データの表示はListBoxのみ --> <ListBox Grid.Row="2" Grid.Column="1" Margin="5" ItemsSource="{Binding StatusItems}" ItemTemplate="{DynamicResource DataTemplate1}" /> </Grid> </Window>
WPFアプリのマイグレーション手順
マイグレーションの基本的な流れはWinFormsアプリとほぼ同様です。
今回はモダナイゼーションのアプローチにComponentOneのTiles for WPFを使用しています。
全体の手順は以下の通りです。
- ItemsTemplateに指定しているDataTemplateをTiles for WPFを使用したものに更新する。
- .NET Portability Analyzerや.NET API アナライザーなどを使用して互換性の問題がないか確認する。
- Windows互換機能パックの利用を検討する。
- NuGetパッケージの更新を行う。
- プロジェクトの移行を行う。
2や3の方法はすでに説明した内容と同じなので、ここでは1、5、6の手順について解説します。
ItemsTemplateに指定しているDataTemplateをTiles for WPFを使用したものに更新
現在のXAMLから以下の手順で更新します。
- DataTemplateに指定されていた内容をComponentOneのC1Tileに入れ替える。
- C1Tileに対するStyleを定義する。
- ItemsPanelにC1WrapPanelを指定する。
- MainWindowのHeightを調整する。
DataTemplateに指定されていた内容をComponentOneのC1Tileに入れ替える
ツールボックスからC1TileをXAMLのWindows.Resources要素内に定義されているDataTemplateにドラッグして、属性を以下の通り書き換えます。
<DataTemplate x:Key="DataTemplate1"> <c1:C1Tile Padding="0" Content="{Binding Header}" Header="{Binding Body}" HeaderBackground="#22000000" HeaderPadding="12" HorizontalHeaderAlignment="Stretch" Background="#FFFF2610" /> </DataTemplate>
ツールボックスからではなく、XAMLを直接記述する場合、ルート要素であるWindow要素に以下のXML名前空間を追加します。
xmlns:c1="http://schemas.componentone.com/winfx/2006/xaml"
C1Tileに対するStyleを定義する
同じくWindows.Resourcesに対してC1Tileで利用する既定のスタイルを以下の通り追加します。
<Style TargetType="c1:C1Tile"> <Setter Property="Background" Value="#FFC410" /> <Setter Property="Foreground" Value="White" /> <Setter Property="FontSize" Value="28" /> <Setter Property="TextOptions.TextFormattingMode" Value="Display" /> <Setter Property="HeaderForeground" Value="White" /> <Setter Property="HeaderFontSize" Value="12" /> <Setter Property="BorderThickness" Value="0" /> <Setter Property="Width" Value="280" /> <Setter Property="Height" Value="200" /> </Style>
ItemsPanelにC1WrapPanelを指定する
ListBoxの記述にItemsPanelの定義を追加します。
<ListBox Grid.Row="2" Grid.Column="1" Margin="5" ItemTemplate="{DynamicResource DataTemplate1}" ItemsSource="{Binding StatusItems}"> <ListBox.ItemsPanel> <ItemsPanelTemplate> <c1:C1WrapPanel /> </ItemsPanelTemplate> </ListBox.ItemsPanel> </ListBox>
MainWindowのHeightを調整する
要素の高さが不足するので、MainWindowのHeightプロパティを500に変更します。ここまでの手順を正しく行えると以下のXAMLになります。
<Window x:Class="FxWpfApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:c1="http://schemas.componentone.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:FxWpfApp" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Title="FxDemoApp" Width="800" Height="500" mc:Ignorable="d"> <Window.Resources> <Style TargetType="c1:C1Tile"> <Setter Property="Background" Value="#FFC410" /> <Setter Property="Foreground" Value="White" /> <Setter Property="FontSize" Value="28" /> <Setter Property="TextOptions.TextFormattingMode" Value="Display" /> <Setter Property="HeaderForeground" Value="White" /> <Setter Property="HeaderFontSize" Value="12" /> <Setter Property="BorderThickness" Value="0" /> <Setter Property="Width" Value="280" /> <Setter Property="Height" Value="200" /> </Style> <DataTemplate x:Key="DataTemplate1"> <c1:C1Tile Padding="0" Background="#FFFF2610" Content="{Binding Header}" Header="{Binding Body}" HeaderBackground="#22000000" HeaderPadding="12" HorizontalHeaderAlignment="Stretch" /> </DataTemplate> </Window.Resources> <Window.DataContext> <local:MainWindowViewModel /> </Window.DataContext> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="5" /> <ColumnDefinition Width="649*" /> <ColumnDefinition Width="5" /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> <RowDefinition Height="3*" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Rectangle Grid.ColumnSpan="3" Margin="0,0,0.667,0" Fill="#FF2B4FFB" /> <TextBlock Grid.ColumnSpan="3" Margin="5" FontSize="30" Foreground="White" Text="Fx WPF App" /> <!-- データの表示はListBoxのみ --> <ListBox Grid.Row="2" Grid.Column="1" Margin="5" ItemTemplate="{DynamicResource DataTemplate1}" ItemsSource="{Binding StatusItems}"> <ListBox.ItemsPanel> <ItemsPanelTemplate> <c1:C1WrapPanel /> </ItemsPanelTemplate> </ListBox.ItemsPanel> </ListBox> </Grid> </Window>
修正が終わったら、保存してビルドし、実行できるか確認します。
プロジェクトの移行
NuGetなどから追加で使用したPackageが存在する場合、ソリューションエクスプローラーからpackage.configの移行を行います。
[NuGetの形式をPackageReferenceに移行します]ウインドウが表示されたら[OK]ボタンをクリックして移行します。
互換ライブラリの調査など、移行のための手順が終了したら、WPF App(.NET Core)プロジェクトをソリューションに追加します。今回はCoreWpfAppの名前でプロジェクトを作成します。
.NET FrameworkプロジェクトのファイルをWPF App(.NET Core)プロジェクトにコピーします。
ソリューションエクスプローラーからFxWpfAppプロジェクトの参照を確認し、.NET Framework互換モードで動作するライブラリを確認後、CoreWpfAppの参照に追加します。
ComponentOneのコントロールは、いずれも.NET Framework互換モードで動作するため、ここでは以下の3つのライブラリをCoreWpfAppから参照します。
.NET Coreプロジェクトへ参照の追加する方法は、ソリューションエクスプローラーからCoreWpfAppプロジェクトを右クリックしてコンテキストメニューを表示します。
表示されたメニューから[追加]→[参照]の順に指定します。
参照マネージャーが表示されるので、使用するライブラリの追加を行います。
ライブラリは、元プロジェクト(今回の場合はFxWpfApp)のbinフォルダーなどから参照できるので、参照マネージャーの[参照]ボタンをクリックし、使用するライブラリファイルを追加します。追加するファイルの指定は複数同時に行うことも可能です。
続けて、NuGetから参照するライブラリがある場合は、ソリューションエクスプローラーから.NET Coreアプリのプロジェクトを右クリックし、NuGet Packageの追加を行います。
正しく移行ができると、以下の通りに起動します。
まとめ
.NET Coreに対するマイグレーションは、ツールやライブラリのサポートなども充実していることもあり、単純なものであれば比較的スムーズに行うことができます。
しかし、古いライブラリを利用していたり現在非推奨のAPIを利用していたりするケースでは、別の方法に切り替える必要があります。
スムーズに移行が行えた場合も、テストをしっかり行って動作の確認を行う必要があります。そのため、移行にはそれなりの工数を見込む必要があるでしょう。
しかし、移行するだけで得られることができるパフォーマンスの向上や今後登場する新しい機能を利用できるなどのメリットも多くあり、極力早めに対応したいところです。