【WPF】ViewModelLocatorって何?

こんにちは!
今日は ServiceProvider を使った ViewModelLocator パターンの実装例を、WPF アプリの最小構成でサクッと紹介します。
「まずは動くサンプルを見たい!」という方向けに、① App 起動時に DI コンテナを組み立てる → ② ViewModelLocator で公開 → ③ View でバインド の 3 ステップでまとめました。コピペで動くレベルにしてあるので、ぜひ手元のプロジェクトで試してみてください。


0. 動作確認環境

  • Visual Studio 2022
  • .NET9
  • WPF

ソースコードはこちらに置いておきます。


1. App.xaml / App.xaml.cs ― ServiceProvider を用意する

<Application x:Class="blog_WPF_ViewModelLocator.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:blog_WPF_ViewModelLocator"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <vm:ViewModelLocator x:Key="Locator" xmlns:vm="clr-namespace:blog_WPF_ViewModelLocator" />
    </Application.Resources>
</Application>
using Microsoft.Extensions.DependencyInjection;
using System.Windows;

namespace blog_WPF_ViewModelLocator
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        public static IServiceProvider Services { get; private set; } = null!;

        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            var services = new ServiceCollection();

            // ===== 依存登録はここにまとめる =====
            services.AddTransient<MainViewModel>();
            // ===================================

            Services = services.BuildServiceProvider();

        }
    }
}

ポイントは App.Services に完成形の ServiceProvider を格納 しておくことです。これだけで「どのクラスからも DI を経由してインスタンス取得」ができるようになります。


2. ViewModelLocator.cs ― DI への“窓口”を作る

using Microsoft.Extensions.DependencyInjection;

namespace blog_WPF_ViewModelLocator;

/// <summary>
/// XAML から ViewModel を解決するための案内所
/// XAML では StaticResource で呼び出すだけ
/// </summary>
public class ViewModelLocator
{
    // MainWindow 用
    public MainViewModel MainViewModel =>
        App.Services.GetRequiredService<MainViewModel>();

    // 別の画面が増えたら同じ書式で追加する
    // public SettingsViewModel SettingsViewModel =>
    //     App.Services.GetRequiredService<SettingsViewModel>();
}

App.Services.GetRequiredService<T>() を使ってDIコンテナからViewModelを取ってきています。余計なロジックは一切不要で、継続的な保守がラクになります。


MainWindow.xaml ― 実際の View での使い方

<Window x:Class="blog_WPF_ViewModelLocator.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:blog_WPF_ViewModelLocator"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800"
        DataContext="{Binding MainViewModel, Source={StaticResource Locator}}">
    <Grid>
        <TextBlock Text="{Binding Title}" FontSize="24" />
    </Grid>
</Window>

上記 1 行👇が ViewModel を“注入”している決め手 です。

DataContext="{Binding MainViewModel, Source={StaticResource Locator}}"

StaticResource LocatorViewModelLocator インスタンス を取得

Path=MainViewModelDI コンテナ経由の MainViewModel を解決

結果として DataContext にセット——これだけで MVVM が成立します


MainViewModel.cs ― 一応ViewModelも貼っておきます

namespace blog_WPF_ViewModelLocator;

public class MainViewModel
{
    public string Title { get; set; } = "From ViewModel";
}

上記のコードで”From ViewModel”と表示されれば成功です!

4. ちょっと深掘り Q&A

疑問回答
デザインタイムで値が見えない…Blend でデザイナー表示するときは DI が未初期化なので null になります。ViewModelLocator 内で DesignerProperties.IsInDesignTool を見てモック ViewModel を返すか、d:DataContext でデザイン専用 VM を指定するとラクです。
スコープ (Singleton/Transient) の使い分けは?ViewModel は基本 Transient で都度生成、リポジトリや API クライアントは Singleton にするのが典型です。AddScoped は ASP.NET Core 向けの “HTTP1 リクエスト単位” なので WPF ではあまり出番ナシ。
Hot Reload 時に更新が反映されないHot Reload は既存オブジェクトを再利用するため、登録情報を変更したらアプリ再起動が安全です。

5. まとめ

  1. App 起動時に ServiceCollection→ServiceProvider を組む
  2. ViewModelLocator では App.Services.GetRequiredService<T>() で取り出すだけ
  3. View 側は StaticResource+Binding で DataContext を設定
  4. デザインタイムや Hot Reload のクセを知っておくと開発がラク

「公式 DI を WPF でも普通に使える」と分かれば、ASP.NET Core や MAUI とのコード共有もしやすくなります。まずはこのテンプレートを貼り付けて、自分の ViewModel/Service を登録してみてください。