简体   繁体   English

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

[英]Better way to handle navigation between ContentControls in WPF MVVM?

I am currently developing a C# WPF application and am trying to follow the MVVM design pattern. 我目前正在开发C#WPF应用程序,并试图遵循MVVM设计模式。

The way it is working now, is I am using a ContentControl in my main window and binding it to the CurrentViewModel , and declaring in App.xaml my datatemplates. 现在的工作方式是,我在主窗口中使用ContentControl并将其绑定到CurrentViewModel ,并在App.xaml声明我的数据模板。 When I want to change the current view in the main window, all I have to do is change the CurrentViewModel property in the main window's view model, which works well. 当我想在主窗口中更改当前视图时,我要做的就是更改主窗口的视图模型中的CurrentViewModel属性,效果很好。 Also, in order to not have a direct reference of a view model (by doing new blablaViewModel() in a view model), I have a singleton FlowManager class that I call in the ICommand function, and the instantiation is done in that class rather than the view model. 另外,为了不直接引用视图模型(通过在视图模型中执行new blablaViewModel() ),我在ICommand函数中调用了一个Singleton FlowManager类,并且实例化是在该类中完成的比视图模型。

The problem with this approach, is that for each view I add to my application, I have to add a datatemplate in App.xaml , an enum entry in my FlowManager class and a new case in my switch() in the ChangePage() function, a new ICommand in my MainViewModel , on top of adding the code for the actual view and creating it's own view model. 这种方法的问题在于,对于我添加到应用程序中的每个视图,我必须在App.xaml添加一个数据模板,在FlowManager类中添加一个enum条目,并在ChangePage()函数中的switch()中添加一个新case 。 ,是MainViewModel新的ICommand,除了为实际视图添加代码并创建其自己的视图模型之外。

Here is an example of how I handle the flow of my application: 这是我处理应用程序流程的示例:

In MainWindow.xaml , I have the following layout: 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>

In App.xaml.cs I start the application by creating the main window and settings its DataContext and MainViewModel property: App.xaml.cs中,我通过创建主窗口并设置其DataContextMainViewModel属性来启动应用程序:

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

FlowManager.Instance.AppWindow = mainWindow;

mainWindow.Show();

In MainViewModel.cs , I handle a button request to change the CurrentView property with an ICommand like follows: MainViewModel.cs中 ,我使用ICommand处理一个按钮请求以更改CurrentView属性,如下所示:

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

In FlowManager.cs , I have an enum that lists all the pages (views) in my application, and the actual ChangePage() function that will change the CurrentViewModel property in my MainViewModel : FlowManager.cs中 ,我有一个enum ,列出了应用程序中的所有页面(视图),以及实际的ChangePage()函数,该函数将更改MainViewModelCurrentViewModel属性:

// 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
}

Finally, in App.xaml , I have the list of all my datatemplates for all my views: 最后,在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>

Like I said, this works well, but I can see some scalability problems as I have to modify several parts of my code in order to add a view in the application. 就像我说的那样,这很好,但是我看到了一些可伸缩性问题,因为我必须修改代码的几个部分才能在应用程序中添加视图。 Is there a better way to do this without the use of any frameworks? 有没有使用任何框架的更好方法呢?

After looking at @WojciechKulik comment, I have come up with the following change in my FlowManager.cs class: 在查看@WojciechKulik注释后,我在FlowManager.cs类中提出了以下更改:

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

This way, I add support for keeping the state of each of my view's viewmodel, and I got rid of my enum with an entry for each view I have in my application by using the power of Generics and reflection. 这样,我添加了对保持每个视图的视图模型状态的支持,并且通过使用Generics和反射的功能,为我的应用程序中的每个视图添加了一个条目,从而摆脱了enum

I will update this answer if I find other ways to reduce the number of places I have to modify for each view I want to add to the application. 如果我找到其他方法来减少要添加到应用程序中的每个视图所必须修改的位置数量,则将更新此答案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM