2017-06-29 67 views
2

我想了解更多关于MVVM在WPF中的实现,目前需要一些使用ViewModels进行导航的指导。我正在关注来自Rachel's blog的WPF导航示例,并需要从其他ViewModel调用ApplicationViewModel的Command。MVVM从另一个viewmodel调用WPF命令的方式是什么?

根据博客,从MainWindow切换视图非常清晰,但我想知道更多关于视图间导航,即说我有主窗口上的Home,Product和Contact按钮以及View和ViewModel类,现在我想要从主视图中的某个按钮而不是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()); 
} 
+0

您可以使用调解模式。 –

+0

@ rory.ap我了解调解器模式的基本原理。但是如何混合使用MVVM和Mediator? – Anup

回答

5

有几种方法我会这样做。

第一,如果动作是交互的业务类型,我觉得这是一个相当不错的例子,我将描述一个接口的作用,并注入它作为一个依赖关系到需要它的的ViewModels 。

这实际上是你在做什么,但它是值得抽象出来的界面。这提供了两个ViewModel之间更紧密的耦合。

这里是一个IPageDisplay界面包起来的功能的一个例子:

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

ApplicationViewModel实现它,有它以前做完全相同的方法:

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

HomeViewModel然后作为接口,而不是'整个'ViewModel:

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

这是'更安全',因为它更抽象。您只需嘲笑IPageDisplay即可测试HomeViewModel而不创建AppViewModel。您可以更改页面的显示方式或执行AppViewModel,还可以通过IPageDisplay的某些其他实现来显示任何其他类型的页面。

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

第二个将是评论中建议的中介模式。您可以拥有一个共同介体PageManager,该介体定义了ChangeViewModel(IPageViewModel newPage);方法并触发了ChangeViewModelRequest事件或回调。要更改当前页面的ApplicationViewModel和任何其他ViewModels接受PageManager实例作为依赖关系。 ApplicationViewModel监听事件,另一个的调用ChangeViewModelRequest来触发它。

同样,如果这是一个复杂的应用程序,则需要有效管理依赖注入。

这自然导致第三。这是中介模式(Event Aggregator)的扩展。

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

在这里,您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); 
    } 
} 

而且HomeViewModel发布给事件:

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

有很多事件聚合器,你可以使用一些内置MVVM框架像棱镜。

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

编辑:

这里有您需要为您的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()); 
    } 
} 

}

+0

似乎在一篇文章中有很多答案。我需要一些时间深入了解你的帖子。一旦我完成它,我将重新发布。 – Anup

+0

任何问题/混淆问。我花了一段时间试图让我的头脑如何构建虚拟机和通信。这很混乱。我试着添加更多的例子。 – Joe

+0

正在尝试你的接口解决方案,我如何通过_pageDisplay.ChangePageCommand.Execute(new ContactViewModel())调用ApplicationViewModel中定义的命令;'_pageDisplay接口的实例? – Anup

相关问题