.NET WPF サンプル集 [.NET 6, netFx47 対応]

このサンプル集は今のところ C# だけだが、他にも手を広げるかも。広げないかも。

リポジトリ: netsphere / dotnet-wpf-examples · GitLab

01~ クライアント側

2023年3月現在, .NET 5 と .NET Core 3.1 はすでにサポート期限が切れている。クライアント側は、3rd party ライブラリや Microsoft のライブラリも、非互換のため .NET 6 で動かないものもある。

.NET Framework v4.7 で様子見も選択肢。ライブラリが上げられれば、パフォーマンス向上を享受できるので、.NET ヴァージョンを上げればよい。ただ、無理しなくてよい。

01_wpf-validation-rules/

  • View と view model との分離: View model は INotifyPropertyChanged インタフェイスを実装するのではなく, DependencyObject class から派生させるべき。
  • View に bind するクラスは, IDE の支援を受けるため, ソースコードではなく XAML の <Window.DataContext> で指定する。ここでの指定は, 原則として "型". インスタンスが自動的に生成される。View と view model は 1:1 になる。
  • データ検証: IDataErrorInfo インタフェイスは古い. INotifyDataErrorInfo インタフェイスを実装する。もっと良いのは, ValidationRule クラスから派生させ, XAML 側から検証器を指定する。入力コントロールごとの検証と, submit 時の検証とがある。
  • エラーの表示: フィールドの近くに表示するのが UI 観点でも good. XAML の <Window.Resources> 内に <ControlTemplate> 要素を記述する。

次のように, XAML で検証器を指定する。

HTML/XML
[RAW]
  1. <TextBox Name="numberBox" HorizontalAlignment="Left" Height="23" Margin="85,141,0,0"
  2. Validation.ErrorTemplate="{StaticResource errorTemplate}"
  3. VerticalAlignment="Top" Width="120">
  4. <TextBox.Text>
  5. <!-- XAML 属性値の {...} も, 子要素として書いてよい。というかそれの簡略
  6. 記法.
  7. TextBox の UpdateSourceTrigger の default 値は LostFocus. リアルタイ
  8. ム検証したいときは変更すること。-->
  9. <Binding Path="NumberBox" UpdateSourceTrigger="PropertyChanged">
  10. <!-- 検証器は複数書ける. DependencyProperty.Register() では一つしか書
  11. けない. -->
  12. <Binding.ValidationRules>
  13. <local:ValidatesPresence />
  14. <local:ValidatesIntInclusion Min="1" ValidationStep="RawProposedValue" />
  15. </Binding.ValidationRules>
  16. </Binding>
  17. </TextBox.Text>
  18. </TextBox>

参考: Data validation in WPF | Magnus Montin

02_ListView-DataVirtualization/

ListView のサンプル. Forked from WPF: Data Virtualization - CodeProject Original license: public domain

表形式で大量のデータを表示する場合、データ全部をメモリに載せると大変。Data virtualization で, 表示するのに必要なデータだけ view model から提供する。

このサンプルでは比較のため, List<T> (全部をメモリに載せる), VirtualizingCollection<T> および AsyncVirtualizingCollection>T> という3つのクラスを用意した。ListView.ItemsSource プロパティに設定する,

ListView.ItemsSource プロパティが受け付けるのは, 非ジェネリックな IEnumerable. このプロパティにコレクションを set すると, 内部で ListView.Items プロパティ (ItemCollection 型) に紐づけられ, ItemsSource モードになる。

非常に分かり辛くなっているのは, ItemsSource プロパティには, コレクションだけでなく, コレクションを wrap した collection view を設定してもよいということ。コレクションを設定した場合は, 内部で collection view が自動生成される。

このサンプルは, コレクションを拡張する方向で作っている。Collection view を拡張するやり方は, サンプル #05 にて。

ItemsSource に設定するオブジェクトによって, 内部で生成される collection view の型が変わってくる。相当ゴチャゴチャしていてよく分からないが、こうかな?

  1. IListSource: コレクションの GetList()IList オブジェクトを返せば、それを使って再度、判定。
  2. ICollectionView collection view: それを使う.
  3. ICollectionViewFactory: CreateView() する。戻り値は ICollectionView 型.
  4. IBindingList コレクション: BindingListCollectionView で wrap.
  5. IList (非ジェネリック): ListCollectionView で wrap.
  6. IEnumerable (非ジェネリック): internal class EnumerableCollectionView で wrap.

イケてないが過去との互換性のため, コンテナが非ジェネリック版を強制される。IEnumerable 派生では機能が足りずエラーになったりするので, 拡張するときは, 非ジェネリックな IList から派生させること。this[]GetEnumerator() を実装する。

03_dotnet-http2-sample/

サーバに HTTP/2 でリクエストを投げるサンプル。 TargetFrameworkVersion: v4.7.2

Topic 1. MenuItem.CommandButton.Command の使い方。

ググると DelegateCommand を使う例ばかりだが、妥当ではない。ショートカットキーの設定に <Window.InputBindings>, <KeyBinding Key="B" Modifiers="Control" ... のようにしたりするが、的外れ。

WPF の「コマンド」は, UI Text や Key Gesture (ショートカットキー) を定義する RoutedUICommand オブジェクトと, それを command handler に紐づける CommandBinding の二段階になっている。

"コマンド" として ApplicationCommands.Close などの標準コマンドも使える。自作のコマンドは x:Static で設定する。

HTML/XML
[RAW]
  1. <MenuItem Header="ファイル(_F)">
  2. <MenuItem Header="終了(_X)" Command="ApplicationCommands.Close" />
  3. </MenuItem>
HTML/XML
[RAW]
  1. <Button Name="fetchButton" Grid.Column="2" Content="Fetch" Width="75"
  2. Command="{x:Static local:MyCommands.FetchCommand}" />

これらのコマンドとハンドラとを CommandBinding で結びつける。引数で canExecute ハンドラも渡せる。

Topic 2. テキストフィールドの値によって, Command プロパティを持つボタンの有効・無効を切り替える。

  1. <TextBox><Binding.ValidationRules> でヴァリデータを設定する。
  2. ヴァリデータから view model に状態を通知し, ICommand.CanExecute に反映させたい。TextBoxValidation.HasError attached property があってエラーの有無を取れるが、bind できない。このようなときは behaviour を使う。ググると旧い System.Windows.Interactivity パッケージを使う例ばかり出てくる。そんなの不要。

    なお, 今回の例では使わないが, System.Windows.Interactivity パッケージは廃れて, Microsoft.Xaml.Behaviors.Wpf パッケージが後継になっている。

    ValidationBehavior が作ったビヘイビア。似たような状況はサンプル #14 にもある。

    HTML/XML
    1. <TextBox Grid.Column="1" Name="urlBox" Height="23" Margin="0,0,10,0"
    2. Validation.ErrorTemplate="{StaticResource errorTemplate}"
    3. local:ValidationBehavior.HasErrors="{Binding HasErrors}"
    4. KeyDown="urlBox_KeyDown" >
    5. <TextBox.Text>
    6. <Binding Path="UrlBox" UpdateSourceTrigger="PropertyChanged"
    7. NotifyOnValidationError="True" >
    8. <Binding.ValidationRules >
    9. <local:ValidatesPresence />
    10. <local:ValidatesURI />
    11. </Binding.ValidationRules>
    12. </Binding>
    13. </TextBox.Text>
    14. </TextBox>
  3. WPF の <Button>ICommand.CanExecute の状態変化に伴って、有効・無効が自動的に切り替わる。

Topic 3. HTTP/2 サポート。

.NET Framework 4.7 で HTTP/2 を使うには NuGet パッケージ System.Net.Http.WinHttpHandler の WinHttpHandler クラスを使わなければならない。.NET Core 3.0 以降は素の HttpClient でも HTTP/2 がサポートされる。

csharp
[RAW]
  1. static readonly HttpClient http_client =
  2. new HttpClient(new WinHttpHandler() {
  3. AutomaticRedirection = false });

04_wpf-sample-for-calling-winrt-apis/

UWP アプリ用の API "Windows Runtime APIs" (WinRT APIs) を呼び出す。現代は, Microsoft.Windows.SDK.Contracts パッケージを使えばよい。

たとえば, Windows.Globalization.JapanesePhoneticAnalyzer class で、日本語の形態素解析が手軽に可能。

Microsoft.Windows.SDK.Contracts のターゲット: `netstandard2.0` .. .NET Framework 4.6+, .NET Core 3.0+

ターゲットフレームワーク ".NET 6.0" 以降, ターゲット OS バージョンを 10.0.17763.0 (v1809) 以降だと、何もしなくても WinRT API が呼び出せるようになった。 .NET 5.0 の破壊的変更 (CoreCLR の変更) で、逆に, `Microsoft.Windows.SDK.Contracts` パッケージへの参照は削除しなければならない。

サンプル #09 も参照されたい。

05_DataGrid-DataVirtualization/

再び data virtualization について。今度は <DataGrid> に適用する。以前のサンプルと異なり, collection view を拡張する方向。

<DataGrid> は列ヘッダのクリックでソートできる。Data virtualization とどのように組み合わせるか。

Collection クラスではなく, collection view クラスを作り、これを DataGrid.ItemsSource に bind する。手を抜くため, ListCollectionView クラスから派生して最小限だけ override するのがよい。サンプル #02 も参照されよ。

07_SimpleGraphicEditor/

GUI, ファイル保存

09_wpf-with-xaml-islands/

XAML 諸島のサンプル。サンプル #04 の続き。 TargetFrameworkVersion: v4.7.2

XAML 諸島は、WPF のウィンドウに、UWP のコントロールを配置する。このサンプルでは、カメラとキャプチャ Windows.UI.Xaml.Controls.CaptureElement クラスと MessageDialog クラスを使っている。

2023年3月現在, .NET 6 では XAML 諸島は使えない。.NET Core 3.1 のサポート期間も2022年12月で終了した。 .NET Framework 4.7 を使え。ひどい。

11_DynamicColumnsInDataGrid/

<DataGrid> の「列」を動的に追加する。この目的のときだけ, 列指向である DataTable クラスを使う。

DataTable クラスは、まあまあ罠が多い

14_Notepad-wpf/

MVC モデル, Undo-Redo の実装。添付ビヘイビア。

15_weak-listener/

WPF は, view と view model は 1:1 になっていて、寿命も同じ。アプリケィションは通常 MVC モデルで作り, WPF では document (model) の変更を view model で察知する。View & view model のほうが document よりも寿命が短い。

INotifyPropertyChanged インタフェイスの PropertyChanged イベントはターゲット (listener) を強参照で保持するため, view を閉じたときにイベントハンドラを remove しなければ, メモリリークする。

このサンプルでは, view model 側のイベントハンドラを document に登録するときに, Mediator デザインパタンの PropertyChangedEventManager を挟む. イベントハンドラ側が弱参照になって、view を単に破壊してもよくなる。

非GUI, C#

10_data-protect/

分離ストレージ (Isolated Storage) は、どの程度データが保護されるのか?

結果:

  • 分離ストレージ機能は、伝統的なデスクトップアプリでも、UWP アプリでも、どちらでも使える
  • 分離ストレージでは、データは保護されない! UWPアプリは自パッケージのデータにしかアクセスできないが、伝統的なデスクトップアプリではそのような制限はなく、他のアプリの分離ストレージに難なくアクセスでき、しかも暗号化されていない。
  • 実体はただのファイルなので、端末の管理者ユーザと、ユーザが起動した悪意あるアプリから、どちらもアクセス可能。
  • Windows 10/11 in S mode のように UWP アプリしか実行できない環境でない限り、分離ストレージだけでは、保護にならない

何ですと!

Data Protection API (DPAPI) を組み合わせる:

  • User or machine credentials と additional entropy を使って暗号化する
  • 別プロセスからも、同じ entropy を使えば復号できる
  • 端末の管理者ユーザからは守れるが、ユーザが起動した悪意あるアプリからは保護できない。カジュアルには元データを詐取できないが、逆アセンブルすればエントロピーを入手できるため、横から復号可能。
  • とはいえ、悪意あるアプリを起動した時点でやりたい放題なので、保護レベルとしてはこれが精一杯。
これ以上の保護レベルが必要な場合は、アプリの起動時に毎回認証して、ローカルストレージには秘密鍵を一切保存しないようにしないといけない。金融アプリは、普通はそうなっている。

12_ReadExcel/

Excel ファイルを読み込み、表に表示する。表示にウィンドウを使っているが、主眼はファイルを読み込むところ。

16_aop-auto-inotifypropertychanged/

アスペクト指向プログラミング (AOP). TargetFramework: net6.0

いちいち INotifyPropertyChanged (INPC) インタフェイスを実装するようにクラスを書くのは、非常に面倒くさい。オブジェクトのプロパティに値を代入するだけで自動的に notify するようにする。

次のクラスが実装例。このクラスで, INotifyPropertyChanged (INPC) はどこにも出てこない。

csharp
[RAW]
  1. internal class Customer
  2. {
  3. // 注意! `virtual` が必要
  4. [AlsoNotifyFor("FullName")]
  5. public virtual string GivenName { get; set; }
  6. [AlsoNotifyFor("FullName")]
  7. public virtual string FamilyName { get; set; }
  8. public string FullName {
  9. get { return $"{GivenName} {FamilyName}"; }
  10. }
  11. public virtual string Address { get; set; }
  12. [DoNotNotify]
  13. public virtual string Hoge { get; set; }
  14. }

INPC にキャストして使う:

csharp
[RAW]
  1. var c = AutoNotifyInterceptor<Customer>.CreateInstance();
  2. var o1 = new Observer("a");
  3. ((INotifyPropertyChanged) c).PropertyChanged += o1.OnThingPropertyChanged;

ちゃんとしたライブラリでは, C# Attributes をクラスに付けて、オブジェクトの生成すら隠蔽できる。例えば, MVVM Toolkit v8 では次のようになる。See .NET用 MVVM Toolkit v8でMVVMコードを短く

csharp
[RAW]
  1. [INotifyPropertyChanged]
  2. internal partial class Vm
  3. {
  4. [ObservableProperty]
  5. private string name;
  6. [RelayCommand]
  7. private void Greet(string user) {
  8. Debug.WriteLine($"Hello {user}!");
  9. }
  10. }

余談だが, MVVM Light の後継が Microsoft MVVM Toolkit, 名前が変わって CommunityToolkit.Mvvm パッケージ. このパッケージは .NET Community Toolkit プロジェクトの一部。ヤヤコシイことに, 別系統の Windows Community Toolkit があって, そちらは UWP (WinUI2), WindowsAppSDK/WinUI3, Uno Platform 用。ヤヤコシや

データベース操作

EF Core + SQLite サンプルや, EF6 サンプル。

06_SqliteMigrationSample/

ローカルでデータ保存に SQLite を使うサンプル。.NET Framework 4.7 と Entity Framework Core (EF Core) 3.1 の組み合わせ。

08_sqlserver-ef6-wpf-datagrid/

Entity Framework 6 (EF6). Microsoft SQL Server に接続する。 TargetFrameworkVersion: v4.7.2

13_AdventureWorks.WPF/

AdventureWorks LT のサンプル。 TargetFrameworkVersion: v4.7.2