ViewModelファーストな開発(その3)

前回は簡単な一覧、詳細画面をViewModelをからView(XAML)を生成して作成しました。同じようなことはデータベースから画面を自動生成するようツールでもできます。これらのツールを使ってわかるのですが、簡単な範囲では良いのですが少し複雑なシナリオになると出来ないか、全く別の仕組みを利用しないといけません。一方、WPFやSiliverlightを利用したMVVMでは、より複雑な画面を作成することができるのですが、Visaul Studioなどを利用してもXAMLの記述がだんだん負担になってきます。
ViewMakerではVisaul Studioよりも簡単に画面が作成でき、データベースから自動生成するよりも柔軟にできることを目指して、データベースのテーブルのかわりにViewModelを利用して画面を生成することでこの課題に対応しようとしています。今回は前回作成した検索画面をより使いやすくするシナリオでこの柔軟性を説明したいと思います。

1.検索条件を指定した検索処理

まずは、前回作成したクラスを継承して検索条件を指定して検索できるクラスを作成します。ViewModelはPlainなクラスでできているため非常にシンプルです。

    public class AuthorsQueryViewModel : ListViewModel<authors>
    {
        public string State { get; set; }

        public AuthorsQueryViewModel():base(null)
        {
        }

        public ICommand QueryCommand { get { return CreateCommand(Query); } }
        private void Query()
        {
            using (var db = new pubsEntities())
            {
                Data = db.authors.Where(x => x.state == State).ToList();
                OnPropertyChanged("Data");
            }
        }
    }

あわせて、アプリケーションの起動する処理も書き換えて置きます。

    public partial class App : Application
    {
        private void Application_Startup(object sender, StartupEventArgs e)
        {
            using (var db = new pubsEntities())
            {
                ViewUtil.Show(new AuthorsQueryViewModel());
            }

        }
    }

一旦ここで実行すると以下のような画面で表示されます。明細は前回作成したものがそのまま利用できます。

さらにレイアウト編集機能で調整して画面を作りこみます。検索条件を指定して実行する画面が出来上がります。

2.ComboBoxの利用と閉じる処理の追加

次に検索条件をComboBoxで指定できるようにして、閉じるボタンをつけるようにします。ViewModelに対してComboBoxで表示する一覧用のプロパティと閉じる処理のコマンドを追加します。閉じるボタンのCreateViewCommandの実装は単なる.NETのイベント発生の仕組みであってViewModel上は特別な仕組みにはなっていません。自動生成するView側でこのイベントを受け取って閉じる処理をしています。

    public class AuthorsQueryViewModel : ListViewModel<authors>
    {
        public string State { get; set; }

        public List<string> StateList { get; private set; }

        public AuthorsQueryViewModel():base(null)
        {
            using (var db = new pubsEntities())
            {
                StateList = db.authors.Select(x => x.state).Distinct().ToList();
            }            
        }

        public ICommand QueryCommand { get { return CreateCommand(Query); } }
        public ICommand CloseCommand { get { return CreateViewCommand(ViewCommandId.Close); } }

        private void Query()
        {
            using (var db = new pubsEntities())
            {
                Data = db.authors.Where(x => x.state == State).ToList();
                OnPropertyChanged("Data");
            }
        }
    }

レイアウト編集機能を利用して検索条件のTextBoxのControl TypeをComboBoxに変更してItemsSourceに一覧からStateListを選択すれば、州をComboBoxから選択できるようになります。


いい感じになってきました。ただDataGridが追加や編集可能になっているのが気になりますのでこれを解決します。編集画面でDataGridを選択して「Additional Properties」のボタンを選択するとダイアログが表示されます。IsReadOnlyをTrueに設定するようにします。


あとは、閉じるボタンの追加ですが、ボタンを配置するパネルに対してAdd Itemを選択すると配置可能なアイテムが表示されますので、該当のCloseCommandを選択してタイトル(Caption)を設定します。


一旦これで完成で、以下のような画面を作成することができました。

まとめ

ViewMakerではViewModelをベースにすることでデータベースから自動生成するよりも複雑な画面を簡単に作成することができるようにしています。また、ViewMakerを利用したViewの作成は単純な一方通行的なViewの生成ではなくViewModelの発展に合わせて拡張できる仕組みを用意していることが今回のシナリオでわかります。

追加

今回の実装ではComboBoxで選択しない状態で検索してもエラーが現在通知されません。これをコードを少し変えると通知することも可能になるので補足します。
改訂する箇所は、StateプロパティとQueryメソッドです。前者はSystem.ComponentModel.DataAnnotationsにある検証属性が利用できる仕組みが用意されているので必須用のRequired属性をつけてViewModelで用意されているValidatePropertyを組み込みます。後者はViewModelのValidateメソッドでViewに入力検証を依頼します。入力検証制御自体はView側の責務になっています。

    public class AuthorsQueryViewModel : ListViewModel<authors>
    {
        [Required]
        public string State
        {
            get { return _state; }
            set { ValidateProperty("State", value); _state = value; OnPropertyChanged("State"); }
        }
        public string _state;

        public List<string> StateList { get; private set; }

        public AuthorsQueryViewModel():base(null)
        {
            using (var db = new pubsEntities())
            {
                StateList = db.authors.Select(x => x.state).Distinct().ToList();
            }            
        }

        public ICommand QueryCommand { get { return CreateCommand(Query); } }

        public ICommand CloseCommand { get { return CreateViewCommand(ViewCommandId.Close); } }

        private void Query()
        {
            if (!Validate()) return;
            using (var db = new pubsEntities())
            {
                Data = db.authors.Include("titleauthor").Where(x => x.state == State).ToList();
                OnPropertyChanged("Data");
            }
        }
    }

ついでに検索結果の関連データを取得するようにIncludeメソッドを追加して、DataGridに追加表示できるようにしてみました。DBレベルのジョインではない方法で関連データを複合的なパスを指定することで表示しています。複合的なパスによって複雑な構造を持っているデータもうまく扱うことできます。