[英]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中,我通过创建主窗口并设置其
DataContext
和MainViewModel
属性来启动应用程序:
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()
函数,该函数将更改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
}
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.