久々に使うことになりそうなので、再入門しています。
.NET Framework 3.0 で Avalon のコードネームで搭載され、3.5 で WPF として登場した GUI フレームワーク。当時のプロジェクトで運用系ツールの画面で使いましたが、多くのプロジェクトでは Windows Forms が使われ続け早10年。ただ Silverlight, Windows Mobile, UWP, Xamarin ... と XAML 系 GUI プラットフォームが数年置きに登場する*1ため断続的に触っており、プログラミングモデル自体には結構なじみがあります。
XAML 編集用に Blend という別 IDE が提供されていますが、Visual Studio 本体でも地味に GUI デザイナーが進化しています。
リハビリがてら、RSS Reader 的なサンプルを作っているところです。
参考記事:[C# / WPF] 最新のC# 6.0でMVVMパターンを実装する
WPF 登場時はプロパティベースのデータバインディングが新鮮でした*2が、プロパティの更新通知のためにプロパティ名をハードコーディングしなくてはならず、リファクタリングでイベントが発火しなくなる恐れがあるのが残念でした。
private string hoge; public string Hoge { get { return this.hoge; } set { this.hoge = value; OnPropertyChanged("Hoge"); } }
C# 6 で nameof
演算子が導入されプロパティ名のリテラルが取れるようになったので、リファクタリングも大丈夫になりました。
private string hoge; public string Hoge { get { return this.hoge; } set { this.hoge = value; OnPropertyChanged( nameof(Hoge) ); } }
実は Behavior という Blend 由来の仕組みを使えば更新通知も簡単に実装できるようですが、別途 Blend SDK が必要となります*3。今回はとりあえず Behavior は使っていません。
- イベントハンドラではなく、Command バインディングで書く
- リモート API の非同期呼び出し - 呼び出し中に画面を固まらせない
- 呼び出し中の Command 呼び出し無効化 - 呼び出し中に更新ボタンを押せなくする
あたりを意識して書いてみました。
Command バインディング (正式な呼び方ではないかもしれません) は、画面のボタンに対応する Command を実装して XAML にバインドする書き方です。
<Button Content="Update" Command="{Binding FetchCommand}" />
Command の実装はこんな感じ。
class FetchRSSCommand : ICommand { // 中略 public bool CanExecute(object parameter) { return !Fetching; } public async void Execute(object parameter) { Fetching = true; var contents = await Task.Run(() => FetchRssAsync()); foreach (var content in contents) { _vm.Items.Add(content); } Fetching = false; } private Task<List<RSSViewModel.RSSContent>> FetchRssAsync() { using (var reader = XmlReader.Create(_vm.Url)) { var feed = SyndicationFeed.Load(reader); // 中略 var result = (from f in feed.Items select new RSSViewModel.RSSContent() { Title = f.Title.Text, Summary = f.Summary.Text, PubDate = f.PublishDate.DateTime, Link = f.Id }).ToList(); return Task.FromResult(result); } } }
XmlReader には非同期用メソッドがないみたいなので、取得処理を Task にラップして、async / await で呼んでます。
Execute の前後でFetching
というプロパティを変化させて強制的に Button の Disable / Enable を切り替えています。
Fetching プロパティの実装は次のようになっています。
private bool _fetching = false; public bool Fetching { get { return _fetching; } set { _fetching = value; RaiseCanExecuteChanged(); } } public void RaiseCanExecuteChanged() { CommandManager.InvalidateRequerySuggested(); }
RSS 取得中かどうかを保持する bool 値 _fetching
の setter で CanExecute 状態変化を通知するためのメソッドを呼び出しています。
昨今の JS の Single Page Application のような、非同期に Web API を叩いて結果を画面表示するアプリはサクッと書きたいのですが、なかなか定石が分からず迷いますね。.NET Framework や C# はがんがんバージョンアップしているので、ドキュメントもリニューアルされると嬉しいのですが、公式の WPF 関連情報は滞っている印象です
WPF のサンプルは msdn に取り残されてて、CodePlex へのリンクは切れている・・
— MH (@kondoumh) 2018年5月22日
今は UWP や Xamarin 推しなのでしょうけどエンプラユーザーのことも時々ケアが必要。