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

昨日の記事では、コマンドを定義してなかったのですが、本日は C++/CLI でコマンドにチャレンジしてみました。
まず RelayCommand クラスを C++/CLI で書いてみます。するとこうなる・・・

// RelayCommand.h

#pragma once

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

namespace ViewModel {

	public ref class RelayCommand : ICommand {

	private:
		Func<Object^, bool>^ _canExecuteAction;
		Action<Object^>^ _executeAction;

	public:
		virtual event EventHandler^ CanExecuteChanged;

		RelayCommand(Action<Object^>^ executeAction, Func<Object^, bool>^ canExecuteAction)
		{
			_executeAction = executeAction;
			_canExecuteAction = canExecuteAction;
		}

		virtual bool CanExecute(Object^ parameter) {
			return _canExecuteAction(parameter);
		}

		virtual void Execute(Object^ parameter) {
			_executeAction(parameter);
		}

		void RaiseCanExecuteChanged() {
			this->CanExecuteChanged(this, EventArgs::Empty);
		}
	};
}


んで、お次は PersonViewModel のクラスを改修。以下のようにコーディングしました。

// ViewModel.h

#pragma once

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

#include "RelayCommand.h"

namespace ViewModel {

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

	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 = "東京都";

			Action< Object^ >^ action = gcnew Action< Object^ >(PersonViewModel::OK);
			Func< Object^, bool >^ func = gcnew Func< Object^, bool >(PersonViewModel::Method);
			_command = gcnew RelayCommand( action, func );
		}

		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);
			}
		}

		property RelayCommand^ OnOK {
			RelayCommand^ get() { 
				return _command;
			}
		}

		static void OK(Object^ parameter) {
			MessageBox::Show(
				String::Format("Id : {0}\n名前 : {1}\n住所 : {2}", 
						_person->Id,
						_person->Name,
						_person->Address)
				);
		}

		static bool Method(Object^ parameter) { 
			PersonViewModel::OK( true );
			return true;
		}
	};
}


CPP/CLI はまだ不勉強なところがあるため、非常に苦しい実装になってしまいました・・・(大汗)


まずコンストラクタ内で RelayCommand の初期化をしてるわけなんですが、

Action< Object^ >^ action = gcnew Action< Object^ >(PersonViewModel::OK); 

・・・C++/CLI だと Action にはスタティック関数しか渡せないのか? OK メソッドを static でなくすと途端に怒涛のようにコンパイラがエラーを吐き出します。
また、C++/CLIラムダ式をサポートしてない・・・正確に言えば VS2010 以降サポートするようになったものの、ネイティブC++ラムダ式はサポートしても、マネージドのラムダ式はサポートしてないため、C# みたいに

this.OnOK = new RelayCommand(OK, parameter => true);

とか、VB みたく

Me.OnOK = New RelayCommand(AddressOf OK, Function(parameter) True)

みたいにパラメータ内でラムダ式が使えません。で、苦し紛れに

static bool Method(Object^ parameter) { 
	PersonViewModel::OK( true );
	return true;
}

こんなメソッド作って、Func< Object^, bool > に渡しているわけですが、するとスタティック関数内ではメンバのインスタンス呼べないから、必然的にメンバもスタティックにせざるを得ません。

static Model::Person^ _person;

XAML は以下のように Button に Commmand をバインドします。これに伴い View からイベントハンドラをばっさり削除します。

<Button Content="表示" Grid.Column="2" Grid.Row="1" 
		HorizontalAlignment="Left" Margin="25,0,0,0"
		Width="66" Command="{Binding OnOK}" />
using System.Windows;
using ViewModel;

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


これを実行してみると、なぜかWindow の初期化時にコマンドが実行されてしまう・・・(-ω-;
さらにボタンをクリックすると、コマンドが重複して実行されるのか、二回ダイアログが表示されてしまう・・・(-ω-;
まだまだ C++/CLI の仕様は判ってないので、今後も調査が必要なようです。

#もっとも実戦で ViewModel を C++/CLI で作るケースは当面考えられませんが・・・


#2011/04/15 前日の記事の修正に合わせて ViewModel.h のコードを修正した。