WPF使用MVVM模式浏览视图
我正在使用MVVM模式构build我的第一个WPF。 在这个社区的帮助下,我设法创build了我的模型,我的第一个ViewModel和视图。 现在我想为应用程序devise基本的应用程序布局界面增加一些复杂性。 我的想法是至less有两个子视图和一个主视图,并将它们分开放在几个XAML上:
- Main.XAML
- Products.XAML
- Clients.XAML
Main将有一个菜单和一个空间来加载子视图(产品和客户端)。 现在,遵循MVVM模式,视图之间的所有导航逻辑应该被写在ViewModel上。 所以mi想法是有4个ViewModels:
- MainViewModel
- ProductsViewModel
- ClientsViewModel
- NavigationViewModel
那么NavigationViewModel应该包含一系列的子视图模型? 一个活跃的视图模型是可行的?
所以我的问题是:
1)如何使用MVVM模式在主视图中加载不同的视图(产品,客户端)?
2)我如何实现导航视图模型
3)如何控制打开或活动视图的最大数量?
4)如何在打开的视图之间切换?
我一直在做大量的search和阅读,找不到任何简单的MVVM导航WPF导航,在主视图中加载多个视图的例子。 其中许多是:
1)使用外部工具包,我现在不想使用。
2)将创build所有视图的所有代码放在一个单独的XAML文件中,这似乎不是一个好主意,因为我需要实现近80个视图!
我在这里的道路上? 任何帮助,特别是一些代码将被折衷。 谢谢!
UPDATE
所以,我build立了一个@LordTakkera的build议,但卡住了testing项目。 这是我的解决scheme的样子:
我创造:
两种模式(客户和产品)
一个MainWindow和两个wpf用户控件(客户和产品)XAML。
三个ViewModel(客户,产品和主ViewModel)
然后我把每个视图上的dataContext设置为相应的viewModel。 之后,我使用ContentPresenter创buildMainWindow,并将其绑定到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>
而这也是来自MainWindow的视图模型:
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"); } } } }
所以这个默认的客户端加载视图,看起来像这样(这是正确的!):
所以我想我需要一种方法来将左边的button与某个viemodel相关联,然后将它们与Main viewModel的CurrentView属性绑定。 我怎样才能做到这一点? 谢谢!
UPDATE2
根据@LordTakkera的build议我修改我的主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"); } } } }
我使用RelayCommand而不是DelegateCommand,但我认为它以同样的方式工作。 当我点击button和types参数string它的确定,但我得到这个错误时执行该命令:
翻译: 值不能为空。 参数名称:types。 build议使用New关键字来创build对象实例我不知道把New关键字放在哪里。 我已经尝试CommandParameter,但它不会工作。 任何想法? 谢谢
更新3
当所有的build议和帮助在这里接受和大量的工作,这里是我最后的导航菜单和我的应用程序接口的基础。
感谢所有的帮助。
我不确定你需要一个单独的“导航”视图模型,你可以很容易地把它放在主要。 无论哪种方式:
要分离你的“孩子”的意见,我会在你的“主”视图上使用一个简单的ContentPresenter:
<ContentPresenter Content="{Binding CurrentView}"/>
最简单的方法来实现支持属性是使其成为一个UserControl
,但有人会认为这样做违反了MVVM(因为ViewModel现在依赖于“视图”类)。 你可以使它成为一个对象,但你失去了一些types的安全。 在这种情况下,每个视图都是一个UserControl。
要切换它们,你将需要某种select控制。 我之前用单选button做了这个,你把它们绑定成这样:
<RadioButton Content="View 1" IsChecked="{Binding Path=CurrentView, Converter={StaticResource InstanceEqualsConverter}, ConverterParameter={x:Type views:View1}"/>
转换器是非常简单的,在“转换”它只是检查当前控件是否是参数types,在“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; } }
绑定到combobox或其他select控件将遵循类似的模式。
当然你也可以使用DataTemplates(带有一个select器,不幸的是我之前没做过),并使用合并字典(允许单独的XAML)将它们加载到资源中。 我个人更喜欢用户控制路线,挑哪个最适合你!
这种方法是“一次一个观点”。 转换为多个视图相对容易(您的UserControl成为用户控件的集合,在转换器中使用.Contains等)。
要做到这一点与button,我会使用命令,并利用CommandParameter。
buttonXAML将如下所示:
<Button ... Command={Binding SwitchViewsCommand} CommandParameter={x:Type local:ClientsView}/>
然后你有一个委托命令(教程在这里 ),从转换器运行激活码:
public ICommand SwitchViewsCommand {get; private set;} public MainViewModel() { SwitchViewsCommand = new DelegateCommand((parameter) => CurrentView = Activator.CreateInstance(parameter as Type)); }
这是我的头顶,但应该是非常接近。 让我知道事情的后续!
让我知道,如果我提供任何进一步的信息!
更新:
回答您的疑虑:
-
是的,每次按下button,都会创build一个新的视图实例。 你可以通过持有预先创build了视图和索引的
Dictionary<Type, UserControl>
来轻松解决这个问题。 对于这个问题,你可以使用Dictonary<String, UserControl>
并使用简单的string作为转换参数。 缺点是你的ViewModel变得紧密耦合到它可以呈现的种类(因为它必须填充所述字典)。 -
只要没有其他人持有对它的引用(认为它注册的事件处理程序),该类就应该被丢弃。
-
正如你所指出的,一次只创build一个视图,所以你不需要担心内存。 当然,你是在调用一个构造函数,但这并不是那么昂贵,特别是在现代计算机上,我们往往有足够的CPU时间来备用。 与往常一样,性能问题的答案是“基准testing”,因为只有您有权访问预期的部署目标和整个源代码,才能看到实际执行的最佳效果。
恕我直言,最好的select是使用MVVM框架(PRISM,MMVM Light,Chinch等),因为导航已经实现。 如果你想创build自己的导航 – 尝试DataTemplate。