sh1’s diary

プログラミング、読んだ本、資格試験、ゲームとか私を記録するところ

WPF メインウィンドウ、子ウィンドウのインスタンス取得

ボタンなどの GUI イベントを実行したときに、メインウィンドウのインスタンス(ハンドル)が欲しいことがあります。

本来、MVVM で設計をするとこういうことは無いように設計するのかもしれませんが、現実的に VM から子ウィンドウを(親ウィンドウの)ダイアログとして表示したい、のようなことくらいはサクッと実装したい。あまり原理原則を大切にしすぎても、View はどこまでいっても XAML なので深く付き合ってもどうなのかな、という気持ちはある。

WindowService の利用

というわけで WindowService を実装例。ウィンドウのインスタンスを渡すとインターフェース経由でウィンドウ(でやりたいこと)を操作できる。

ウィンドウのインスタンスそのものを渡してしまってもいいんだけど、さすがに MVVM の矜持に悖る気がするので、インターフェースでラップしておこう、という提案が根本。なので Window インスタンスを返却してしまうのは微妙とも感じる。子ウィンドウを返却するメソッドに置き換えてもいいと思う。

public interface IWindowService
{
    Window Window { get; }
    IntPtr WindowHandle { get; }

    void Close();
}
public class WindowService : IWindowService
{
    private readonly Window _window;

    public Window Window => _window;
    public IntPtr WindowHandle => new System.Windows.Interop.WindowInteropHelper(_window).Handle;

    /// <summary>
    /// <see cref="WindowService"/> クラスの新しいインスタンスを初期化します。
    /// </summary>
    /// <param name="window">ウィンドウのインスタンス。</param>
    public WindowService(Window window)
    {
        if (window == null)
        {
            throw new ArgumentNullException(nameof(window));
        }

        _window = window;

        // WindowHandle のアドレス値は、Window の非表示前だと 0x00 が返却される。
        // Window 表示後のボタン押下のタイミングなどで取得されることが望ましい
    }

    public void Close() => _window.Close();
}

使い方

DI と連携させると、こんな感じになると思う。

protected override void 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();
}

    private void ConfigureServices(IServiceCollection services, MainWindow v)
{
    // DIコンテナにサービスを登録
    services.AddSingleton<IWindowService>(new WindowService(v));
}
...

public MainWindowViewModel(IMessenger messenger, IServiceProvider serviceProvider)
{
    _messenger = messenger;
    _serviceProvider = serviceProvider;
}

public void 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 でウィンドウのインスタンスを管理
  • 管理しやすく、アクセスしやすい
  • 子ウィンドウにも転用、応用しやすい

参考