WPF笔记14——依赖注入

背景:.net8+WPF+CommunityToolKit.Mvvm

需求:点击界面上的按钮,需要弹出一个提示框。

方法1:直接在对应的ViewModel里写

争议:弹出提示框属于UI操作,在MVVM中应该避免在ViewModel中直接调用MessageBox,因为这会导致视图逻辑和业务逻辑的耦合。

  [ObservableProperty]
    private NicDto? selectedNic; // 允许 null

    // 添加按钮点击命令
    [RelayCommand]
    private void OnButtonClick()
    {
        if (SelectedNic == null)
        {
            MessageBox.Show("请先选择一个网卡!", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
            return;
        }

        // 如果已选中,执行后续操作(例如读取 SelectedNic.IPAddress)
        MessageBox.Show($"已选择网卡:{SelectedNic.Description}", "操作成功");
    }

    public MyViewModel()
    {
        AvailableNics = new ObservableCollection<NicDto>
        {
            new NicDto { Description = "网卡1", IPAddress = IPAddress.Parse("192.168.1.1") },
            new NicDto { Description = "网卡2", IPAddress = IPAddress.Parse("192.168.1.2") }
        };
    }

方法2:使用依赖注入

特点:不会破坏MVVM结构,通过接口(如 IMessageService)解耦 UI 交互。但对初学者有难度。

// 定义接口
public interface IMessageService
{
    void ShowWarning(string message);
}

// 在 ViewModel 构造函数中注入
private readonly IMessageService _messageService;
public MyViewModel(IMessageService messageService)
{
    _messageService = messageService;
}

// 使用接口代替 MessageBox
if (SelectedNic == null)
{
    _messageService.ShowWarning("请先选择一个网卡!");
    return;
}

扩展功能:根据选中项自动禁用按钮

如果希望未选择时按钮自动变为不可点击状态,修改 ViewModel 和 XAML:

  1. ViewModel 中启用 CanExecute 条件
[RelayCommand(CanExecute = nameof(CanButtonClick))]
private void OnButtonClick()
{
    // ...原有逻辑...
}

private bool CanButtonClick() => SelectedNic != null; // 只有选中项时按钮可用
  1. XAML 中无需修改,自动同步状态

ViewModel的构造要传入IMessageService messageService参数

使用 CommunityToolkit.Mvvm 实现依赖注入时,需要通过 IOC 容器(控制反转容器)来管理服务注册和解析。以下是具体实现步骤:

1. 定义 IMessageService 接口和实现类

首先创建消息服务的接口和具体实现(例如弹窗服务):

点击查看代码
// IMessageService.cs
public interface IMessageService
{
    void ShowWarning(string message);
}

// MessageBoxService.cs
public class MessageBoxService : IMessageService
{
    public void ShowWarning(string message)
    {
        MessageBox.Show(message, "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
    }
}

2. 在 ViewModel 中注入服务

修改 ViewModel,通过构造函数注入服务:

点击查看代码
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;

public partial class MyViewModel : ObservableObject
{
    private readonly IMessageService _messageService;

    [ObservableProperty]
    private ObservableCollection<NicDto> availableNics;

    [ObservableProperty]
    private NicDto? selectedNic;

    // 构造函数接收 IMessageService
    public MyViewModel(IMessageService messageService)
    {
        _messageService = messageService;
        InitializeNics();
    }

    private void InitializeNics()
    {
        AvailableNics = new ObservableCollection<NicDto>
        {
            new NicDto { Description = "网卡1", IPAddress = IPAddress.Parse("192.168.1.1") },
            new NicDto { Description = "网卡2", IPAddress = IPAddress.Parse("192.168.1.2") }
        };
    }

    [RelayCommand]
    private void OnButtonClick()
    {
        if (SelectedNic == null)
        {
            _messageService.ShowWarning("请先选择一个网卡!");
            return;
        }
        _messageService.ShowWarning($"已选择网卡:{SelectedNic.Description}");
    }
}

3. 配置 IOC 容器

在 WPF 应用程序启动时(通常在 App.xaml.cs 中),注册服务和 ViewModel:

点击查看代码
// App.xaml.cs
public partial class App : Application
{
    public App()
    {
        // 初始化 CommunityToolkit.Mvvm 的 IOC 容器
        Ioc.Default.ConfigureServices(
            new ServiceCollection()
            // 注册服务
            .AddSingleton<IMessageService, MessageBoxService>()
            // 注册 ViewModel
            .AddTransient<MyViewModel>()
            .BuildServiceProvider()
        );
    }
}

4. 在 View 中获取 ViewModel

在窗口的代码后端(如 MainWindow.xaml.cs)中,通过 IOC 容器解析 ViewModel:

点击查看代码
// MainWindow.xaml.cs
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        // 通过 IOC 容器获取 ViewModel 实例
        DataContext = Ioc.Default.GetService<MyViewModel>();
    }
}

关键点解析

① 为什么不需要手动传递参数?

IOC 容器自动解析:通过 Ioc.Default 容器注册了 MyViewModel 和 IMessageService,当调用 GetService() 时,容器会自动解析构造函数所需的 IMessageService 参数(因为已注册 MessageBoxService)。

② 服务生命周期

AddSingleton:服务以单例模式存在,整个应用程序生命周期内只有一个实例。

AddTransient:每次请求时创建新实例(适合 ViewModel)。

③ 为何在 App.xaml.cs 中配置 IOC?

这是 WPF 应用程序的入口点,确保在窗口创建前完成服务注册。

扩展:使用更强大的 DI 容器

如果项目复杂度增加,可以替换为其他 DI 容器(如 Microsoft.Extensions.DependencyInjection),但 CommunityToolkit.Mvvm 内置的 Ioc 类已足够应对大多数场景。


注意

1、如果我们的App.xaml.cs 中没有 public App() 方法,可以直接手动添加,但是要确保下面两点:

1.1 检查 App.xaml 的标记:
确保 App.xaml 中没有定义 StartupUri(如果已定义,需删除或通过代码动态创建窗口):

点击查看代码
<!-- App.xaml -->
<Application x:Class="YourNamespace.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <!-- 删除 StartupUri="MainWindow.xaml" -->
</Application>

1.2 在 App.xaml.cs 中添加构造函数:
手动添加 App 构造函数,并在其中初始化 IOC 容器:

点击查看代码
using Microsoft.Extensions.DependencyInjection;

public partial class App : Application
{
    public App()
    {
        // 初始化 IOC 容器 
        // ServiceCollection类型的命名空间是Microsoft.Extensions.DependencyInjection
        // 通过NuGet安装:Install-Package Microsoft.Extensions.DependencyInjection
        Ioc.Default.ConfigureServices(
            new ServiceCollection()
                .AddSingleton<IMessageService, MessageBoxService>()
                .AddTransient<MyViewModel>()
                .BuildServiceProvider()
        );
    }

    protected override void OnStartup(StartupEventArgs e)
    {
        // 手动创建并显示主窗口
        var mainWindow = new MainWindow();
        mainWindow.Show();
        base.OnStartup(e);
    }
}

1.3 如果多个ViewModel要使用弹出提示框,那么就这样写:

public App()
{
    Ioc.Default.ConfigureServices(
        new ServiceCollection()
            .AddSingleton<IMessageService, MessageBoxService>() // 注册服务
            .AddTransient<ViewModelA>()
            .AddTransient<ViewModelB>()
            .BuildServiceProvider()
    );
}
posted @   青云Zeo  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
点击右上角即可分享
微信分享提示