WPF/MVVM Quick Start Tutorial - WPF/MVVM 快速入门教程 -原文,翻译及一点自己的补充

学习自 WPF MVVM快速入门 2012-10-11

WPF/MVVM Quick Start Tutorial

WPF/MVVM 快速入门教程

Introduction

介绍

Assuming that you have a decent understanding of C#, getting started in WPF isn't too difficult. I started looking at WPF a while ago, and didn't find many helpful MVVM tutorials. Hopefully this article addresses that.

假设您对 C# 有很好的了解,WPF 入门并不太难。 前段时间开始看WPF,没找到很多有用的MVVM教程。 希望这篇文章能解决这个问题。

As with learning any new technology, you get the benefit of hindsight. From my perspective, almost every tutorial on WPF I've come across is inadequate for one of several reasons:

与学习任何新技术一样,您会受益于事后诸葛亮。 从我的角度来看,由于以下几个原因之一,我遇到的几乎所有关于 WPF 的教程都不合适:

  • The example is all in XAML.
  • The example glosses over the key facts that would actually make your life easier.
  • The example tries to show off WPF/XAML's capabilities with lots of pointless effects that aren't helping you.
  • The example uses classes that have properties that appear far too similar to framework keywords and classes, and are therefore difficult to identify in the (XAML) code as being user defined (the ListBox GroupStyle's Name attribute is a complete headache for novices).
  • 该示例全部采用 XAML。
  • 这个例子掩盖了实际上会让你的生活更轻松的关键事实。
  • 该示例试图通过许多对您没有帮助的毫无意义的效果来展示 WPF/XAML 的功能。
  • 该示例使用的类的属性与框架关键字和类非常相似,因此很难在 (XAML) 代码中将其识别为用户定义的(ListBox GroupStyle 的 Name 属性对于新手来说是一个完全头疼的问题)。

补充

这几点注意是我决定认真看一下这个快速教程的最主要原因,尤其是第3,4点。说到我的心里了。很多介绍都是浅尝则止,比如下面我补充的关于公共命令类(也就是Mvvm Light的RelayCommand)为什么是这种写法,我就没看到有介绍的比较详细的。关于第四点,最常见于Mvvm模式下一些原本不是依赖属性的控件属性,需要自己补充的用法,点名PasswordBox的Passwork属性,绝大部分教程自定义的附加属性也叫Passwork,加上wpf关于附加属性注册的约定命令,根本分不清哪些是控件自己的属性,那些是自己定义的属性,一团乱。

当然这些因素官方文档都解释的很详细,理解不上去最根本的原因还是基础不扎实,但这些快速介绍明明可以换一个自定义的名称让教程看上去更清晰,却非要使用一样的,看的时候还是很不爽。

End

So to address this, I've written this based on what I would have liked to have found as the #1 hit on Google after typing 'WPF Tutorial'. This article may not be 100% correct, or even do things 'the one true way', but it will illustrate the main points that I wish I had found in one place 6 months ago.

因此,为了解决这个问题,我根据我希望在键入“WPF 教程”后在 Google 上找到的排名第一的内容编写了这篇文章。 这篇文章可能不是 100% 正确,甚至不是“以一种正确的方式”做事,但它会说明我希望在 6 个月前在一个地方找到的要点。

I will quickly introduce some topics, then show an example that explains or demonstrates each point. Accordingly, I haven't really attempted to make the GUIs pretty, that's not the point of this article (see the bullet points above).

我将快速介绍一些主题,然后展示一个示例来解释或演示每一点。 因此,我并没有真正尝试使 GUI 变得漂亮,这不是本文的重点(请参阅上面的要点)。

As this tutorial is quite long, I've elided quite a lot of code for brevity, so please download the attached zip file, and look at the examples (.NET 4.0/VS2010). Each example builds on the previous one.

由于本教程很长,为了简洁起见,我省略了很多代码,因此请下载随附的 zip 文件,并查看示例 (.NET 4.0/VS2010)。 每个示例都建立在前一个示例的基础上。

The Basic

基础知识

  1. The most important thing about WPF is data binding. In short, you have some data, typically in a collection of some sort, and you want to display it to the user. You can 'bind' your XAML to the data.

  2. WPF has two parts, the XAML which describes your GUI layout and effects, and the code-behind that is tied to the XAML.

  3. The neatest and probably most reusable way to organise your code is to use the 'MVVM' pattern: Model, View, ViewModel. This has the aim of ensuring that your View contains minimal (or no) code, and should be XAML-only.

  4. WPF 最重要的一点是数据绑定。 简而言之,您有一些数据,通常是某种类型的集合,并且您希望将其显示给用户。 您可以将 XAML 与数据“绑定”。

  5. WPF 有两个部分,XAML 描述您的 GUI 布局和效果,以及与 XAML 相关的代码隐藏。

  6. 组织代码的最简洁且可能是最可重用的方法是使用“MVVM”模式:模型、视图、视图模型。 这样做的目的是确保您的视图包含最少(或不包含)代码,并且应该仅包含 XAML。

The Key Points You Need to Know

你需要了解的关键点

  1. The collection you should use to hold your data is the ObservableCollection<>. Not a list, not a dictionary, but an ObservableCollection. The word 'Observable' is the clue here: the WPF window needs to be able to 'observe' your data collection. This collection class implements certain interfaces that WPF uses.

  2. Every WPF control (including 'Window's) has a 'DataContext' and Collection controls have an 'ItemsSource' attribute to bind to.

  3. The interface 'INotifyPropertyChanged' will be used extensively to communicate any changes in the data between the GUI and your code.

  4. 您应该用来保存数据的集合是 ObservableCollection<>。 不是列表,也不是字典,而是 ObservableCollection。 “可观察”一词是此处的线索:WPF 窗口需要能够“观察”您的数据集合。 此集合类实现 WPF 使用的某些接口。

  5. 每个 WPF 控件(包括“Window”)都有一个“DataContext”,集合控件有一个要绑定到的“ItemsSource”属性。

  6. 接口“INotifyPropertyChanged”将广泛用于在 GUI 和您的代码之间传达数据中的任何更改。

Example 1:Doing It(mostly) Wrong

The best way to start is an example. We will start with a Song class, rather than the usual Person class. We can arrange songs into Albums, or one large collection, or by Artist. A simple Song class would be as follows:

最好的开始方式是一个例子。 我们将从 Song 类开始,而不是通常的 Person 类。 我们可以将歌曲编排成专辑,或一个大集合,或按艺术家。 一个简单的 Song 类如下:

public class Song
{
    string _artistName;
    string _songTitle;

    public string ArtistName
    {
        get { return _artistName; }
        set { _artistName = value; }
    }

    /// The song title.
    public string SongTitle
    {
        get { return _songTitle; }
        set { _songTitle = value; }
    }
}

In WPF terminology, this is our 'Model'. The GUI is our 'View'. The magic that data binds them together is our 'ViewModel', which is really just an adapter that turns our Model into something that the WPF framework can use. So just to reiterate, this is our 'Model'.

在 WPF 术语中,这是我们的“模型”。 GUI 是我们的“视图”。 数据将它们绑定在一起的神奇之处在于我们的“ViewModel”,它实际上只是一个适配器,可以将我们的模型变成 WPF 框架可以使用的东西。 所以重申一下,这是我们的“模型”

Since we've created a Song as a reference type, copies are cheap and light on memory. We can create our SongViewModel quite easily. What we need to consider first is, what are we going to (potentially) display? Suppose we just care about the song's artist name, not the song title, then the SongViewModel could be defined as follows:

由于我们创建了一个Song作为引用类型,所以副本便宜且占用内存少。 我们可以很容易地创建我们的“SongViewModel”。 我们首先需要考虑的是,我们要(可能)展示什么? 假设我们只关心 song 的艺术家姓名,而不是 song title,那么 SongViewModel 可以定义如下:

public class SongViewModel
{
    Song _song;

    public Song song
    {
        get
        {
            return _song;
        }
        set
        {
            _song = value;
        }
    }

    public string ArtistName
    {
        get { return song.ArtistName; }
        set { song.ArtistName = value; }
    }
}

Except that this isn't quite correct. Since we're exposing a property in our ViewModel, we would obviously want a change to the song's artist name made in the code to be automatically shown in the GUI, and vice versa:

除了这并不完全正确。 由于我们在ViewModel中公开了一个属性,我们显然希望在代码中对song的艺术家姓名进行更改以在GUI中自动显示,反之亦然:

SongViewModel song = new SongViewModel();
// 这里原文是SongViewModel song = ...;可能但是单纯的伪代码吧
// .. enable the databinding .. 启用数据绑定
// change the name 改变ArtistName
song.ArtistName = "NewName";
// the gui shoule change 视图上应该发生变化

Notice that in all the examples here, we create our view model declaratively, i.e., we do this in the XAML:

请注意,在此处的所有示例中,我们声明式 创建了我们的视图模型,即,我们在 XAML 中执行此操作:

<Window x:Class="WPFDemo.MVVMTutorial.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPFDemo.MVVMTutorial"
        mc:Ignorable="d"
        Title="Window1" Height="450" Width="800">
    <Window.DataContext>
        <!-- Declaratively create an instance of our SongViewModel -->
        <!--声明性地创建一个 SongViewModel 的实例-->
        <local:SongViewModel></local:SongViewModel>
    </Window.DataContext>
    <Grid>
        
    </Grid>
</Window>

This is equivalent to doing this in your code-behind MainWindow.cs:

这相当于在您的代码隐藏 MainWindow.cs 中执行此操作:

public partial class Window1 : Window
{
    SongViewModel _viewModel = new SongViewModel();

    public Window1()
    {
        InitializeComponent();
        base.DataContext = _viewModel;
    }
}

And removing your DataContext element in the XAML:

并删除 XAML 中的“DataContext”元素:

<Window x:Class="WPFDemo.MVVMTutorial.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPFDemo.MVVMTutorial"
        mc:Ignorable="d"
        Title="Window1" Height="450" Width="800">
    <!-- no data context -->
    <!-- 不声明DataContext -->
    <Grid>
        
    </Grid>
</Window>

原文这里直接贴窗体了。其实都蛮简单的。直接把xaml代码贴上也可以

<Window x:Class="WPFDemo.MVVMTutorial.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPFDemo.MVVMTutorial"
        mc:Ignorable="d"
        Title="Window1" Height="211.173" Width="356.285" FontSize="18">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        
        <TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Text="Example1 this is dosen't work!" VerticalAlignment="Center"></TextBlock>
        <TextBlock Grid.Row="1" Grid.Column="0"  Text="Artist:" VerticalAlignment="Center"></TextBlock>
        <TextBlock Grid.Row="1" Grid.Column="1"  Text="Unkonwn" VerticalAlignment="Center"></TextBlock>
        <Button Grid.Row="2" Grid.Column="1" Content="Update Artist Name" Click="Button_Click"></Button>
    </Grid>    
</Window>

End

Clicking the button does not update anything, because we have not completely implemented data binding.

点击按钮不会更新任何东西,因为我们还没有完全实现数据绑定

Data Binding

Remember I said at the start that I would choose a property that stands out. In this example, we want to display the ArtistName. I chose this name because it is NOT the same as any WPF attribute. There are a countless number of examples on the web that choose a Person class and then a Name attribute (the Name attribute exists on multiple .NET WPF classes). Perhaps the authors of the articles just don't realise that this is particularly confusing for beginners (who are, curiously enough, the target audience of these articles).

记得我一开始说过我会选择一个突出的属性。 在这个例子中,我们想要显示ArtistName。 我选择这个名称是因为它与任何 WPF 属性 NOT 相同。 网络上有无数的示例选择一个 Person 类,然后选择一个 Name 属性(Name 属性存在于多个 .NET WPF 类中)。 也许这些文章的作者只是没有意识到这对于初学者来说尤其令人困惑(奇怪的是,这些文章的目标受众)。

There are dozens of other articles about data binding out there, so I won't cover it here. I hope the example is so trivial that you can see what is going on.

还有很多其他关于数据绑定的文章,所以我不会在这里介绍。 我希望这个例子非常简单,你可以看到发生了什么。

To bind to the ArtistName property on our SongViewModel, we simply do this in the MainWindow.xaml:

要绑定到 SongViewModel 上的 ArtistName 属性,我们只需在 MainWindow.xaml 中执行以下操作:

<TextBlock Grid.Row="1" Grid.Column="1"  Text="{Binding ArtistName}" VerticalAlignment="Center"></TextBlock>

The 'Binding' keyword binds the content of the control, in this case a Label, to the property 'ArtistName' of the object returned by DataContext. As you saw above, we set our DataContext to an instance of SongViewModel, therefore we are effectively displaying _songViewModel.ArtistName in the Label.

“Binding”关键字将控件的内容(在本例中为“Label”)绑定到由“DataContext”返回的对象的属性“ArtistName”。 正如你在上面看到的,我们将 DataContext 设置为 SongViewModel 的一个实例,因此我们在 Label 中有效地显示了 _songViewModel.ArtistName

这里需要补充一下。仅按照原文中的展示的代码是跑不起来的。因为SongViewModel没有初始化。会报错“未将对象引用到对象的实例。这个通过附带的示例程序就可以了解。原文提供的示例程序是在构造函数里初始化了。在窗体后台声明时初始化也行。如下:”

public partial class Window1 : Window
{
    SongViewModel _viewModel = new SongViewModel();

    public Window1()
    {
        InitializeComponent();

        _viewModel.song = new Song() { ArtistName = "111", SongTitle = "2222" };
        base.DataContext = _viewModel;
    }
    
    private void Button_Click(object sender, RoutedEventArgs e)
    {
        _viewModel.ArtistName = "ssss";
    }
}

End

Once again: clicking the button does not update anything, because we have not completely implemented data binding. The GUI is not receiving any notifications that the property has changed.

再说一遍:虽然按钮事件修改了ArtistName,但点击按钮不会更新任何东西,因为我们还没有完全实现数据绑定。 GUI 未收到任何关于属性已更改的通知

Example 2:INotifyPropertyChanged

This is where we have to implement the cunningly named interface: INotifyPropertyChanged. As it says, any class that implements this interface, notifies any listeners when a property has changed. So we need to modify our SongViewModel class a little bit more:

这是我们必须实现巧妙命名的接口的地方:INotifyPropertyChanged。 正如它所说,任何实现此接口的类都会在属性更改时通知任何侦听器。 所以我们需要稍微修改一下我们的SongViewModel类:

public class SongViewModel_Change1: INotifyPropertyChanged
{
    Song _song;

    public Song song
    {
        get
        {
            return _song;
        }
        set
        {
            _song = value;
        }
    }

    //public string ArtistName
    //{
    //    get { return song.ArtistName; }
    //    set { song.ArtistName = value; }
    //}
    public string ArtistName
    {
        get { return song.ArtistName; }
        set { 
            song.ArtistName = value;
            RaisePropertyChanged("ArtistName");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void RaisePropertyChanged(string propertyName)
    {
        // take a copy to prevent thread issues
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public SongViewModel_Change1()
    {
        _song = new Song { ArtistName = "Unknown", SongTitle = "Unknown" };
    }
}

There are several things now happening here. Firstly, we check to see if we are going to really change the property: this improves performance slightly for more complex objects. Secondly, if the value has changed, we raise the PropertyChanged event to any listeners.

现在这里发生了几件事情。 首先,我们检查是否真的要更改属性:这会稍微提高更复杂对象的性能。 其次,如果值发生了变化,我们会向任何侦听器引发 PropertyChanged 事件。

So now we have a Model, and a ViewModel. We just need to define our View. This is just our MainWindow:

所以现在我们有一个 Model 和一个 ViewModel。 我们只需要定义我们的View。 这只是我们的Window1

就是把待绑定的这句
<TextBlock Grid.Row="1" Grid.Column="1"  Text="{Binding ArtistName}" VerticalAlignment="Center"></TextBlock>
替换这句
<TextBlock Grid.Row="1" Grid.Column="1"  Text="Unkonwn" VerticalAlignment="Center"></TextBlock>
不全粘了

To test the databinding, we can take the traditional approach and create a button and wire to its OnClick event, so the XAML above has a button, and Click event, giving the code behind:

为了测试数据绑定,我们可以采用传统方法并创建一个按钮并连接到它的 OnClick 事件,因此上面的 XAML 有一个按钮和 Click 事件,给出了后面的代码:

// ViewModel记得改 SongViewModel_Change1 _viewModel = new SongViewModel_Change1();
int _count = 0;
private void Button_Click(object sender, RoutedEventArgs e)
{            
    ++_count;
    _viewModel.ArtistName = string.Format("2021 ({0})", _count);
}

This is ok, but it is not how we should use WPF: firstly, we have added our 'update artist' logic into our code-behind. It does not belong there. The Window class is concerned with windowing. The second problem is, suppose we want to move logic in the button click event to a different control, for example, making it a menu entry. It means we will be cut'n'pasting, and editing in multiple places.

这没问题,但这不是我们应该如何使用 WPF:首先,我们已将“更新艺术家”逻辑添加到我们的代码隐藏中。 它不属于那里。 Window 类与窗口有关。 第二个问题是,假设我们想将 button click 事件中的逻辑移动到不同的控件,例如,使其成为菜单项。 这意味着我们将在多个地方进行剪切和粘贴。

Here is our improved view, where clicking now works:

这是我们改进的视图,现在单击可以工作:

Example 3:Comands

Binding to GUI events is problematic. WPF offers you a better way. This is ICommand. Many controls have a Command attribute. These obey binding in the same way as Content and ItemsSource, except you need to bind it to a property that returns an ICommand. For the trivial example that we are looking at here, we just implement a trivial class called 'RelayCommand' that implements ICommand.

绑定到 GUI 事件是有问题的。 WPF 为您提供了更好的方法。这是“ICommand”。许多控件都有一个Command属性。它们以与ContentItemsSource相同的方式遵守绑定,除了您需要将其绑定到返回ICommand的属性。对于我们在这里看到的简单示例,我们只实现了一个名为“RelayCommand”的简单类,它实现了“ICommand”。

ICommand requires the user to define two methods: bool CanExecute, and void Execute. The CanExecute method really just says to the user, can I execute this command? This is useful for controlling the context in which you can perform GUI actions. In our example, we don't care, so we return true, meaning that the framework can always call our 'Execute' method. It could be that you have a situation where you have a command bound to button, and it can only execute if you have selected an item in a list. You would implement that logic in the 'CanExecute' method.

ICommand 需要用户定义两个方法:bool CanExecutevoid ExecuteCanExecute方法真的只是告诉用户,我可以执行这个命令吗?这对于控制可以在其中执行 GUI 操作的上下文很有用。在我们的示例中,我们不关心,所以我们返回 true,这意味着框架总是可以调用我们的 'Execute' 方法。可能是您有一个命令绑定到按钮的情况,并且只有在您选择了列表中的项目后才能执行。您将在 'CanExecute' 方法中实现该逻辑。

Since we want to reuse the ICommand code, we use the RelayCommand class that contains all the repeatable code we do not want to keep writing.

因为我们想重用ICommand代码,我们使用包含我们不想继续编写的所有可重复代码的RelayCommand类。

To show how easy it is to reuse the ICommand, we bind the Update Artist command to both a button and a menu item. Notice that we no longer bind to Button specific Click event, or Menu specific Click event.

为了展示重用ICommand是多么容易,我们将更新艺术家命令绑定到一个按钮和一个菜单项。请注意,我们不再绑定到 Button 特定的 Click 事件或 Menu 特定的 Click 事件。

这里啥也没贴,全在示例代码里了。这里也补充一下很重要的一点:为什么要使用公共类RelayCommand来创建命令实例?这个公共命令类中又为什么要用委托?

关于MVVM的Command绑定的讲解,99%都是直接从如示例的RelayCommand部分开始讲解。这非常让人奇怪,我在很多评论以及教学视频的弹幕中都看到了此类疑问?为什么就直接要创建这个公共命令类?这个类中的委托都委托到哪里去了?

我来尝试解答这个疑难。

俗话说的好,只有失去才懂得珍惜。想要了解为什么是这种用法,最好的办法就是完全不用。反其道而行,不要解耦,怎么耦合怎么来。就在ViewModel中直接编写命令,让这个命令离了这个ViewModel就完全失效。

首先是编写更新ArtistName的命令UpdateArtistNameCommandClass

public class UpdateArtistNameCommandClass : ICommand
{
    public event EventHandler CanExecuteChanged;
    public object o;

    public bool CanExecute(object parameter)
    {
        return true;
    }

    int _count = 0;
    public void Execute(object parameter)
    {
        SongViewModel_Change2_2 vm = o as SongViewModel_Change2_2;

        ++_count;
        vm.ArtistName = string.Format("2021 Command ({0})", _count);
    }

    public UpdateArtistNameCommand(object obj)
    {
        o = obj;
    }
}

其次,在ViewModel中声明,初始化

// 其他部分和之前都一样 省略……

public ICommand UpdateArtistNameCommand { get; set; }

public SongViewModel_Change2_2()
{
    _song = new Song { ArtistName = "Unknown", SongTitle = "Unknown" };
    UpdateArtistNameCommand = new UpdateArtistNameCommandClass(this);
}

// 其他部分和之前都一样 省略……

最后在Xaml中调用(这里也体现了UI与逻辑的分离。只要定义的Command名称是一样的,虽然后台完全不同但Xaml一点也不需要修改)。

<Button Grid.Row="2" Grid.Column="1" Content="Update Artist Name" Command="{Binding UpdateArtistNameCommand}"></Button>

这里的关键是如何在这个命令中操作ViewModel中的值(这个关键很关键,一定要理解)。

以上的例子中我选择在命令初始化时,直接将整个ViewModel传递进去。这样在命令中,无论想处理什么都行。除了这种方法,因为我们把ViewModel放在了Window的DataContent中供xaml使用,我们也可以通过命令参数的方式将DataContent中的ViewModel传递进命令处理中。如下:

<Window x:Class="WPFDemo.MVVMTutorial.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPFDemo.MVVMTutorial"
        mc:Ignorable="d"
        Title="Window1" Height="211.173" Width="356.285" FontSize="18" x:Name="w1">
    <!-- 一样的地方都省略了 -->
        <Button Grid.Row="2" Grid.Column="1" Content="Update Artist Name" Command="{Binding UpdateArtistNameCommand}" CommandParameter="{Binding ElementName=w1,Path=DataContext}"></Button>    
    <!-- 一样的地方都省略了 -->
</Window>

命令中,不再需要构造函数,根据参数parameter获取来源

SongViewModel_Change2 vm = parameter as SongViewModel_Change2;

ViewModel中命令的初始化也不用传值了。

UpdateArtistNameCommand = new UpdateArtistNameCommandClass();

如上所示,其实定义与执行命令与委托完全无关。

我们只需要

  1. 新建一个实现ICommand接口的命令,及其验证处理。public class UpdateArtistNameCommandClass : ICommand
  2. 在ViewModel中创建它的实例并初始化。public ICommand UpdateArtistNameCommand { get; set; };UpdateArtistNameCommand = new UpdateArtistNameCommandClass(this);
  3. 绑定至页面元素。Command="{Binding UpdateArtistNameCommand}"以及可能的参数传递。

就可以完全实现一个Command的MVVM式绑定。

但使用这种方式就会有很大的代码重复,因为每需要一个命令就要新建一个Command类。所以,都会使用公共类(如MVVM Light的RelayCommand和Prism的DelegateCommand等等MVVM的集合包)来避免新建Command的重复。

但使用公共类的弊端就是无法在其中直接操作ViewModel。也就是上面例子中的关键,它们其实都是一个问题,在ViewModel以外的位置处理ViewModel。

在这里,使用委托解决这个问题。初始化RelayCommand实例时,不需要像我上面的例子一样直接传进整个ViewModel。而是利用ViewModel包含该视图所有信息的特点,在ViewModel中编写好各命令所需要的处理并封装为处理函数。并将此处理函数传递给其对应的命令实例。

命令中,又以委托的形式调用这些处理函数。

这样,当某命令被触发执行时,就会被委托给已经在初始化实例时关联好的、ViewModel中的处理函数执行。

以上。

-End

Example 4:FrameWorks

By now, if you have read closely, you'll probably notice that a lot of this is just repetitive code: raising INPC, or creating commands. This is mostly boilerplate, and for INPC, we can move it to base class that we call 'ObservableObject'. For the RelayCommand class, we just move that into our .NET class library. This is how all of the MVVM frameworks you find on the web begin (Prism, Caliburn, etc.).

现在,如果您仔细阅读,您可能会注意到其中很多只是重复的代码:提高 INPC 或创建命令。 这主要是样板文件,对于 INPC,我们可以将它移到我们称为“ObservableObject”的基类中。 对于 RelayCommand 类,我们只需将其移动到我们的 .NET 类库中。 这就是您在网络上找到的所有 MVVM 框架(Prism、Caliburn 等)的开始方式。*

As far as the ObservableObject and RelayCommand classes are concerned, they are rather basic and are the inevitable result of refactoring. Unsurprisingly, these classes are practically the same as those by Josh Smith.

ObservableObjectRelayCommand类而言,它们是比较基础的,是重构的必然结果。 不出所料,这些类实际上与 Josh Smith 的类相同。

So we move these classes into a small class library that we can reuse in future.

因此我们将这些类移到一个小型类库中,以便将来重用。

The view looks much the same as before:

视图看起来与以前大致相同:

Example 5:Collections of Songs,Doing it wrong

As I said before, in order to display collections of items in your View (i.e. the XAML), you need to use an ObservableCollection. In this example, we create an AlbumViewModel, which nicely collects our songs together in something that people understand. We also introduce a simple song database, purely so we can quickly produce some song information for this example.

正如我之前所说,为了在您的“视图”(即 XAML)中显示项目集合,您需要使用“ObservableCollection”。 在这个例子中,我们创建了一个AlbumViewModel,它很好地将我们的歌曲以人们理解的方式收集在一起。 我们还介绍了一个简单的歌曲数据库,纯粹是为了我们可以为这个例子快速生成一些歌曲信息。

Your first attempt might be as follows:

您的第一次尝试可能如下:

class AlbumViewModel
{
    #region Members
    ObservableCollection<Song> _songs = new ObservableCollection<Song>();
    #endregion
}

You might think: "I have a different view model this time, I want to display the songs as an AlbumViewModel, not a SongViewModel".

您可能会想:“这次我有一个不同的视图模型,我想将歌曲显示为AlbumViewModel,而不是SongViewModel”。

We also create some more ICommands and attach them to some buttons:

我们还创建了更多 ICommand 并将它们附加到一些按钮上:

public ICommand AddAlbumArtist {}

public ICommand UpdateAlbumArtists {}

In this example, clicking 'Add Artist' works fine. But clicking 'Update Artist Names', fails. If you read the yellow highlighted note on this page on MSDN, it explains why:

To fully support transferring data values from binding source objects to binding targets, each object in your collection that supports bindable properties must implement an appropriate property changed notification mechanism such as the INotifyPropertyChanged interface.

Our view looks like this:

在此示例中,单击“Add Artist”可以正常工作。 但是单击“Update Artist Names”失败。 如果您阅读了此 [MSDN 上的页面](http://msdn.microsoft.com/query/dev10.query?appId=Dev10IDEF1&l=EN-US&k=k("SYSTEM.COLLECTIONS.OBJECTMODEL.OBSERVABLECOLLECTION` 1");k(TargetFrameworkMoniker-".NETFRAMEWORK%2cVERSION%3dV4.0");k(DevLang-CSHARP)&rd=true),它解释了为什么:

要完全支持将数据值从绑定源对象传输到绑定目标,您的集合中支持可绑定属性的每个对象都必须实现适当的属性更改通知机制,例如“INotifyPropertyChanged”接口。

我们的视图是这样的:

Example 6: Collections of Songs, the Right Way

In this final example, we fix the AlbumViewModel to have an ObservableCollection of SongViewModels that we created earlier:

在最后一个示例中,我们修复 AlbumViewModel 以具有我们之前创建的 SongViewModels 的 ObservableCollection:

class AlbumViewModel
{
    #region Members
    ObservableCollection<SongViewModel> _songs = new ObservableCollection<SongViewModel>();
    #endregion
    //  code elided for brevity
}

Now all our buttons that are bound to commands operate on our collection. Our code-behind in *MainWindow.cs* is still completely empty.

现在我们所有绑定到命令的按钮都在我们的集合上运行。 我们在*MainWindow.cs* 中的代码仍然是完全空的。

Conclusion

总结

Instantiating Your ViewModel

实例化你的 ViewModel

One last point that is worth mentioning is that when you declare your ViewModel declaratively in the XAML, you cannot pass it any parameters: in other words, your ViewModel must have an implicit, or explicit default constructor. How you add state to your ViewModel is up to you. You may find it easier to declare the ViewModel in the MainWindow.cs code-behind, where you can pass in construction parameters.

最后一点值得一提的是,当您在 XAML 中以声明方式声明 ViewModel 时,您不能向它传递任何参数:换句话说,您的 ViewModel 必须具有隐式或显式默认构造函数。如何向 ViewModel 添加状态取决于您。您可能会发现在 MainWindow.cs 代码隐藏中声明 ViewModel 更容易,您可以在其中传递构造参数。

Other Frameworks

其他框架

There are lots of other MVVM Frameworks of wildly different complexity and functionality, targeting WPF, WP7, Silverlight, and any combination of the three.

还有许多其他 MVVM 框架,其复杂性和功能各不相同,针对 WPF、WP7、Silverlight 以及这三者的任意组合。

Finally...

最后...

Hopefully these six examples show you how easy it is to write a WPF application using MVVM. I've tried to cover all of the points that I think are important and often discussed in multiple articles.

希望这六个示例向您展示使用 MVVM 编写 WPF 应用程序是多么容易。我试图涵盖所有我认为重要且经常在多篇文章中讨论的要点。

posted @ 2021-11-02 22:16  几个酒菜成这样  阅读(199)  评论(0编辑  收藏  举报