MVVM パターンの ViewModel を C++/CLI で作ってみた

現在 MVVM パターンを勉強中なんですが、View と ViewModel とModel と三つのレイヤーに明確に分離できることを見て、だったら各レイヤーごとにプロジェクトも完全分離して、ついでに各レイヤーごとに言語も分けて作ってみようと思いました。ついでに C++/CLI の勉強も兼ねて、ViewModel を C++/CLI で作ってみました。


まずmvvm というソリューションを用意し、プロジェクトを三つ用意します。

Model プロジェクトを VB のクラスライブラリ、ViewModel プロジェクトを C++/CLI のクラスライブラリ、View プロジェクトを C#WPF アプリケーションで作成します。

Model

Model プロジェクトは、以下のように Person というクラスを定義するだけです。VB2010 だと自動プロパティ使えるので便利です。

Option Explicit On
Option Strict On

Public Class Person
	Public Property Id As Integer
	Public Property Name As String
	Public Property Address As String
End Class

ViewModel

ViewModel プロジェクトですが、ViewModel.h を編集してます。こちらは INotifyPropertyChanged を C++/CLI でどう実装するのか、CLI 初心者に加え、INotifyPropertyChanged インターフェイスCLI で実装した資料が少ないため少し苦労しました。
ちなみに Person プロパティのセッター内で使ってる __FUNCSIG__ ですが、コメントにあるとおり id:ladybug さんの有難い指摘により、記事投稿後に追加してます。C++ にはこんな便利なものがあるんですね。はじめて知りました。


#2010/04/15 追記
コメ欄にあるとおり、後から教えられて判りました。コードも修正してあります。
__FUNCSIG__ も __FUNCTION__ もこのままだと set メソッド名まで返してしまう。加工して使わないと駄目。><


あとプロジェクトの参照に WindowBase アセンブリを追加してます。参考に試される方はお忘れなく。


// ViewModel.h

#pragma once

using namespace System;
using namespace System::ComponentModel;
using namespace System::Windows::Input;
using namespace Model;


namespace ViewModel {

	public ref class PersonViewModel : INotifyPropertyChanged
	{
	private:
		Model::Person^ _person;

	public:
		virtual event PropertyChangedEventHandler^ PropertyChanged;
		void OnPropertyChanged(String^ propertyName) {
			PropertyChanged(this, gcnew PropertyChangedEventArgs(propertyName));
		}

		PersonViewModel() {
			_person = gcnew Model::Person();
			_person->Id = 10;
			_person->Name = "ひらぽん";
			_person->Address = "東京都";
		}

		property Model::Person^ Person {
			Model::Person^ get() {
				return _person; 
			}
			void set(Model::Person^ value) {
				_person = value; 

				// __FUNCTION__ は ViewModel::PersonViewModel::Person::set を返すので加工する
				String^ funcname = __FUNCTION__;
				funcname = funcname->Replace("::set", "");
				funcname = funcname->Substring(funcname->LastIndexOf(":")+1);
				this->OnPropertyChanged(funcname);
			}
		}
	};
}

View

最後は View です。以下のように MainWindow.xaml を編集します。


<Window x:Class="View.MainWindow"
		xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
		xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
		xmlns:vm="clr-namespace:ViewModel;assembly=ViewModel"
		Title="MainWindow" Height="151" Width="427">
	<Window.DataContext>
		<vm:PersonViewModel />
	</Window.DataContext>
	<Grid>
		<Grid.RowDefinitions>
			<RowDefinition Height="8" />
			<RowDefinition Height="23" />
			<RowDefinition Height="4" />
			<RowDefinition Height="23" />
			<RowDefinition Height="4" />
			<RowDefinition Height="23" />
			<RowDefinition />
		</Grid.RowDefinitions>
		<Grid.ColumnDefinitions>
			<ColumnDefinition Width="100" />
			<ColumnDefinition Width="200" />
			<ColumnDefinition />
		</Grid.ColumnDefinitions>
		<TextBlock Grid.Row="1" Text="ID" TextAlignment="Right" 
				   VerticalAlignment="Center" Padding="0,0,8,0" />
		<TextBlock Grid.Row="3" Text="名前" TextAlignment="Right" 
				   VerticalAlignment="Center" Padding="0,0,8,0" />
		<TextBlock Grid.Row="5" Text="住所" TextAlignment="Right" 
				   VerticalAlignment="Center" Padding="0,0,8,0" />
		<TextBlock Grid.Column="1" Grid.Row="1" 
				   VerticalAlignment="Center" Padding="6" 
				   Text="{Binding Person.Id}" />
		<TextBox Grid.Column="1" Grid.Row="3" VerticalContentAlignment="Center" 
				 Text="{Binding Person.Name}" />
		<TextBox Grid.Column="1" Grid.Row="5" VerticalContentAlignment="Center" 
				 Text="{Binding Person.Address}" />
		<Button Content="表示" Grid.Column="2" Grid.Row="1" Height="22" 
				HorizontalAlignment="Left" Margin="25,0,0,0" 
				Name="button1" VerticalAlignment="Top" Width="66" Click="button1_Click" />
	</Grid>
</Window>

実行すると、ViewModel のコンストラクタで設定された値が View に反映されているのが判ります。


View で編集した内容が Model に反映されるかテストするために、Button にイベントハンドラを設けます。ここをコマンドでできればかっこいいのですが、ちょっと Command 実装している時間がないのでまた後日。

using System.Windows;
using ViewModel;

namespace View {
	/// <summary>
	/// MainWindow.xaml の相互作用ロジック
	/// </summary>
	public partial class MainWindow : Window {
		public MainWindow() {
			InitializeComponent();
		}

		private void button1_Click(object sender, RoutedEventArgs e) {
			var vm = (PersonViewModel)this.DataContext;
			MessageBox.Show(
				string.Format(
						"Id : {0}\n名前 : {1}\n住所 : {2}", 
						vm.Person.Id,
						vm.Person.Name,
						vm.Person.Address)
				);
		}
	}
}


Button をクリックすると、View の編集内容が Model に反映されてるのが判ります。


やってて思ったんですが、今回は Model を VB で実装しましたが、Model をネイティブ C++ で実装し、ViewModel を CLI で実装するのも使えそうだなと思いました。