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



我正在尝试了解有关WPF中MVVM实现的更多信息,目前需要一些有关使用ViewModels进行导航的指导。我正在遵循 Rachel 博客中的 WPF 导航示例,并且需要一种方法来从其他视图模型调用应用程序视图模型的命令。

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

博客中的代码片段 - 应用程序视图模型.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-->

我在主页视图中的代码模型.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());
}

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

首先,如果操作是服务类型的交互,我认为这是一个很好的示例,我会在接口中描述操作,并将其作为依赖项注入到需要它的 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());
}

这是"更安全"的,因为它更抽象。您可以通过模拟IPageDisplay来测试HomeViewModel而无需创建AppViewModel。您可以更改页面的显示方式或AppViewModel的实现,您还可以通过具有其他一些IPageDisplay实现来在任何其他类型的位置显示页面。

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

第二种是评论中建议的调解人模式。您可以使用一个通用中介程序PageManager来定义 ChangeViewModel(IPageViewModel newPage); 方法并触发ChangeViewModelRequest事件或回调。ApplicationViewModel 和任何其他想要更改当前页面的ViewModels都接受PageManager实例作为依赖项。 ApplicationViewModel侦听事件,对方调用 ChangeViewModelRequest 来触发它。

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

这自然会引出第三个。它是调解器模式(事件聚合器(的扩展。

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

在这里,您的应用程序视图模型订阅了该事件:

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 框架(如 Prism(中。

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

编辑:

以下是您的家庭视图模型所需的内容:

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

}

最新更新