WPF に再入門中

久々に使うことになりそうなので、再入門しています。

.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 的なサンプルを作っているところです。

github.com

参考記事:[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 関連情報は滞っている印象です

今は UWP や Xamarin 推しなのでしょうけどエンプラユーザーのことも時々ケアが必要。

*1:そして衰退していく・・あ、Xamarin は現役ですね。

*2:昨今の JS フレームワークでは普通ですね。

*3:標準に取り込んで欲しいものです。