DelphiでMVVMを実装したくて、本を読んだり、ライブラリーをGitHubで公開してそうなものを探したりしたけど、「LiveBindingを使えばできるのでは?」と思って探してみた。
参考にしたサイト
自分なりに実装してみたので、手順を残す。
環境
- Delphi 10.2
- libeay32.dll (Web接続用)
- ssleay32.dll (Web接続用)
構成
- URLを指定し、コンテンツ情報を文字列で取得するアプリケーション。
- ViewはTForm、ViewModelはTDateModuleで実装する。
実装
データ用クラス
まず、アクセスしたURL、アクセスしたページを文字列として保持するクラスを作成する。
/// <summary>Web情報クラス。</summary> TWebInfo = class private FURL: string; FContent: string; procedure SetURL(const Value: string); procedure SetContent(const Value: string); public /// <summary>URL。</summary> property URL: string read FURL write SetURL; /// <summary>コンテンツ文字列。</summary> property Content: string read FContent write SetContent; end;
Modelクラス
指定したURLを使って、コンテンツを文字列として返すクラスメソッド(GetContent)を持つクラスを用意する。
/// <summary>データ通信用Model。</summary> TDataCommunicationModel = class public /// <summary>コンテンツを取得する。</summary> /// <param name="URL">URL文字列。</param> /// <returns>コンテンツの文字列を返す。</returns> class function GetContent(URL: string): string; end;
クラスメソッドにして、インスタンス化しないで済むようにしている。 実装はIndyを使ってSSL通信で書いた内容とほぼ同じ。
class function TDataCommunicationModel.GetContent(URL: string): string; var http: TIdHTTP; sslIoHandler: TIdSSLIOHandlerSocketOpenSSL; begin Result := ''; http := TIdHTTP.Create; sslIoHandler := TIdSSLIOHandlerSocketOpenSSL.Create(http); try sslIoHandler.SSLOptions.SSLVersions := [sslvTLSv1_1, sslvTLSv1_2]; http.IOHandler := sslIoHandler; http.HandleRedirects := True; // URLを指定して取得。 Result := http.Get(URL); finally FreeAndNil(http); end; end;
ViewModelクラス
定義
TDataModuleを継承してViewModelを作成する。 バインディングさせるためにTPrototypeBindSourceを持たせる。
TPrototypeBindSourceには、バインドさせるTWebInfoクラスが持っているプロパティ(URL、Content)と同じ名前のフィールドを定義する。
データを保持するためにデータクラス(TWebInfo)の変数をprivate変数として宣言する。
/// <summary>サンプルViewModelクラス。</summary> TMainViewModel = class(TDataModule) BindSource: TPrototypeBindSource; private /// <summary>Web情報。</summary> WebInfo: TWebInfo; public end;
Adapter作成
データ用保持用の変数とTPrototypeBindSourceのフィールドを関連付けるために、TPrototypeBindSource.OnCreateAdapterイベントを実装する。
- ①まず、データ保持用クラスのインスタンスを生成。
- ②TObjectBindSourceAdapterのインスタンスを生成。引数にデータ保持用インスタンスを指定する。
- ③AutoEdit、AutoPostをTrueにして、なるべく自動反映されるようにしておく。
procedure TMainViewModel.BindSourceCreateAdapter(Sender: TObject; var ABindSourceAdapter: TBindSourceAdapter); begin // ①データ保持用インスタンスを生成。 WebInfo := TWebInfo.Create; WebInfo.URL := 'https://www.nakamurakko.jp'; WebInfo.Content := ''; // ②TObjectBindSourceAdapterのインスタンスを生成。 ABindSourceAdapter := TObjectBindSourceAdapter<TWebInfo>.Create(Self, WebInfo, False); // ③AutoEdit、AutoPostをTrueに設定。 ABindSourceAdapter.AutoEdit := True; ABindSourceAdapter.AutoPost := True; end;
これで、TPrototypeBindSourceがデータ保持用クラスと画面のコントロールの橋渡しをしてくれるようになる。
取得処理の実装
View側から呼び出すWeb情報取得処理をViewModelに実装する。
procedure TMainViewModel.GetContent; begin BindSource.Refresh; // Modelのクラスメソッドを使用してコンテンツを文字列で取得。 WebInfo.Content := TDataCommunicationModel.GetContent(WebInfo.URL); BindSource.Refresh; end;
BindSource.Refresh
を呼び出しているのは、次の理由で行っている。
- 最初に呼び出しているのは、Viewの変更状態を反映させるため。
- 最後に呼び出しているのは、Viewに変更状態を通知させるため。
AutoPost、AutoEditをTrueに設定しても反映されない場合は試してみるといいはず。
Viewクラス
定義
TFormを継承してViewクラスを作成して、ViewModelを変数に宣言する。
/// <summary>サンプルフォーム。(Viewクラス)</summary> TMainForm = class(TForm) private ViewModel: TMainViewModel; end;
コンストラクターでViewModelのインスタンスを生成しておく。
constructor TMainForm.Create(AOwner: TComponent); begin inherited; ViewModel := TMainViewModel.Create(Self); end;
デザイナーを開き、URLを入力するTEdit(①)、取得処理を呼び出すTButton(②)、取得したコンテンツ文字列を表示するTMemo(③)を用意する。
ViewModelのメソッドを呼び出す
貼り付けたボタンのクリックイベントでViewModel.GetContentメソッドの呼び出す。
procedure TMainForm.GetContentButtonClick(Sender: TObject); begin ViewModel.GetContent; end;
ViewとViewModelのバインド
LiveBindingデザイナを開いて、URL(①)、コンテンツ(②)をバインドする。
実行してみる。
起動してみるとこんな感じ。 TPrototypeBindSource.OnCreateAdapterイベントで設定したURLが初期表示されている。
ボタンをクリックしてみるとこんな感じで反映される。
- ボタンクリック
- ViewModelのメソッドが呼ばれる
- Modelのメソッドが呼ばれる
- ViewModelのデータ用変数に取得結果を反映
- バインドされたTMemoの表示が変わる
以上、TWebBrowser.Navigateメソッドで出来るWebアクセスを、MVVMライクに出来ないかと実装してみた。
課題
- TPrototypeBindSourceを仲介しない方法があるかどうか。(間に1ステップはさむのが手間)
- クラスのプロパティをバインド出来たけど、深い階層のバインドや複雑なバインドが出来るのか調査が必要。(サンプルソースを見ると、コレクションのバインドは多分出来そう。)
追記
ソースをGitHubにアップしました。https://github.com/nakamurakko/MVVMLikeDelphiSample