从另一个视图模型调用WPF命令的MVVM方式是什么?

时间:2017-06-29 15:17:38

标签: c# wpf mvvm

我正在尝试更多地了解WPF中的MVVM实现,目前需要一些使用ViewModels进行导航的指导。我正在跟踪来自Rachel's blog的WPF导航示例,并且需要一种从其他ViewModel调用CommandViewModel命令的方法。

根据博客,从MainWindow切换视图非常清楚,但我想了解更多关于视图间导航的信息,即我在MainWindow上的Home,Product和Contact按钮以及View和ViewModel类,现在我想要从Home视图中的某个按钮而不是MainWindow打开联系页面。我已经在Home ViewModel中编写了一些代码来实现相同但我怀疑这是否是MVVM的最佳实践。有没有办法从HomeView.XAML实现相同的目标?

来自博客的

代码段 - ApplicationViewModel.cs

private ICommand _changePageCommand;

private IPageViewModel _currentPageViewModel;
private List<IPageViewModel> _pageViewModels;

public ApplicationViewModel()
{
    // Add available pages in c'tor
    PageViewModels.Add(new HomeViewModel(this));
    PageViewModels.Add(new ProductsViewModel());
    PageViewModels.Add(new ContactViewModel());
}

public ICommand ChangePageCommand
{
    get
    {
        if (_changePageCommand == null)
            _changePageCommand = new RelayCommand(
              p => ChangeViewModel((IPageViewModel)p), p => p is IPageViewModel);

        return _changePageCommand;
    }
}

private void ChangeViewModel(IPageViewModel viewModel)
{
    if (!PageViewModels.Contains(viewModel))
        PageViewModels.Add(viewModel);

    CurrentPageViewModel = PageViewModels.FirstOrDefault(vm => vm == viewModel);
}
来自博客的

代码段 - ApplicationView.xaml

<Window.Resources>
    <DataTemplate DataType="{x:Type local:HomeViewModel}">
        <local:HomeView />
    </DataTemplate>

    <!-- Data template for other views -->
</Window.Resources>

<DockPanel>
    <Border DockPanel.Dock="Left" BorderBrush="Black" BorderThickness="0,0,1,0">
        <ItemsControl ItemsSource="{Binding PageViewModels}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Button Content="{Binding Name}" 
                            Command="{Binding DataContext.ChangePageCommand, 
                            RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
                            CommandParameter="{Binding }"/>

 <!--All closing tags-->

我在HomeViewModel.cs中的代码

// This is the command to get bind with my button inside Home view to invoke Contact view

private ICommand _loadContactCommand;
public ICommand LoadContactCommand
{
    get
    {
        if (_loadContactCommand == null)
            _loadContactCommand = new RelayCommand(p => LoadOtherView());

        return _loadContactCommand;
    }
}

private void LoadOtherView()
{
    // _appVM is the instance of 'ApplicationViewModel' which is being set from c'tor
    // Even I'm thinking to pass Contact view member of ApplicationViewModel class here, 
    // as I need exactly the same instance of the Contact which has been created earlier

    _appVM.ChangePageCommand.Execute(new ContactViewModel());
}

1 个答案:

答案 0 :(得分:6)

我有几种方法可以做到这一点。

第一个,如果操作是交互的服务类型,我认为这是一个相当不错的示例,我会在接口中描述操作并将其作为依赖项注入到需要它的ViewModels。

这实际上就是你正在做的事情,但是值得将它抽象到一个界面中。这样可以减少两个ViewModel之间的紧密耦合。

以下是在IPageDisplay界面中包装功能的示例:

public interface IPageDisplay
{
    IPageViewModel GetCurrentPage();
    void ChangeViewModel(IPageViewModel newPage);
}

您的ApplicationViewModel实现了它,并且具有与之前完全相同的方法:

public class ApplicationViewModel: IPageDisplay
{
    // implement like you are doing

HomeViewModel然后将class HomeViewModel { HomeViewModel(IPageDisplay pageDisplay) {//constructor stuff} private void LoadOtherView() { // Instead of interacting with a whole ViewModel, we just use the interface _pageDisplay.ChangePageCommand.Execute(new ContactViewModel()); } 作为界面,而不是整个&#39;视图模型:

HomeViewModel

这是更安全的&#39;因为它更抽象。您只需模拟AppViewModel即可在不创建IPageDisplay的情况下测试AppViewModel。您可以通过IPageDisplay的其他实现来更改页面的显示方式或IPageDisplay的实现,还可以在任何其他类型的位置显示您的页面。< / p>

值得注意的是,任何需要执行导航操作的页面都需要PageManager。如果你有很多依赖项,那么匹配所有这些依赖项会很麻烦 - 这就像依赖注入框架这样的东西可以真正帮助你。

第二个将是评论中建议的中介模式。您可以使用一个公共中介ChangeViewModel(IPageViewModel newPage);来定义ChangeViewModelRequest方法并触发ApplicationViewModel事件或回调。要更改当前页面的ViewModels和任何其他PageManager接受ApplicationViewModel实例作为依赖项。 public class ApplicationViewModel { private EventAgregator _eventAggregator; ApplicationViewModel(EventAgregator eventAggregator) { this._eventAggregator = eventAggregator; _eventAggregator.Subscribe('ChangeViewModelRequest', (EventArgs eventArgs) => ChangeViewModel(eventArgs.Parameter)) } private void ChangeViewModel(IPageViewModel viewModel) { if (!PageViewModels.Contains(viewModel)) PageViewModels.Add(viewModel); CurrentPageViewModel = PageViewModels.FirstOrDefault(vm => vm == viewModel); } } 听取事件,另一个人调用ChangeViewModelRequest来触发它。

同样,如果在复杂的应用程序中,则需要有效地管理依赖注入。

这自然会导致第三。这是中介模式的扩展,即事件聚合器。

事件聚合器是一种通用服务,它允许所有不同的ViewModel引发或订阅应用程序范围的事件。这绝对值得一看。

在这里,您的ApplicationViewModel订阅了该事件:

private void LoadOtherView()
{
    _eventAggregator.Publish("ChangeViewModelRequest", new EventArgs(new ContactViewModel()));
}

HomeViewModel发布到事件:

public class HomeViewModel : IPageViewModel // doesn't implement IPageDisplay
{
    private IPageDisplay _pageDisplay;
    public HomeViewModel(IPageDisplay pageDisplay)
    {
        // HomeViewModel doesn't implement IPageDisplay, it *consumes* one
        // as a dependency (instead of the previous ApplicationViewModel).
        // Note, that the instance you're passing still is the ApplicationViewModel,
        // so not much has actually changed - but it means you can have another
        // implementation of IPageDisplay. You're only linking the classes together
        // by the functionality of displaying a page.
        _pageDisplay= pageDisplay;
    }

    public string Name
    {
        get
        {
            return "Home Page";
        }
    }

    private ICommand _loadDashboardCommand;
    public ICommand LoadDashboardCommand
    {
        get
        {
            if (_loadDashboardCommand == null)
            {
                _loadDashboardCommand = new RelayCommand(
                    p => LoadOtherView());
            }
            return _loadDashboardCommand;
        }
    }

    private void LoadOtherView()
    {
        // Here you have the context of ApplicatiomViewModel like you required
        // but it can be replaced by any other implementation of IPageDisplay
        // as you're only linking the little bit of interface, not the whole class

        _pageDisplay.ChangeViewModel(new DashboardViewModel());
    }
}

您可以使用大量的事件聚合器,其中一些内置于像Prism这样的MVVM框架中。

虽然像所有其他人一样,这是一种依赖 - 它是一种非常普遍的依赖。很可能,您的大多数ViewModel都需要访问聚合器实例并将其作为依赖项,因为它几乎可用于所有视图间模型通信。只需让所有虚拟机将其传递给构造函数中任何创建的虚拟机,都可以用于简单的应用程序。但我仍然会说支持依赖注入(比如工厂模式?)的东西值得实施。

修改

以下是HomeViewModel所需的内容:

{{1}}

}