筆記目錄

Skip to content

在 WPF MVVM Application 中使用 Dependency Injection

TLDR

  • 在 WPF 中使用 Dependency Injection (DI) 必須移除 App.xaml 中的 StartupUri 屬性,改為在 OnStartup 方法中手動建立並顯示 MainWindow
  • 使用 CommunityToolkit.Mvvm 時,ViewModel 必須繼承 ObservableObject 並宣告為 partial class。
  • ObservableProperty 會自動產生 Pascal Case 的屬性供 XAML 綁定。
  • RelayCommand 會自動產生「方法名稱 + Command」的屬性供 XAML 綁定。

在 WPF 使用 Dependency Injection

在 WPF 專案中引入 Microsoft.Extensions.DependencyInjection,可以有效管理物件生命週期並解耦元件。

App.xaml 設定與注意事項

若要在 WPF 中使用 DI,必須調整應用程式的啟動邏輯。

什麼情況下會遇到這個問題: 當你在 App.xaml 中保留了 StartupUri 屬性,同時又在 MainWindow 的建構函式中加入了需要注入的參數時,WPF 框架會因為找不到無參數的建構函式而拋出錯誤。

解決方案:

  1. 移除 App.xaml 中的 StartupUri 屬性。
  2. App.xaml.csOnStartup 方法中,手動建立 ServiceProvider 並啟動 MainWindow
csharp
public partial class App : Application {
    protected override void OnStartup(StartupEventArgs e) {
        IConfigurationBuilder builder = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);

        IConfiguration configuration = builder.Build();

        ServiceCollection serviceCollection = new ServiceCollection();
        ConfigureServices(serviceCollection, configuration);

        ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();

        MainWindow mainWindow = serviceProvider.GetRequiredService<MainWindow>()!;
        mainWindow.Show();
    }

    private static void ConfigureServices(IServiceCollection services, IConfiguration configuration) {
        services.Configure<AppOptions>(configuration!.GetSection("App"));
        services.AddTransient<MainWindow>();
        services.AddTransient<ViewModel>();
    }
}

建立 WPF MVVM Application

使用 CommunityToolkit.Mvvm 可以大幅簡化 MVVM 的開發流程。

ViewModel 實作規範

什麼情況下會遇到這個問題: 若未正確使用 partial 修飾詞或未繼承 ObservableObject,Source Generators 將無法產生對應的屬性與 Command,導致編譯失敗或綁定失效。

實作重點:

  • ViewModel 必須繼承 ObservableObject
  • ViewModel 必須使用 partial 修飾詞。
  • 使用 [ObservableProperty] 標記欄位,系統會自動產生對應的 Pascal Case 屬性。
  • 使用 [RelayCommand] 標記方法,系統會自動產生對應的「方法名 + Command」屬性。
csharp
public partial class ViewModel : ObservableObject {
    [ObservableProperty]
    private string? input;

    [RelayCommand]
    private void Submit() {
        MessageBox.Show("輸入值:" + Input);
        Input += "_修改";
    }
}

observable property attribute

relay command attribute

MainWindow.xaml 綁定

MainWindow 的建構函式中透過 DI 注入 ViewModel,並設定 DataContext

csharp
public partial class MainWindow : Window {
    public MainWindow(ViewModel viewModel) {
        InitializeComponent();
        DataContext = viewModel;
    }
}

在 XAML 中進行綁定時,需使用 Source Generator 產生的名稱:

xml
<TextBox Text="{Binding Input}"/>
<Button Content="送出" Command="{Binding SubmitCommand}"/>

執行結果

  • 輸入資料後點擊按鈕,SubmitCommand 會正確觸發 Submit() 方法。
  • 透過 ObservableProperty 產生的屬性,當 Input 值在 ViewModel 中變更時,UI 會自動同步更新。

wpf di demo inputwpf di demo successwpf di demo update

異動歷程

  • 2023-02-15 初版文件建立。