Tag: WPF MVVM WPFのためのMVVMデザイン パターン

目次

参考情報

概要

  • 顧客情報を一覧表示するビューと顧客情報を新規追加するビューが存在。どちらもタブで表示。

RelayCommandの役割

  • ViewにViewModelがバインドされ、ButtonなどにViewModelのICommandインターフェイスがバインドされる(「保存」などの機能を実行する)。
  • ICommandからViewModelの内部構造に触りたい。しかしICommandをViewModelのインナークラスにすると複雑になるので、処理をリレーするためにRelayCommandを導入。
  • 例えばCutomerViewModelでは次のように定義されている。
            public ICommand SaveCommand
            {
                get
                {
                    if (_saveCommand == null)
                    {
                        _saveCommand = new RelayCommand(
                            param => this.Save(),
                            param => this.CanSave
                            );
                    }
                    return _saveCommand;
                }
            }
    
  • CustomerView.xamlでは次のようにバインドされている。
        <!-- SAVE BUTTON -->
        <Button 
          Grid.Row="8" Grid.Column="2"
          Command="{Binding Path=SaveCommand}"
          Content="_Save"
          HorizontalAlignment="Right"
          Margin="4,2" 
          MinWidth="60" 
          />
    

ViewModelの構造

  • ViewModelは継承構造となっている。

ViewModelBase

  • ViewModelの階層のルート。
  • INotifyPropertyChangedインターフェイスを実装。ViewModelのプロパティが変更されるたびにPropertyChangedイベントを起動する(結果バインドされたUIが更新される)。
  • サブクラスのために、OnPropertyChanged()メソッドを公開している。サブクラスから文字列ベースで変更されたプロパティ名が指定されるため、実際にそのプロパティが存在するかどうか、VerifyPropertyName()メソッドを使って確認している。
    // In ViewModelBase.cs
    public event PropertyChangedEventHandler PropertyChanged;
    
    protected virtual void OnPropertyChanged(string propertyName)
    {
        this.VerifyPropertyName(propertyName);
    
        PropertyChangedEventHandler handler = this.PropertyChanged;
        if (handler != null)
        {
            var e = new PropertyChangedEventArgs(propertyName);
            handler(this, e);
        }
    }
    
    [Conditional("DEBUG")]
    [DebuggerStepThrough]
    public void VerifyPropertyName(string propertyName)
    {
        // Verify that the property name matches a real,  
        // public, instance property on this object.
        if (TypeDescriptor.GetProperties(this)[propertyName] == null)
        {
            string msg = "Invalid property name: " + propertyName;
    
            if (this.ThrowOnInvalidPropertyName)
                throw new Exception(msg);
            else
                Debug.Fail(msg);
        }
    }
    
  • CustomewViewModelサブクラスからはつぎのように呼び出されている。
            public string Email
            {
                get { return _customer.Email; }
                set
                {
                    if (value == _customer.Email)
                        return;
    
                    _customer.Email = value;
    
                    base.OnPropertyChanged("Email");
                }
            }
    

CommandViewModel

  • メインウィンドウに表示される。"View all customers", "Create new customer"などのリンクに対するViewモデル。
  • コマンドをセットして実行するだけの役割。
  • MainWindowViewModelで次のように生成されている。
            public ReadOnlyCollection<CommandViewModel> Commands
            {
                get
                {
                    if (_commands == null)
                    {
                        List<CommandViewModel> cmds = this.CreateCommands();
                        _commands = new ReadOnlyCollection<CommandViewModel>(cmds);
                    }
                    return _commands;
                }
            }
    
            List<CommandViewModel> CreateCommands()
            {
                return new List<CommandViewModel>
                {
                    new CommandViewModel(
                        Strings.MainWindowViewModel_Command_ViewAllCustomers,
                        new RelayCommand(param => this.ShowAllCustomers())),
    
                    new CommandViewModel(
                        Strings.MainWindowViewModel_Command_CreateNewCustomer,
                        new RelayCommand(param => this.CreateNewCustomer()))
                };
            }
    
  • MainWindowResource.xamlでDateTemplateとして定義され、MainWindow.xamlに埋め込まれている。
      <DataTemplate x:Key="CommandsTemplate">
        <ItemsControl IsTabStop="False" ItemsSource="{Binding}" Margin="6,2">
          <ItemsControl.ItemTemplate>
            <DataTemplate>
              <TextBlock Margin="2,6">
                <Hyperlink Command="{Binding Path=Command}">
                  <TextBlock Text="{Binding Path=DisplayName}" />
                </Hyperlink>
              </TextBlock>
            </DataTemplate>
          </ItemsControl.ItemTemplate>
        </ItemsControl>
      </DataTemplate>
    

WorkspaceViewModel

  • ViewModelBaseを直接継承し、「閉じる」機能を実装。

MainWindowViewModel

  • メニューから呼び出される「閉じる」機能は、AppクラスのOnStartupで設定される。
                EventHandler handler = null;
                handler = delegate
                {
                    viewModel.RequestClose -= handler;
                    window.Close();
                };
                viewModel.RequestClose += handler;
    
  • 顧客一覧ビュー、顧客登録ビューの各タブにある×ボタンがおされたときビューを非表示にする処理を設定するのも、MainWindowViewModelの役割。
    • ObservableCollection<WorkspaceViewModel> _workspacesでタブごとのViewModelを管理。
    • タブ生成時にViewModelに、CustomerViewModel、AllCustomersViewModelのインスタンスを追加。
    • OnWorkspacesChanged()により、生成されたViewModelのRequestCloseにOnWorkspaceRequestClose()を設定。
    • これにより、×が押されたときに、MainWindowViewModelのOnWorkspaceRequestCloseが呼び出され結果としてビューが非表示になる。

ちなみに、タブ生成時は次のコードでタブの順番を制御している。TabControlを使っているのでバインドされているWorkspacesが変更されるだけで自動的にタブは増える。

        void SetActiveWorkspace(WorkspaceViewModel workspace)
        {
            Debug.Assert(this.Workspaces.Contains(workspace));

            ICollectionView collectionView = CollectionViewSource.GetDefaultView(this.Workspaces);
            if (collectionView != null)
                collectionView.MoveCurrentTo(workspace);
        }

Bea Stollnitz » WPF’s CollectionViewSource

データモデルとリポジトリ

  • 全てのCustomerはCustomerRepositoryで管理される。
  • CustomerViewModelでSave()が実行されるとCustomerRepositoryに新しいCustomerが追加される。

新しい顧客のデータ入力フォーム

  • CustomerView.xamlが定義。
  • Customerクラス・CustomerViewModelクラスの検証メソッドにより検証される。
  • Customer Typeリストボックスの初期値は"(Not Specified)"。CustomerのIsCompanyプロパティはboolがたなので困る(true or falseしかとれない)。→ViewModelを使う。
  • CustomerViewModelでは次のようにリストボックスの選択肢と、CustomerTypeのアクセサを定義。
    public string[] CustomerTypeOptions
    {
        get
        {
            if (_customerTypeOptions == null)
            {
                _customerTypeOptions = new string[]
                {
                    "(Not Specified)",
                    "Person",
                    "Company"
                };
            }
            return _customerTypeOptions;
        }
    }
    public string CustomerType
    {
        get { return _customerType; }
        set
        {
            if (value == _customerType || 
                String.IsNullOrEmpty(value))
                return;
    
            _customerType = value;
    
            if (_customerType == "Company")
            {
                _customer.IsCompany = true;
            }
            else if (_customerType == "Person")
            {
                _customer.IsCompany = false;
            }
    
            base.OnPropertyChanged("CustomerType");
            base.OnPropertyChanged("LastName");
        }
    }
    
  • 保存時はCustomerクラスのIsValid()により検証している。

顧客一覧

  • AllCustomersView.xamlで定義。
  • CustomerViewModelの一覧が、AllCustomersとして公開されている
           public ObservableCollection<CustomerViewModel> AllCustomers { get; private set; }
    
  • CustomerRepositoryを監視し、Customerが追加されたタイミングで自分の管理するAllCustomersに新しいCustomerViewModelを追加する。
           void OnCustomerAddedToRepository(object sender, CustomerAddedEventArgs e)
            {
                var viewModel = new CustomerViewModel(e.NewCustomer, _customerRepository);
                this.AllCustomers.Add(viewModel);
            }
    
  • 顧客が選択されたり選択解除されたりしたときのために、CustomerViewModelのPropertyChangedに、OnCustomerViewModelPropertyChangedがセットされている。
  • CustomerViewModelのIsSelectedが変更された結果OnCustomerViewModelPropertyChangedが呼ばれ、そのプロパティ名が"IsSelected"なら、TotalSelectedSalesが変更されたことをUIに通達する。
            void OnCustomerAddedToRepository(object sender, CustomerAddedEventArgs e)
            {
                var viewModel = new CustomerViewModel(e.NewCustomer, _customerRepository);
                this.AllCustomers.Add(viewModel);
            }
    
    

ポイント


トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2016-04-13 (水) 16:47:25