带参数的依赖注入:WPF列表项选择加载带VM的UserControl问题
问题描述
我有一个WPF应用程序,主窗口左侧是包含若干条目的ListBox,右侧是ContentControl。选中ListBox中的条目时,需要加载对应的UserControl及其ViewModel。
选中ListBox条目后,需创建带ViewModel的UserControl实例,并将选中元素或其某个字段传入ViewModel的构造函数。
目前不清楚如何在不手动创建UserControl和ViewModel实例、不违反依赖注入(DI)原则的前提下正确实现该功能——手动创建实例会导致应用关闭时内存无法释放。
现有代码
MainView.xaml
<ListBox ItemsSource="{Binding ContainerList}" SelectedItem="{Binding SelectedContainer}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> <i:Interaction.Triggers> <i:EventTrigger EventName="MouseLeftButtonUp"> <i:InvokeCommandAction Command="{Binding ShowContent}"/> </i:EventTrigger> </i:Interaction.Triggers> </ListBox> <ContentControl Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Content="{Binding TheContent}"></ContentControl>
代码后置(MainView与MainViewModel)
public partial class MainView : Window { public MainView(IMainViewModel viewModel) { this.DataContext = viewModel; InitializeComponent(); } } public class MainViewModel : ViewModelBase, IMainViewModel { private readonly IRepositories _repositories; private readonly IAbstractFactory<ChangeExecutorView> _factory; public ObservableCollection<Container> ContainerList { get; set; } private Container _SelectedContainer { get; set; } public Container SelectedContainer { get { return _SelectedContainer; } set { _SelectedContainer = value; OnPropertyChanged(nameof(SelectedContainer)); } } private object _TheContent { get; set; } public object TheContent { get { return _TheContent; } set {_TheContent = value; OnPropertyChanged(nameof(TheContent)); } } public MainViewModel(IRepositories repositories, IAbstractFactory<ChangeExecutorView> factory) { _repositories = repositories; _factory = factory; ContainerList = new ObservableCollection<Container>(_repositories.ContainerRepository.GetAll()); } // 用于创建新UserControl的命令 public ICommand ShowContent { get { return new RelayCommand(delegate (object param) { // 注释掉的手动创建实例代码 // var content = new ContainerContentView( // new ContainerContentViewModel(_repositories, SelectedUserID)); }); } } } public interface IMainViewModel { ObservableCollection<Container> ContainerList { get; set; } }
App.xaml代码后置
public static IHost AppHost { get; set; } public App() { AppHost = Host.CreateDefaultBuilder() .ConfigureHostConfiguration((hostConfiguration => { hostConfiguration.AddJsonFile("appsettings.json",false,true) .AddEncryptedProvider() .AddJsonFile($"appsettings.json", false, true); })) .ConfigureServices((hostConext, services) => { services.AddSingleton<MainView>(); services.AddTransient<IMainViewModel, MainViewModel>(); services.AddTransient<IRepositories,Repositories>(); services.AddFormFactory<ChangeExecutorView>(); services.AddScoped<ContainerContentViewModel>(); }) .Build(); } public static T GetService<T>() where T : class { var service = AppHost.Services.GetService(typeof(T)) as T; return service; } protected override async void OnStartup(StartupEventArgs e) { await AppHost.StartAsync();//.ConfigureAwait(true); var startupForm = AppHost.Services.GetRequiredService<MainView>(); startupForm.Show(); base.OnStartup(e); } protected override async void OnExit(ExitEventArgs e) { await AppHost.StopAsync(); base.OnExit(e); } }
解决方案
1. 定义带参数的ViewModel工厂接口
由于需要将SelectedContainer传入ViewModel构造函数,直接通过DI无法传递参数,需定义工厂接口创建ViewModel实例:
public interface IContainerContentViewModelFactory { ContainerContentViewModel Create(Container selectedContainer); } public class ContainerContentViewModelFactory : IContainerContentViewModelFactory { private readonly IRepositories _repositories; // 注入ViewModel依赖的服务 public ContainerContentViewModelFactory(IRepositories repositories) { _repositories = repositories; } public ContainerContentViewModel Create(Container selectedContainer) { // 传递选中的容器到ViewModel构造函数 return new ContainerContentViewModel(_repositories, selectedContainer.Id); } }
2. 注册工厂与View到DI容器
在App.xaml的服务配置中,添加工厂和ContainerContentView的注册:
.ConfigureServices((hostConext, services) => { // 原有注册保持不变 services.AddSingleton<MainView>(); services.AddTransient<IMainViewModel, MainViewModel>(); services.AddTransient<IRepositories,Repositories>(); services.AddFormFactory<ChangeExecutorView>(); // 注册工厂和View services.AddTransient<IContainerContentViewModelFactory, ContainerContentViewModelFactory>(); services.AddTransient<ContainerContentView>(); // 注册UserControl,由DI管理生命周期 })
3. 修改MainViewModel使用工厂创建实例
在MainViewModel中注入工厂和服务提供者,在ShowContent命令中通过DI获取UserControl并绑定ViewModel:
public class MainViewModel : ViewModelBase, IMainViewModel { private readonly IRepositories _repositories; private readonly IAbstractFactory<ChangeExecutorView> _factory; private readonly IContainerContentViewModelFactory _contentViewModelFactory; private readonly IServiceProvider _serviceProvider; // 注入服务提供者获取UserControl // 修改构造函数,注入工厂和服务提供者 public MainViewModel(IRepositories repositories, IAbstractFactory<ChangeExecutorView> factory, IContainerContentViewModelFactory contentViewModelFactory, IServiceProvider serviceProvider) { _repositories = repositories; _factory = factory; _contentViewModelFactory = contentViewModelFactory; _serviceProvider = serviceProvider; ContainerList = new ObservableCollection<Container>(_repositories.ContainerRepository.GetAll()); } public ICommand ShowContent { get { return new RelayCommand(delegate (object param) { if (SelectedContainer == null) return; // 通过工厂创建带参数的ViewModel var viewModel = _contentViewModelFactory.Create(SelectedContainer); // 通过DI获取UserControl实例 var contentView = _serviceProvider.GetRequiredService<ContainerContentView>(); // 绑定ViewModel到View contentView.DataContext = viewModel; // 设置到ContentControl TheContent = contentView; }); } } }
4. 优化内存管理与MVVM规范
- 避免强引用泄漏:如果ViewModel使用了事件订阅,需实现
IDisposable接口并在销毁时取消订阅;切换TheContent时确保旧实例无其他强引用。 - 使用Transient生命周期:将View和ViewModel注册为Transient,每次创建新实例,便于GC回收。
- 可选:DataTemplate自动绑定(更符合MVVM)
在MainView.xaml中定义DataTemplate,让ContentControl自动根据ViewModel类型加载对应View,无需手动创建View实例:
<Window.Resources> <DataTemplate DataType="{x:Type local:ContainerContentViewModel}"> <local:ContainerContentView/> </DataTemplate> </Window.Resources>
此时ShowContent命令只需设置TheContent为ViewModel即可:
public ICommand ShowContent { get { return new RelayCommand(delegate (object param) { if (SelectedContainer == null) return; TheContent = _contentViewModelFactory.Create(SelectedContainer); }); } }
这种方式由WPF自动管理View的创建与绑定,减少手动代码,进一步降低内存泄漏风险。
内容的提问来源于stack exchange,提问作者The Witcher
相关产品推荐
相关产品推荐

