我正在使用MVVM模式构建我的第一个WPF.在这个社区的帮助下,我设法创建了我的模型,我的第一个ViewModel和视图.现在我想为设计基本应用程序布局界面的应用程序添加一些复杂性.我的想法是至少有2个子视图和一个主视图,并将它们分成几个XAML:
- Main.XAML
- Products.XAML
- Clients.XAML
Main将有一个菜单和一个空间来加载子视图(产品和客户端).现在遵循MVVM模式,视图之间的所有导航逻辑都应该在ViewModel上写入.所以mi想法是拥有4个ViewModel:
- MainViewModel
- ProductsViewModel
- ClientsViewModel
- NavigationViewModel
那么NavigationViewModel应该包含一个子视图模型的集合?一个活跃的viewmodel是吗?
所以我的问题是:
1)如何使用MVVM模式在主视图上加载不同的视图(产品,客户端)?
2)如何实现导航viewModel?
3)如何控制打开或活动视图的最大数量?
4)如何在打开的视图之间切换?
我一直在做大量的搜索和阅读,并且找不到任何简单的MVVM导航示例,其中WPF在主视图中加载了多个视图.许多人:
1)使用外部工具包,我现在不想使用它.
2)将所有视图的所有代码放在一个XAML文件中,这似乎不是一个好主意,因为我需要实现近80个视图!
我在这里正确的道路?任何帮助,特别是一些代码将不胜感激.
UPDATE
所以,我按照@LordTakkera的建议建立了一个测试项目,但是卡住了.这就是我的解决方案的样子:

我创造:
两种型号(客户和产品)
一个MainWindow和两个wpf用户控件(客户端和产品)XAML.
三个ViewModel(客户端,产品和主ViewModel)
然后我将每个视图上的dataContext设置为相应的viewModel.之后,我使用ContentPresenter创建MainWindow,并将其绑定到viewmodel的属性.
MainWindow.XAML
<Window x:Class="PruevaMVVMNavNew.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="519" Width="890">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="80"/>
<RowDefinition Height="*"/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<Border Grid.Column="0" Grid.ColumnSpan="2" Background="AntiqueWhite" ></Border>
<Border Grid.Row="1" Grid.RowSpan="2" Background="AliceBlue"></Border>
<Border Grid.Row="1" Grid.Column="1" Background="CadetBlue"></Border>
<ContentPresenter Grid.Row="1" Grid.Column="1" x:Name="ContentArea" Content="{Binding CurrentView}"/>
<StackPanel Margin="5" Grid.Column="0" Grid.Row="1">
<Button>Clients</Button>
<Button>Products</Button>
</StackPanel>
</Grid>
Run Code Online (Sandbox Code Playgroud)
这也是来自MainWindow的viewmodel:
class Main_ViewModel : BaseViewModel
{
public Main_ViewModel()
{
CurrentView = new Clients();
}
private UserControl _currentView;
public UserControl CurrentView
{
get
{
return _currentView;
}
set
{
if (value != _currentView)
{
_currentView = value;
OnPropertyChanged("CurrentView");
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
所以这个加载默认客户端查看并看起来像这样(这是正确的!):

所以我想我需要一种方法来将左侧的按钮与某个viemodel相关联,然后将它们与Main viewModel的CurrentView属性绑定.我怎样才能做到这一点?
UPDATE2
根据@LordTakkera建议我这样修改我的主viewModel:
class Main_ViewModel : BaseViewModel
{
public ICommand SwitchViewsCommand { get; private set; }
public Main_ViewModel()
{
//CurrentView = new Clients();
SwitchViewsCommand = new RelayCommand((parameter) => CurrentView = (UserControl)Activator.CreateInstance(parameter as Type));
}
private UserControl _currentView;
public UserControl CurrentView
{
get
{
return _currentView;
}
set
{
if (value != _currentView)
{
_currentView = value;
OnPropertyChanged("CurrentView");
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
我使用RelayCommand而不是DelegateCommand,但我认为它的工作方式相同.当我按下按钮并且类型参数字符串确定但是我收到此错误时执行该命令:

翻译:价值不能为空.参数名称:类型.建议使用New关键字来创建对象实例 我不知道在哪里放置New关键字.我试过CommandParameter但它不会工作.任何的想法?谢谢
更新3
在这里收到所有建议和帮助以及大量工作之后,这里是我的最终导航菜单和我的应用程序界面的基础.

Bra*_*NET 20
我不确定你需要一个单独的"导航"视图模型,你可以很容易地把它放到主要的.无论哪种方式:
要分离您的"子"视图,我会在"主"视图中使用简单的ContentPresenter:
<ContentPresenter Content="{Binding CurrentView}"/>
Run Code Online (Sandbox Code Playgroud)
实现支持属性的最简单方法是使其成为一个UserControl,尽管有人会认为这样做会违反MVVM(因为ViewModel现在依赖于"View"类).你可以把它变成一个对象,但你失去了一些类型的安全性.在这种情况下,每个视图都是UserControl.
要在它们之间切换,您将需要某种选择控制.我以前用单选按钮完成了这个,你就像这样绑定它们:
<RadioButton Content="View 1" IsChecked="{Binding Path=CurrentView, Converter={StaticResource InstanceEqualsConverter}, ConverterParameter={x:Type views:View1}"/>
Run Code Online (Sandbox Code Playgroud)
转换器非常简单,在"转换"中它只检查当前控件是否是参数的类型,在"ConvertBack"中它返回参数的新实例.
public class InstanceEqualsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (parameter as Type).IsInstanceOfType(value);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (bool)value ? Activator.CreateInstance(parameter as Type) : Binding.DoNothing;
}
}
Run Code Online (Sandbox Code Playgroud)
与组合框或其他选择对照的结合将遵循类似的模式.
当然你也可以使用DataTemplates(带有选择器,遗憾的是我以前没做过的事情)并使用合并的字典将它们加载到你的资源中(允许单独的XAML).我个人更喜欢用户控制路线,选择哪个最适合你!
这种方法是"一次一个视图".转换为多个视图相对容易(您的UserControl成为用户控件的集合,在转换器中使用.Contains等).
要使用按钮执行此操作,我将使用命令并利用CommandParameter.
XAML按钮看起来像:
<Button ... Command={Binding SwitchViewsCommand} CommandParameter={x:Type local:ClientsView}/>
Run Code Online (Sandbox Code Playgroud)
然后你有一个委托命令(这里的教程),它从转换器运行激活器代码:
public ICommand SwitchViewsCommand {get; private set;}
public MainViewModel()
{
SwitchViewsCommand = new DelegateCommand((parameter) => CurrentView = Activator.CreateInstance(parameter as Type));
}
Run Code Online (Sandbox Code Playgroud)
这是我的头脑,但应该非常接近.让我知道事情的后续!
如果我提供任何进一步的信息,请告诉我!
更新:
回答你的疑虑:
是的,每次按下按钮都会创建一个新的视图实例.您可以通过保存Dictionary<Type, UserControl>具有预先创建的视图和索引的方法来轻松解决此问题.就此而言,您可以使用a Dictonary<String, UserControl>并使用简单的字符串作为转换器参数.缺点是你的ViewModel与它可以呈现的各种视图紧密耦合(因为它必须填充所述的Dictionary).
只要没有其他人拥有对它的引用(想想它注册的事件处理程序),该类就应该被处理掉.
正如您所指出的,一次只创建一个视图,因此您不必担心内存.当然,您正在调用构造函数,但这并不昂贵,特别是在现代计算机上,我们往往有足够的CPU时间.与往常一样,性能问题的答案是"基准测试",因为只有您可以访问预期的部署目标和整个源,才能看到实际上最佳的性能.