WPF使用MVVM模式浏览视图 [英] WPF Navigate through views using MVVM pattern

查看:110
本文介绍了WPF使用MVVM模式浏览视图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用MVVM模式构建我的第一个WPF.在这个社区的帮助下,我设法创建了我的模型,我的第一个ViewModel和视图.现在,我想为设计基本应用程序布局界面的应用程序增加一些复杂性.我的想法是至少拥有2个子视图和一个主视图,并在多个XAML上将它们分开:

I'm building my first WPF using MVVM pattern. With the help of this community, I manage to create my Model, my first ViewModel and view. Now I want to add some complexity to the app designing the basic application layout interface. My idea is to have at least 2 child views and one main view and separate them on several XAML:

  • Main.XAML
  • Products.XAML
  • Clients.XAML
  • Main.XAML
  • Products.XAML
  • Clients.XAML

Main将具有一个菜单和一个用于加载子视图(产品和客户端)的空间.现在,按照MVVM模式,视图之间的所有导航逻辑都应写在ViewModel上.所以我的想法是要有4个ViewModel:

Main will have a menu and a space to load child views (Products and Clients). Now following MVVM pattern all the navigation logic between views should be write on a ViewModel. So mi idea is to have 4 ViewModels:

  • MainViewModel
  • ProductsViewModel
  • ClientsViewModel
  • NavigationViewModel
  • MainViewModel
  • ProductsViewModel
  • ClientsViewModel
  • NavigationViewModel

那么NavigationViewModel应该包含子viewmodel的集合?主动的视图模型对吗?

So NavigationViewModel should contain a collection of child viewmodels? and an active viewmodel is that right?

所以我的问题是:

1)如何使用MVVM模式在主视图上加载不同的视图(产品,客户)?

1) How can I load different views (Products, Clients) on Main view using MVVM pattern?

2)如何实现导航viewModel?

2) How do I implement navigation viewModel?

3)如何控制打开或活动视图的最大数量?

3) How can I control the max number of open or active views?

4)如何在打开的视图之间切换?

4) How can I switch between open views?

我一直在进行大量搜索和阅读,找不到带有WPF的MVVM导航的简单工作示例,该示例在主视图中加载了多个视图.当时很多:

I have been doing a lot of search and reading and couldn't find any simple working example of MVVM navigation with WPF that loads multiple views inside a main view. Many of then:

1)使用我现在不想使用的外部工具包.

1) Use external toolkit, which I don't want to use right now.

2)将用于创建所有视图的所有代码放在一个XAML文件中,这似乎不是一个好主意,因为我需要实现近80个视图!

2) Put all the code for creating all the views in a single XAML file, which doesn't seems like a good idea because I need to implement near 80 views!

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

I'm in the right path here? Any help, especially with some code will be appreciated.

更新

因此,我遵循@LordTakkera的建议构建了一个测试项目,但遇到了麻烦.这是我的解决方案的样子:

So, I build a test project following @LordTakkera advices, but get stuck. This is how my solution looks like:

我创建:

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

  • Two Models (Clients and Products)

一个MainWindow和两个wpf用户控件(客户端和产品)XAML.

One MainWindow and two wpf user controls(Clients and Products) XAML.

三个ViewModel(客户端,产品和主ViewModel)

Three ViewModels (Clients, Products and Main ViewModel)

然后,我在每个视图上将dataContext设置为对应的viewModel.之后,我用这样的ContentPresenter创建MainWindow并将其绑定到viewmodel的属性.

Then I set dataContext on each view to corresponding viewModel. After that I create MainWindow with the ContentPresenter like this and bind it to a property of the 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:

And also this is viewmodel from 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");
                }
            }
        }

    }

因此,默认情况下,此负载会在客户端视图中显示(看起来是这样!)

So this load by default clients view and looks like this (which is just right!):

所以我想我需要一种将左侧按钮与某个viemodel关联起来的方法,然后将它们与Main viewModel的CurrentView属性绑定.我该怎么办?

So I suppose I need a way to relate the buttons on the left, with a certain viemodel and then bind them with CurrentView Property of Main viewModel. How can I do that?

UPDATE2

根据@LordTakkera的建议,我以这种方式修改了我的主viewModel:

According to @LordTakkera advice I modify my main viewModel this way:

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,但是我认为它的工作方式相同.当我按下按钮并输入类型参数字符串就可以了,但是我得到了这个错误:

I use RelayCommand instead of DelegateCommand but I think it works the same way. The command is executed when I hit the buttons and the type parameter string its ok but i get this error:

翻译:值不能为空.参数名称:类型.建议使用New关键字创建对象实例 我不知道将New关键字放在哪里.我尝试使用CommandParameter,但无法正常工作.任何的想法?谢谢

Translation: Value cannot be null. Parameter name: type. Suggestion use New keyword to create object instance I don't know where to put the New keyword. I have try on CommandParameter but it wont work. Any idea? Thanks

更新3

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

After all the advices and help received here, and a lot of work, here is my final navigation menu and the base for my application interface.

推荐答案

我不确定您是否需要单独的导航"视图模型,您可以轻松地将其放入主视图中.无论哪种方式:

I'm not sure you need a separate "navigation" view model, you could easily put it into the main. Either way:

要分离您的子"视图,我将在您的主"视图上使用一个简单的ContentPresenter:

To separate your "child" views, I would use a simple ContentPresenter on your "main" view:

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

实现backing属性的最简单方法是将其设置为UserControl,尽管有人认为这样做违反了MVVM(因为ViewModel现在依赖于"View"类).您可以将其设为对象,但是会丢失某些类型安全性.在这种情况下,每个视图都将是一个UserControl.

The easiest way to implement the backing property is to make it a UserControl, though some would argue that doing so violates MVVM (since the ViewModel is now dependent on a "View" class). You could make it an object, but you lose some type safety. Each view would be a UserControl in this case.

要在它们之间切换,您将需要某种选择控件.之前,我已经使用单选按钮完成过此操作,您可以像这样绑定它们:

To switch between them, you are going to need some sort of selection control. I've done this with radio buttons before, you bind them like so:

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

转换器非常简单,在"Convert"中,它仅检查当前控件是否是参数的类型,在"ConvertBack"中,它返回参数的新实例.

The converter is pretty simple, in "Convert" it just checks if the current control is a type of the parameter, in "ConvertBack" it returns a new instance of the parameter.

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

绑定到组合框或其他选择控件将遵循类似的模式.

Binding to a combobox or other selection control would follow a similar pattern.

当然,您也可以使用DataTemplates(带有选择器,不幸的是我之前没有做过),然后使用合并的字典将它们加载到您的资源中(允许使用单独的XAML).我个人更喜欢用户控制路线,选择最适合您的路线!

Of course you could also use DataTemplates (with a selector, unfortunately not something I have done before) and load them into your resources using merged dictionaries (allowing separate XAML). I personally prefer the user control route, pick which is best for you!

这种方法是一次查看一个视图".转换为多个视图相对容易(您的UserControl成为用户控件的集合,可以使用.Contains包含在转换器中等).

This approach is "one view at a time". It would be relatively easy to convert to multiple views (your UserControl becomes a collection of user controls, use .Contains in the converter etc.).

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

To do this with buttons, I would use commands and take advantage of the CommandParameter.

按钮XAML如下所示:

The button XAML would look like:

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

然后您有一个委托命令(教程此处)从转换器运行激活程序代码:

Then you have a delegate command (tutorial here) that runs the activator code from the converter:

public ICommand SwitchViewsCommand {get; private set;}

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

那是我的头顶,但应该非常接近.让我知道怎么回事!

That is off the top of my head, but should be pretty close. Let me know how it goes!

让我知道是否提供进一步的信息!

Let me know if I provide any further information!

更新:

要回答您的问题:

  1. 是的,每次您按下按钮时,都会创建一个新的视图实例.您可以通过持有已预先创建视图并在其中建立索引的Dictionary<Type, UserControl>轻松解决此问题.为此,您可以使用Dictonary<String, UserControl>并将简单的字符串用作转换器参数.缺点是ViewModel与它可以呈现的视图紧密相关(因为必须填充所述Dictionary).

  1. Yes, each time you push the button a new instance of the view is created. You could easily fix this by holding a Dictionary<Type, UserControl> that has pre-created views and index into it. For that matter, you could use a Dictonary<String, UserControl> and use simple strings as the converter parameters. The disadvantage is that your ViewModel becomes tightly coupled to the kinds of views it can present (since it has to populate said Dictionary).

该类应该被处置,只要没有其他人对该类进行引用(请考虑为其注册的事件处理程序).

The class should get disposed, as long as no one else holds a reference to it (think event handlers that it registered for).

如您所指出的,一次只创建一个视图,因此您不必担心内存.当然,您是在调用构造函数,但这并不昂贵,特别是在现代计算机上,我们往往有大量的CPU时间可以用来节省时间.与往常一样,对性能问题的回答是对它进行基准测试",因为只有您才能访问预期的部署目标和整个资源,以了解实际执行效果最佳的情况.

As you point out, only one view is created at a time so you shouldn't need to worry about memory. You are, of course, calling a constructor but that isn't THAT expensive, particularly on modern computers where we tend to have plenty of CPU time to spare. As always, the answer to performance questions is "Benchmark it" because only you have access to the intended deployment targets and entire source to see what actually performs the best.

这篇关于WPF使用MVVM模式浏览视图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆