在 WPF MVVM 中处理 ContentControls 之间导航的更好方法?

Better way to handle navigation between ContentControls in WPF MVVM?

我目前正在开发 C# WPF 应用程序,并尝试遵循 MVVM 设计模式。

它现在的工作方式是,我在主 window 中使用 ContentControl 并将其绑定到 CurrentViewModel,并在 App.xaml 中声明我的数据模板。当我想改变主 window 中的当前视图时,我所要做的就是改变主 window 的视图模型中的 CurrentViewModel 属性,这是有效的出色地。此外,为了不直接引用视图模型(通过在视图模型中执行 new blablaViewModel()),我有一个单例 FlowManager class 我在 ICommand 函数,实例化是在 class 而不是视图模型中完成的。

这种方法的问题是,对于我添加到我的应用程序的每个视图,我必须在 App.xaml 中添加一个数据模板,在我的 FlowManager 中添加一个 enum 条目 class 和我的 switch() 中的新 caseChangePage() 函数中,我的 MainViewModel 中的新 ICommand,添加实际视图的代码和创建它自己的视图模型。

以下是我如何处理应用程序流程的示例:

MainWindow.xaml中,我有如下布局:

<Window x:Class="EveExcelMineralUpdater.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:viewModels="clr-namespace:EveExcelMineralUpdater.ViewModels"
        Title="MainWindow" Height="720" Width="1280">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="15*"/>
            <ColumnDefinition Width="auto"/>
            <ColumnDefinition Width="85*"/>
        </Grid.ColumnDefinitions>

        <StackPanel Grid.Column="0">
            <Button>MarketStat Request</Button>
            <Button Command="{Binding ChangeToQuickLookCommand}">QuickLook Request</Button>
            <Button>History Request</Button>
            <Button>Route Request</Button>
            <Button>Settings</Button>
        </StackPanel>

        <Separator Grid.Column="1" Style="{StaticResource {x:Static ToolBar.SeparatorStyleKey}}" />

        <ContentControl Grid.Column="2" Content="{Binding CurrentViewModel}" />
    </Grid>
</Window>

App.xaml.cs 中,我通过创建主 window 并设置其 DataContextMainViewModel 属性:

MainWindow mainWindow = new MainWindow();
MainViewModel mainViewModel = new MainViewModel();
mainWindow.DataContext = mainViewModel;
mainWindow.ViewModel = mainViewModel;

FlowManager.Instance.AppWindow = mainWindow;

mainWindow.Show();

MainViewModel.cs 中,我处理按钮请求以将 CurrentView 属性 更改为 ICommand,如下所示:

private void ChangeToQuickLook(object param)
{
    FlowManager.Instance.ChangePage(FlowManager.Pages.QuickLook);
}
...
public ICommand ChangeToQuickLookCommand
{
    get { return new RelayCommand(ChangeToQuickLook); }
}

FlowManager.cs 中,我有一个 enum 列出了我的应用程序中的所有页面(视图),以及实际的 ChangePage()将更改我的 MainViewModel:

中的 CurrentViewModel 属性 的函数
// Only one view is implemented for now, the rest are empty for now
public void ChangePage(Pages page)
{
    IViewModel newViewModel = null;

    switch (page)
    {
        case Pages.MarketStat:
            break;
        case Pages.QuickLook:
            newViewModel = new QuickLookRequestViewModel();
            break;
        case Pages.History:
            break;
        case Pages.Route:
            break;
        case Pages.Settings:
            break;
    }

    AppWindow.ViewModel.CurrentViewModel = newViewModel;
}
...
public enum Pages
{
    MarketStat,
    QuickLook,
    History,
    Route,
    Settings
}

最后,在 App.xaml 中,我有我所有视图的所有数据模板列表:

<Application x:Class="EveExcelMineralUpdater.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:viewModels="clr-namespace:EveExcelMineralUpdater.ViewModels"
             xmlns:views="clr-namespace:EveExcelMineralUpdater.Views"
             Startup="App_OnStartup">
    <Application.Resources>
        <!-- Pages DataTemplates -->
        <DataTemplate DataType="{x:Type viewModels:QuickLookRequestViewModel}">
            <views:QuickLookRequestView />
        </DataTemplate>
    </Application.Resources>
</Application>

就像我说的,这很好用,但是我发现了一些可伸缩性问题,因为我必须修改代码的几个部分才能在应用程序中添加视图。有没有不使用任何框架的更好方法?

看了@WojciechKulik 的评论后,我在 FlowManager.cs class:

中做出了以下更改
public class FlowManager
{
    private static FlowManager _instance;

    private MainWindow _mainWindow;
    private ICollection<IViewModel> _viewModels; 

    private FlowManager()
    {
        ViewModels = new List<IViewModel>();
    }

    public void ChangePage<TViewModel>() where TViewModel : IViewModel, new()
    {
        // If we are already on the same page as the button click, we don't change anything
        if (AppWindow.ViewModel.CurrentViewModel == null || 
            AppWindow.ViewModel.CurrentViewModel.GetType() != typeof(TViewModel))
        {
            foreach (IViewModel viewModel in ViewModels)
            {
                // If an instance of the viewmodel already exists, we switch to that one
                if (viewModel.GetType() == typeof(TViewModel))
                {
                    AppWindow.ViewModel.CurrentViewModel = viewModel;
                    return;
                }
            }

            // Else, we create a new instance of the viewmodel
            TViewModel newViewModel = new TViewModel();
            AppWindow.ViewModel.CurrentViewModel = newViewModel;
            ViewModels.Add(newViewModel);
        }
    }

    public static FlowManager Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new FlowManager();
            }

            return _instance;
        }
    }

    public MainWindow AppWindow { get; set; }

    public ICollection<IViewModel> ViewModels { get; private set; }
}

通过这种方式,我添加了对保持每个视图视图模型状态的支持,并且通过使用 Generics 和反射。

如果我找到其他方法来减少我必须为要添加到应用程序的每个视图修改的位置数量,我将更新此答案。