WPF MVVM模式下通过View名获取ViewModel及无默认构造函数报错解决方案
实现方案:依赖注入 + ViewModelLocator组合
这个方案可以完美解决你的两个核心需求:1. 不用手动new ViewModel,自动解决构造函数的服务注入问题;2. 支持按View名称获取对应的ViewModel实例。
第一步:配置依赖注入容器
首先安装NuGet包Microsoft.Extensions.DependencyInjection,然后修改App的启动逻辑,替换默认的启动流程:
- 删掉
App.xaml中的StartupUri="MainView.xaml"属性 - 改写
App.xaml.cs代码:
public partial class App : Application { // 全局DI容器,所有实例都从这里获取 public static IServiceProvider ServiceProvider { get; private set; } protected override void OnStartup(StartupEventArgs e) { var serviceCollection = new ServiceCollection(); // 注册你的全局服务 serviceCollection.AddSingleton<SomeService>(); // 注册所有ViewModel serviceCollection.AddSingleton<MainViewModel>(); // 所有实现INavigation的ViewModel都按接口注册 serviceCollection.AddSingleton<INavigation, FirstViewModel>(); serviceCollection.AddSingleton<INavigation, SecondViewModel>(); // 构建容器 ServiceProvider = serviceCollection.BuildServiceProvider(); // 手动启动主窗口,DataContext从容器获取 var mainView = new MainView { DataContext = ServiceProvider.GetRequiredService<MainViewModel>() }; mainView.Show(); base.OnStartup(e); } }
同时删掉MainView.xaml.cs里手动设置DataContext的代码,现在由DI容器注入MainViewModel实例。
第二步:修改MainViewModel解决构造报错
DI容器会自动把所有注册的INavigation实现类注入到MainViewModel的构造函数,不需要你手动new:
public MainViewModel(IEnumerable<INavigation> navigationViewModels) { ShowPageCommand = new RelayCommand<int>(ShowPage); NavigationItems = new ObservableCollection<INavigation>(); int index = 0; foreach (var vm in navigationViewModels) { vm.NavigationIndex = index++; NavigationItems.Add(vm); } // 默认选中第一个 NavigationItems[0].IsActive = true; SelectedNavigation = NavigationItems[0]; }
到这一步你原来的构造函数报错已经完全解决了。
第三步:实现按View名称获取ViewModel的功能
新增ViewModelLocator静态类,实现你要的按View名称拿VM的逻辑:
public static class ViewModelLocator { /// <summary> /// 传入View名称(如FirstView.xaml)返回对应的ViewModel实例 /// </summary> public static INavigation GetViewModelByViewName(string viewName) { // 这里用你自己的命名约定匹配即可,示例约定:View为xxxView.xaml,对应ViewModel为xxxViewModel var viewModelName = viewName.Replace(".xaml", string.Empty).Replace("View", "ViewModel"); // 从DI容器获取所有INavigation实例,匹配名称返回 var allNavViewModels = App.ServiceProvider.GetServices<INavigation>(); return allNavViewModels.FirstOrDefault(vm => vm.GetType().Name == viewModelName); } /// <summary> /// 也支持直接按类型获取ViewModel /// </summary> public static T GetViewModel<T>() where T : class { return App.ServiceProvider.GetRequiredService<T>(); } }
使用示例:
你在任意地方需要获取FirstViewModel,只要传View名称即可:
var firstVm = ViewModelLocator.GetViewModelByViewName("FirstView.xaml");
可选优化
如果你的ViewModel数量很多,不想手动一个个注册,可以用反射批量扫描程序集里所有实现了INavigation的类,自动注册到DI容器,不用手动修改注册代码。
如果需要每次获取都是全新的ViewModel实例,注册的时候把AddSingleton改成AddTransient即可。
内容的提问来源于stack exchange,提问作者Melissa
相关产品推荐
相关产品推荐

