ViewMakerで作成するリアルアプリケーション 主要機能の開発(その2)
今回は画面全体に対応するViewModelを定義します。検索用文字列、データ表示用のコレクション、詳細表示、および各種コマンド(開く・クリア・検索)のプロパティを定義します。画面の構成要素としての入出力項目およびユーザ機能としてのコマンドをプロパティとして定義しているだけで、発見的なテクにニックも必要なく要求からストレートにコードに落とし込めます。
public class ReflectionViewerViewModel : ViewModel { //検索文字列 public string FindString { get; set; } //ツリービュー public ObservableCollection<TreeItem> Children { get; private set; } //詳細表示 public object CurrentContent { get { return _currentContent; } set { _currentContent = value; OnPropertyChanged("CurrentContent"); } } public object _currentContent; //開くコマンド public ICommand Load { get { return this.CreateCommand(LoadAction); } } //クリアコマンド public ICommand Claer { get { return CreateCommand(() => Children.Clear()); } } //検索コマンド public ICommand Find { get { return this.CreateCommand(FindAction); } }
上記でCurrentContentがobject型で少し特殊なものになっています。CurrentContentは詳細表示用で、表示するデータによってレイアウトを変えます。これを実現するために表示データからViewを生成してそれをContentControlコントロールを利用して表示させるようにしています(下記の最終コードのGenerateContentメソッドで生成しています)。ViewModelで画面を生成するのは少し気持ち悪いと感じる方もいるかもしれませんが、動的なコンテンツをViewModel表現する場合に非常に便利です。もちろん、テクノロジ依存していないし詳細なレイアウトなどにも依存していないのでViewModelの責務を超えていません。ViewMakerを使うと簡単にコンテンツ生成できるのでこのようなシナリオも簡単に実装できます。
このままでもレイアウトエディターで一から構造を作成することも可能ですが、上記のViewModelのコードに自動生成のヒントをいくつか足しておきます。構造上固定的なものは予め属性に指定するほうが直ぐに動かしてテストできお勧めです。ViewModelの最終的なコードは以下のようになります。
[View(ViewControlType.Grid, Title = "リフレクションビューア")] public class ReflectionViewerViewModel : ViewModel { [Display(Name = "検索文字列")] [View(ViewControlType.TextBox)] public string FindString { get; set; } [View(ViewControlType.TreeView)] [ViewProperty(TreeViewControl.Properties.DisplayMember, "Name")] [ViewProperty(TreeViewControl.Properties.ItemsSource, "Children")] [ViewProperty(TreeViewControl.Properties.IsExpanded, "IsExpanded")] [ViewProperty(TreeViewControl.Properties.IsSelected, "IsSelected")] [ViewProperty(TreeViewControl.Properties.SelectedItemChangedCommand, "SelectedItemChangedCommand")] public ObservableCollection<TreeItem> Children { get; private set; } private TreeItem _root; [Display(Name = "詳細情報")] [View(ViewControlType.Literal)] [ViewProperty(LiteralViewControl.Properties.Literal, @"<ContentControl Content=""{Binding Path=CurrentContent, Mode=OneWay}"" />")] public object CurrentContent { get { return _currentContent; } set { _currentContent = value; OnPropertyChanged("CurrentContent"); } } public object _currentContent; private TreeItem _currentItem; public ReflectionViewerViewModel() { _root = new LiteralTreeItem(null, "Root"); Children = _root.Children; } [Display(Name = "開く")] public ICommand Load { get { return this.CreateCommand(LoadAction); } } private void LoadAction() { foreach (var fileName in this.OpenFileDialog("(*.dll)|*.dll")) { var asm = new AssemblyTreeItem(_root, Assembly.LoadFile(fileName)); var old = Children.FirstOrDefault(x => x.Name == asm.Name); if (old != null) Children.Remove(old); Children.Add(asm); } } [Display(Name = "クリア")] public ICommand Claer { get { return CreateCommand(() => Children.Clear()); } } [Display(Name = "検索")] public ICommand Find { get { return this.CreateCommand(FindAction); } } private void FindAction() { if (string.IsNullOrEmpty(FindString)) return; var startNode = _currentItem ?? (Children == null || Children.Count > 0 ? Children[0] : null); if (startNode == null) return; if (startNode.FindNode(FindString) == null) this.ShowMessage("「" + FindString + "」は見つかりません"); } [Browsable(false)] public ICommand SelectedItemChangedCommand { get { return this.CreateCommand(GenerateContent); } } private void GenerateContent(object arg) { var content = arg as TreeItem; this._currentItem = content; this.CurrentContent = ViewUtil.BuildContent(content); } }
この画面をViewMakerで表示させると以下のような画面が表示され、大きくして実際にファイルを読み込むとツリービューにデータが表示されます。