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:
- ViewModel 中启用 CanExecute 条件
[RelayCommand(CanExecute = nameof(CanButtonClick))]
private void OnButtonClick()
{
// ...原有逻辑...
}
private bool CanButtonClick() => SelectedNic != null; // 只有选中项时按钮可用
- 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
② 服务生命周期
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()
);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架