You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

带参数的依赖注入: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 12:05:54