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

查看:35
本文介绍了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
  • 产品.XAML
  • 客户端.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:

  • 主视图模型
  • 产品视图模型
  • ClientsViewModel
  • 导航视图模型

那么 NavigationViewModel 应该包含子视图模型的集合吗?和一个活动的视图模型是对的吗?

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) 如何实现导航视图模型?

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 并将其绑定到视图模型的属性.

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 的视图模型:

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?

更新2

根据@LordTakkera 的建议,我以这种方式修改了主视图模型:

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}"/>

实现支持属性的最简单方法是将其设为 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 轻松解决此问题.就此而言,您可以使用 Dictonary 并使用简单的字符串作为转换器参数.缺点是您的 ViewModel 与它可以呈现的视图类型紧密耦合(因为它必须填充所述字典).

  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天全站免登陆