WPF使用MVVM模式浏览视图

时间:2014-04-01 17:48:15

标签: c# wpf xaml mvvm

我正在使用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)如何在打开的视图之间切换?

我一直在进行大量的搜索和阅读,并且无法找到任何使用WPF进行MVVM导航的简单工作示例,该示例在主视图中加载多个视图。许多人:

1)使用外部工具包,我现在不想使用它。

2)将所有视图的所有代码放在一个XAML文件中,这看起来不是一个好主意,因为我需要实现近80个视图!

我走在正确的道路上?任何帮助,特别是一些代码将不胜感激。

更新

所以,我在@LordTakkera建议之后建立了一个测试项目,但是卡住了。这就是我的解决方案的样子: Solution

我创建:

  
      
  • 两种型号(客户和产品)

  •   
  • 一个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>

这也是来自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");
                }
            }
        }

    }

所以这个默认客户端加载视图并且看起来像这样(这是正确的!):

Current state

所以我想我需要一种方法来将左侧的按钮与某个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");
                }
            }
        }
    }

我使用RelayCommand而不是DelegateCommand,但我认为它的工作方式相同。当我按下按钮并且类型参数字符串确定但是我收到此错误时执行该命令:

Error

翻译:价值不能为空。参数名称:类型。建议使用New关键字创建对象实例 我不知道在哪里放置New关键字。我试过CommandParameter但它不会工作。任何的想法?感谢

更新3

在这里收到所有的建议和帮助,以及很多工作之后,这是我的最终导航菜单和我的应用程序界面的基础。

Capture 1 Capture 2

2 个答案:

答案 0 :(得分:20)

我不确定您是否需要单独的导航&#34;查看模型,您可以轻松地将其放入主体。无论哪种方式:

分开你的孩子&#34;观点,我会在你的&#34; main&#34;上使用一个简单的ContentPresenter。视图:

<ContentPresenter Content="{Binding CurrentView}"/>

实现支持属性的最简单方法是使其成为UserControl,尽管有人会认为这样做会违反MVVM(因为ViewModel现在依赖于&#34; View&#34;类) 。你可以把它变成一个对象,但你失去了一些类型的安全性。在这种情况下,每个视图都是UserControl。

要在它们之间切换,您需要某种选择控制。我以前用单选按钮完成了这个操作,你就像这样绑定它们:

<RadioButton Content="View 1" IsChecked="{Binding Path=CurrentView, Converter={StaticResource InstanceEqualsConverter}, ConverterParameter={x:Type views:View1}"/>

转换器非常简单,转换&#34;它只是检查当前控件是否是参数的一种类型,在&#34; ConvertBack&#34;它返回参数的新实例。

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

与组合框或其他选择对照的结合将遵循类似的模式。

当然你也可以使用DataTemplates(带有选择器,遗憾的是我之前没做过的事情)并使用合并的字典将它们加载到你的资源中(允许单独的XAML)。我个人更喜欢用户控制路线,选择哪种方式最适合你!

这种方法一次只有一个视图&#34;。转换为多个视图相对容易(您的UserControl成为用户控件的集合,在转换器中使用.Contains等。)。

要使用按钮执行此操作,我将使用命令并利用CommandParameter。

按钮XAML看起来像:

<Button ... Command={Binding SwitchViewsCommand} CommandParameter={x:Type local:ClientsView}/>

然后你有一个委托命令(教程here)来运行转换器中的激活器代码:

public ICommand SwitchViewsCommand {get; private set;}

public MainViewModel()
{
    SwitchViewsCommand = new DelegateCommand((parameter) => CurrentView = Activator.CreateInstance(parameter as Type));
}

这是我的头脑,但应该非常接近。让我知道它是怎么回事!

如果我提供任何进一步的信息,请告诉我们!

<强>更新

回答您的疑虑:

  1. 是的,每按一次按钮,都会创建一个新的视图实例。您可以通过保留已预先创建视图和索引的Dictionary<Type, UserControl>来轻松解决此问题。就此而言,您可以使用Dictonary<String, UserControl>并使用简单字符串作为转换器参数。缺点是你的ViewModel与它可以呈现的各种视图紧密耦合(因为它必须填充所述的字典)。

  2. 只要没有其他人持有对它的引用(想想它注册的事件处理程序),该类就应该被处理掉。

  3. 正如您所指出的,一次只创建一个视图,因此您不必担心内存。当然,您正在调用构造函数,但这并不昂贵,特别是在现代计算机上,我们往往有足够的CPU时间。与往常一样,性能问题的答案是&#34;基准测试&#34;因为只有您可以访问预期的部署目标和整个源,才能看到实际上表现最佳的内容。

答案 1 :(得分:0)

恕我直言,最好的选择是使用MVVM框架(PRISM,MVVM Light,Chinch等),因为导航已经实现。如果您想创建自己的导航 - 请尝试使用DataTemplate。