我正在编写一个有两个窗口的 WPF 应用程序。
我有一个 MainWindowViewModel
包含另外两个 View 模型:AllTagsViewModel
和 PlotViewModel
.
public AllTagsViewModel AllTagsViewModel { get; private set; }
public PlotViewModel PlotViewModel { get; private set; }
目前,我将此解决方案用作主窗口中的单击处理程序:
private void LaunchPlotWindow_OnClick(object sender, RoutedEventArgs e)
{
if (PlotWindow.GlobalInstanceCount == 0)
{
PlotWindow plotWindow = new PlotWindow();
PlotViewModel context = GetViewModel().PlotViewModel;
plotWindow.DataContext = context;
plotWindow.Show();
}
}
我还将命令绑定(bind)到按钮。命令在
MainWindowViewModel
它使用构造函数 PlotViewModel(AllTagsViewModel atvm)
实例化一个新的 PlotViewModel .问题在于设置数据上下文的命令在单击处理程序之后执行。这意味着
PlotWindow
第二次打开时按预期工作。这个问题有什么更好的解决方案?我可以使用事件来保留
AllTagsViewModel
在 PlotViewModel
始终与 MainWindowViewModel
中的那个保持同步?我目前的解决方案感觉就像是一个黑客和非常糟糕的做法。感谢您的建议。
最佳答案
前言:
通常你不想拥有你的 PlotViewModel 并将它传递给一个窗口,因为它使一些事情变得更加复杂。
有 View-First 和 ViewModel First 的基本方法。在 View-First 中,您创建 View (页面、窗口等)并将 ViewModel 注入(inject)其中(通常通过构造函数)。虽然这使得向它传递参数对象有点困难。
这就是 NavigationService 的来源。您通过 IoC 容器解析 View ,然后将参数传递给 ViewModel,即如果它是 UserViewModel
你会通过 userId
到它,ViewModel 将加载用户。
解决方案:导航服务
您可以使用现有的(Prism 或其他带有自己的导航服务的 MVVM 框架)。
如果你想要一个自己的简单的,你可以创建一个 INavigationService
接口(interface)并将其注入(inject)到您的 ViewModels 中。
public interface INavigationService
{
// T is whatever your base ViewModel class is called
void NavigateTo<T>() where T ViewModel;
void NavigateToNewWindow<T>();
void NavigateToNewWindow<T>(object parameter);
void NavigateTo<T>(object parameter);
}
并像实现它一样(我假设您使用 IoC 容器,因为 IoC 是 MVVM 的关键,用于键解耦对象。Unity IoC 容器的示例)
public class NavigationService : INavigationService
{
private IUnityContainer container;
public NavigationService(IUnityContainer container)
{
this.container = container;
}
public void NavigateToWindow<T>(object parameter) where T : IView
{
// configure your IoC container to resolve a View for a given ViewModel
// i.e. container.Register<IPlotView, PlotWindow>(); in your
// composition root
IView view = container.Resolve<T>();
Window window = view as Window;
if(window!=null)
window.Show();
INavigationAware nav = view as INavigationAware;
if(nav!= null)
nav.NavigatedTo(parameter);
}
}
// IPlotView is an empty interface, only used to be able to resolve
// the PlotWindow w/o needing to reference to it's concrete implementation as
// calling navigationService.NavigateToWindow<PlotWindow>(userId); would violate
// MVVM pattern, where navigationService.NavigateToWindow<IPlotWindow>(userId); doesn't. There are also other ways involving strings or naming
// convention, but this is out of scope for this answer. IView would
// just implement "object DataContext { get; set; }" property, which is already
// implemented Control objects
public class PlotWindow : Window, IView, IPlotView
{
}
最后你实现你的
PlotViewModel
类并使用传递的参数加载对象public class PlotViewModel : ViewModel, INotifyPropertyChanged, INavigationAware
{
private int plotId;
public void NavigatedTo(object parameter) where T : IView
{
if(!parameter is int)
return; // Wrong parameter type passed
this.plotId = (int)parameter;
Task.Start( () => {
// load the data
PlotData = LoadPlot(plotId);
});
}
private Plot plotData;
public Plot PlotData {
get { return plotData; }
set
{
if(plotData != value)
{
plotData = value;
OnPropertyChanged("PlotData");
}
}
}
}
当然可以修改
NavigationService
还要设置DataContext
在里面。或者使用字符串来解析 View /窗口(例如 Prism for Windows Store Apps)。在最终代码中,您可以通过调用
navigationService.NavigateToWindow<IPlotView>(platId);
打开窗口在您的代码中(即在 ICommand
中,它绑定(bind)到 XAML 中的按钮 Command
属性。
关于c# - 如何使用单击处理程序和命令在 WPF MVVM 中打开另一个 View ? (我的解决方案合理吗?),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28817827/