1つ前の記事:http://d.hatena.ne.jp/okazuki/20091025/1256472760
前回の記事で、無理矢理?独自ロジックとフォーマットを定義したWindowを継承させる方法を書きました。ただ、この方法も出来るっちゃ出来るけど、個人的には好きじゃありません。
それに、恐らく親Windowで定義したButtonのイベントを拾いたいとかあたりで、つまづいたりしそうな予感もします。これは、ApplyTemplateメソッドあたりをオーバーライドして、Buttonのインスタンスを取得して、ごにょごにょすればいけるはずかな・・・?(未検証)
ということで、今回は、上記の方法ではなくMVVMパターンでちょっぴりやってみようと思います。
ViewModelBaseの作成
WpfMVVMBaseという名前でWPFアプリケーションを作成して、そこにViewModelの基本クラスを作ります。こいつはINotifyPropertyChangedを実装しただけのシンプルなものにしました。
using System.ComponentModel; namespace WpfMVVMBase { public class ViewModelBase : INotifyPropertyChanged { #region INotifyPropertyChanged メンバ public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string name) { var h = PropertyChanged; if (h != null) { h(this, new PropertyChangedEventArgs(name)); } } #endregion } }
基本となるViewModelを作成
次に、ViewModelBaseを継承してMyViewModelBaseクラスを作成します。イメージとしては、こいつが親ウィンドウにあたります。
namespace WpfMVVMBase { public class MyViewModelBase : ViewModelBase { private ViewModelBase _contentModel; // 差分 public ViewModelBase ContentModel { get { return _contentModel; } set { _contentModel = value; OnPropertyChanged("ContentModel"); } } } }
こいつに紐づくViewをApp.xamlでDataTemplateを使用して定義します。
<DataTemplate DataType="{x:Type local:MyViewModelBase}"> <DockPanel> <TextBlock DockPanel.Dock="Top" Text="へっだー" /> <TextBlock DockPanel.Dock="Bottom" Text="ふったー" /> <!-- 真ん中に差分を表示する --> <ContentPresenter Content="{Binding ContentModel, Mode=TwoWay}"/> </DockPanel> </DataTemplate>
差分のViewModelを作成
差分のViewModelといってもMyViewModelBaseを拡張するわけではなくて、ViewModelBaseを継承して作ります。
public class Page1ViewModel : ViewModelBase { } public class Page2ViewModel : ViewModelBase { }
Page1ViewModelとPage2ViewModelに対応するViewをDataTemplateでApp.xamlに定義します。今回は、簡単にするためにApp.xamlに全て書いてますが、UserControlにViewを定義して、DataTemplateの中は、UserControlを書くだけにするのがいいと思います。
<DataTemplate DataType="{x:Type local:Page1ViewModel}"> <Button Content="AAAAAA" /> </DataTemplate> <DataTemplate DataType="{x:Type local:Page2ViewModel}"> <Button Content="BBBBBB" /> </DataTemplate>
App.xamlのStartupUriを削除してStartupイベントを定義します。ここで最初にViewModelを組み立ててViewを表示させます。
using System.Windows; namespace WpfMVVMBase { /// <summary> /// App.xaml の相互作用ロジック /// </summary> public partial class App : Application { private void Application_Startup(object sender, StartupEventArgs e) { // viewmodel組み立てて var viewModel = new MyViewModelBase { ContentModel = new Page1ViewModel() }; // windowと紐付けて var window = new Window1 { Content = viewModel }; // 表示 window.Show(); } } }
次に、別画面を表示する場合を考えて見ます。とりあえず、コマンドが必要なので、コマンドを作成します。今回は手抜きです。すいません。
// 超簡易コマンド実装 public class RelayCommand : ICommand { private Action _execute; public RelayCommand(Action execute) { _execute = execute; } #region ICommand メンバ public bool CanExecute(object parameter) { return true; } public event EventHandler CanExecuteChanged; public void Execute(object parameter) { _execute(); } #endregion }
Page1ViewModelに以下のコマンドの定義を追加します。
public class Page1ViewModel : ViewModelBase { private ICommand _showPage2Command; public ICommand ShowPage2Command { get { return _showPage2Command = _showPage2Command ?? new RelayCommand(ShowPage2); } } public void ShowPage2() { // ViewModelでViewを作るのは合法か・・・? // というのは永遠のテーマ // Viewに依存するのが嫌なら、何かしらインターフェースを1枚かまそう var window = new Window1 { // 中身がPage2ViewModelのMyViewModelBaseをContentに Content = new MyViewModelBase { ContentModel = new Page2ViewModel() } }; // 表示 window.Show(); } }
DataTemplateに定義したボタンにCommandをBindします。
<DataTemplate DataType="{x:Type local:Page1ViewModel}"> <Button Content="AAAAAA" Command="{Binding ShowPage2Command, Mode=OneTime}" /> </DataTemplate>
これでAAAAAAボタンをクリックすると、別ウィンドウで以下のようなBBBBBBBと表示された画面が表示されます。
ん〜もうちょっと整理しないといけないかな・・・。
とりあえず30分くらい考えた感じだとこんなの。