如何使用单击处理程序和命令在 WPF MVVM 中打开另一个视图?(我的解决方案合理吗?) [英] How can I open another view in WPF MVVM using click handlers and commands? (Is my solution reasonable?)

查看:12
本文介绍了如何使用单击处理程序和命令在 WPF MVVM 中打开另一个视图?(我的解决方案合理吗?)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在编写一个有两个窗口的 WPF 应用程序.

I am writing a WPF application that has two windows.

我有一个 MainWindowViewModel 包含另外两个视图模型:AllTagsViewModelPlotViewModel.

I have a MainWindowViewModel that holds two more view models: AllTagsViewModel and PlotViewModel.

public AllTagsViewModel AllTagsViewModel { get; private set; }

public PlotViewModel PlotViewModel { get; private set; }

目前,我将此解决方案用作主窗口中的点击处理程序:

At the moment, I'm using this solution as a click handler in the main window:

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();
        }
    }

我还将命令绑定到按钮.该命令位于 MainWindowViewModel 中,它使用构造函数 PlotViewModel(AllTagsViewModel atvm) 实例化一个新的 PlotViewModel.

I am also binding a command to the button. The command is in the MainWindowViewModel and it instantiates a new PlotViewModel using the constructor PlotViewModel(AllTagsViewModel atvm).

这样做的问题是设置数据上下文的命令在点击处理程序之后执行.这意味着 PlotWindow 在第二次打开时按预期工作.

The problem with this is that the command setting the data context executes after the click handler. this means that the PlotWindow works as expected the second time it is opened.

这个问题有什么更好的解决方案?我可以使用一个事件来使 PlotViewModel 中的 AllTagsViewModel 始终与 MainWindowViewModel 中的那个保持同步吗?我目前的解决方案感觉像是一个黑客和非常糟糕的实践.

What is a better solution for this problem? Can I use an event to keep the AllTagsViewModel in the PlotViewModel up to date at all times with the one in the MainWindowViewModel? My solution at the moment feels like a hack and very poor practice.

谢谢你的建议.

推荐答案

前言:通常,您不希望拥有 PlotViewModel 并将其传递给窗口,因为这会使一些事情变得更加复杂.

Foreword: Usually you wouldn't want to have your PlotViewModel and pass it to a window, as it makes a few things more complicated.

有基本的方法 View-First 和 ViewModel First.在 View-First 中,您创建视图(页面、窗口等)并将 ViewModel 注入其中(通常通过构造函数).尽管这使得将参数对象传递给它有点困难.

There are to basic approaches View-First and ViewModel First. In View-First you create the View (Page, Window etc) and inject the ViewModel into it (usually via constructor). Though this makes it a bit difficult to and pass a parameter object to it.

这就是 NavigationService 的来源.您通过 IoC 容器解析 View,然后将参数传递给 ViewModel,即如果它是 UserViewModel,则您将 userId 传递给它,ViewModel 将加载用户.

Which is where the NavigationService comes. You resolve the View via IoC container, then pass a parameter to the ViewModel, i.e. if it's a UserViewModel you'd pass the userId to it and the ViewModel will load the user.

解决方案:导航服务您可以使用现有的(Prism 或其他自带导航服务的 MVVM 框架).

The solution: Navigation Service You can either use an existing one (Prism, or other MVVM Frameworks which come with their own navigation services).

如果您想要一个自己的简单接口,您可以创建一个 INavigationService 接口并将其注入您的 ViewModel.

If you want a own simple one, you could create an INavigationService interface and inject it into your 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 容器示例)

and implement it like (I am assuming you use a IoC container, since IoC is a key to MVVM to key the objects decoupled. Example with Unity IoC Container)

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 类并使用传递的参数加载对象

and finally you implement your PlotViewModel class and use the passed parameter to load the object

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.或者使用字符串来解析视图/窗口(例如 Windows Store Apps 的 Prism).

Of course could modify the NavigationService to also set the DataContext inside it. Or use strings to resolve the View/Window (such as Prism for Windows Store Apps does).

在最终代码中,您通过在代码中调用 navigationService.NavigateToWindow<IPlotView>(platId); 来打开窗口(即在绑定到的 ICommand 中)XAML 中的按钮 Command 属性.

And in the final code you open the window by calling navigationService.NavigateToWindow<IPlotView>(platId); in your code (i.e. in an ICommand which is bound to a buttons Command Property in your XAML.

这篇关于如何使用单击处理程序和命令在 WPF MVVM 中打开另一个视图?(我的解决方案合理吗?)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆