ボタンなどの GUIイベントを実行したときに、メインウィンドウのインスタンス(ハンドル)が欲しいことがあります。
本来、MVVM で設計をするとこういうことは無いように設計するのかもしれませんが、現実的に VMから子ウィンドウを(親ウィンドウの)ダイアログとして表示したい、のようなことくらいはサクッと実装したい。あまり原理原則を大切にしすぎても、View はどこまでいっても XAMLなので深く付き合ってもどうなのかな、という気持ちはある。
WindowService の利用
というわけで WindowService
を実装例。ウィンドウのインスタンスを渡すとインターフェース経由でウィンドウ(でやりたいこと)を操作できる。
ウィンドウのインスタンスそのものを渡してしまってもいいんだけど、さすがに MVVM の矜持に悖る気がするので、インターフェースでラップしておこう、という提案が根本。なので Window
インスタンスを返却してしまうのは微妙とも感じる。子ウィンドウを返却するメソッドに置き換えてもいいと思う。
publicinterface IWindowService { Window Window { get; } IntPtr WindowHandle { get; } void Close(); }
publicclassWindowService : IWindowService { privatereadonly Window _window; public Window Window => _window; public IntPtr WindowHandle =>new System.Windows.Interop.WindowInteropHelper(_window).Handle; /// <summary>/// <seecref="WindowService"/>クラスの新しいインスタンスを初期化します。/// </summary>/// <paramname="window">ウィンドウのインスタンス。</param>public WindowService(Window window) { if (window ==null) { thrownew ArgumentNullException(nameof(window)); } _window = window; // WindowHandle のアドレス値は、Window の非表示前だと 0x00 が返却される。// Window 表示後のボタン押下のタイミングなどで取得されることが望ましい } publicvoid Close() => _window.Close(); }
使い方
DI と連携させると、こんな感じになると思う。
protectedoverridevoid OnStartup(StartupEventArgs e) { base.OnStartup(e); var serviceCollection =new ServiceCollection(); var messenger = WeakReferenceMessenger.Default; var v =new MainWindow(); ConfigureServices(serviceCollection, v); var serviceProvider = serviceCollection.BuildServiceProvider(); var vm =new MainWindowViewModel(messenger, serviceProvider); v.DataContext = vm; v.Show(); } privatevoid ConfigureServices(IServiceCollection services, MainWindow v) { // DIコンテナにサービスを登録 services.AddSingleton<IWindowService>(new WindowService(v)); }
... public MainWindowViewModel(IMessenger messenger, IServiceProvider serviceProvider) { _messenger = messenger; _serviceProvider = serviceProvider; } publicvoid ShowChildWindow() { var windowService = _serviceProvider.GetRequiredService<IWindowService>(); var v =new SampleWindow { Owner = windowService.Window, WindowStartupLocation = System.Windows.WindowStartupLocation.CenterOwner, }; var childWindowService =new WindowService(v); var vm =new SampleWindowViewModel(childWindowService); v.DataContext = vm; v.ShowDialog(); }
ポイント
ViewModel で View を弄るのはあまり推奨されたことではない。なので、アクセスできる部分を限定するためのインターフェース。
でも、使いたいシーンがあるときは:
- DI でウィンドウのインスタンスを管理
- 管理しやすく、アクセスしやすい
- 子ウィンドウにも転用、応用しやすい