【问题标题】:Better way to handle navigation between ContentControls in WPF MVVM?处理 WPF MVVM 中 ContentControls 之间导航的更好方法?
【发布时间】:2015-05-11 16:53:10
【问题描述】:

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

它现在的工作方式是我在主窗口中使用ContentControl 并将其绑定到CurrentViewModel,并在App.xaml 中声明我的数据模板。当我想更改主窗口中的当前视图时,我所要做的就是更改主窗口视图模型中的CurrentViewModel 属性,效果很好。另外,为了不直接引用视图模型(通过在视图模型中执行new blablaViewModel()),我有一个在ICommand 函数中调用的单例FlowManager 类,并且实例化在那个类而不是视图模型。

这种方法的问题在于,对于我添加到应用程序的每个视图,我必须在App.xaml 中添加一个数据模板,在我的FlowManager 类中添加一个enum 条目,在我的类中添加一个新的case switch()ChangePage() 函数中,在我的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 中,我通过创建主窗口并设置其DataContextMainViewModel 属性来启动应用程序:

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

FlowManager.Instance.AppWindow = mainWindow;

mainWindow.Show();

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

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

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

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

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

【问题讨论】:

标签: c# wpf mvvm navigation datatemplate


【解决方案1】:

查看@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; }
}

这样,我添加了对保持我的每个视图的视图模型状态的支持,并且我摆脱了我的enum,并通过使用Generics 和反射的力量为我在我的应用程序中拥有的每个视图添加了一个条目.

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-12-05
    • 1970-01-01
    • 2016-07-29
    • 2019-12-05
    • 2023-03-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多