mvvm light入门

WPF / MVVM快速入门教程

假设你有C#的一个体面的理解,在WPF中起步并不太难。我开始看WPF前一阵子,并没有找到很多有用的MVVM教程。希望这篇文章解决了。

如同学习任何新的技术,你得到事后的好处。从我的角度来看,在WPF中几乎每一个教程中,我已经遇到不足以几个原因:

·                                这个例子是所有在XAML

·                                这个例子掩盖了,将真正使您的生活更轻松的关键事实。

·                                该示例尝试炫耀的WPF/ XAML的功能有很多毫无意义的效果,不帮助你。

·                                该示例使用有出现过于相似,框架的关键字和类别,并因此难以确定在(XAML)代码作为用户定义(该属性的类ListBox中的GroupStyle名称属性是一个完整的头疼的新手)。

因此,要解决这个问题,我写此基础上我本来希望找到的#1命中谷歌上键入“WPF教程之后。本文可能不是100%正确的,甚至做的事情唯一正确的方法,但它会说明,我想我已经找到一个地方3个月前的要点。

我会很快推出一些主题,然后表明,解释或说明每个点的例子。因此,我还没有真正试图使图形用户界面漂亮,这不是本文的点(见上面的要点)。

由于本教程是相当长的,我已经省略为简便起见,相当多的代码,所以请下载附件中的压缩文件,并查看示例(。NET4.0/VS2010)。每个例子的基础上的前一个。

基础知识

1.                              在有关WPF最重要的是数据绑定。总之,你有一些数据,通常以某种形式的集合,并且要显示给用户。你可以'绑定'你的XAML来的数据。

2.                              WPF中有两个部分,其中描述你的GUI布局和效果的XAML和代码隐藏一个被捆绑到XAML

3.                              最巧妙的也可能是最可重用的方式来组织你的代码是使用'MVVM图案:模型,视图,视图模型。这可确保您的浏览包含极少(或根本没有)代码的目的,而应该是XAML的唯一。

该关键点,你需要知道

1.                              你应该使用来保存你的数据的收集是ObservableCollection <> 。不是一个列表,而不是一个字典,而是一个ObservableCollection。这个词可察觉 '是线索在这里:WPF窗口需要能够看到您的数据收集。此集合类实现WPF使用某些接口。

2.                              每个WPF控件(包括窗口的)有一个DataContext '收集控件有一个ItemsSource 属性绑定到。

3.                              该接口INotifyPropertyChanged '将被广泛用于通信的图形用户界面和代码之间的数据进行任何修改。

1:按自己(主要是)错误的

以最好的方式开始就是一个例子。我们将开始与级,而不是通常的人物类。我们可以安排成歌曲专辑,或者一个大的集合,或者艺术家。一个简单的类将如下所示:

收起复制代码

  public class Song
    {
        #region Members
        string _artistName;
        string _songTitle;
        #endregion
 
        #region Properties
        /// The artist name.
        public string ArtistName
        {
            get { return _artistName; }
            set { _artistName = value; }
        }
 
        /// The song title.
        public string SongTitle
        {
            get { return _songTitle; }
            set { _songTitle = value; }
        }
        #endregion
    }
 

WPF中的术语,这是我们的'模式'。图形用户界面是我们的查看。这些数据结合在一起神奇的是我们的'视图模型,这实际上只是一个适配器,把我们的型号到的东西,WPF框架可以使用。因此,只是为了重申一下,这是我们的'模式'

既然我们已经创建了一个作为一个引用类型,拷贝很便宜,光记忆。我们可以创建SongViewModel很容易。我们首先需要考虑的是,什么是我们要(潜在的)显示?假设我们只关心歌曲的歌手名,而不是歌曲的标题,然后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; }
        }
}

不同之处在于,这是不太正确的。因为我们揭露的属性在我们的视图模型,我们显然希望更改了歌曲中的代码用来被在GUI中自动显示,反之亦然的艺术家的名字:

收起复制代码

SongViewModel song = ...;
// ... enable the databinding ...
//  change the name
song.ArtistName = "Elvis";
//  the gui should change

请注意,在这里所有的例子中,我们创建我们的视图模型*声明*,也就是说,我们为此在XAML中:

收起复制代码

 <Window x:Class="Example1.MainWindow"
        xmlns:local="clr-namespace:Example1">
    <Window.DataContext>
        <!-- Declaratively create an instance of our SongViewModel -->
        <local:SongViewModel />
    </Window.DataContext>
 

这相当于在您的代码隐藏这样MainWindow.cs

收起复制代码

    public partial class MainWindow : Window
    {
        SongViewModel _viewModel = new SongViewModel();
        public MainWindow()
        {
            InitializeComponent();
            base.DataContext = _viewModel;
        }
    }

和删除您DataContext元素在XAML

<Window x:Class="Example1.MainWindow"
        xmlns:local="clr-namespace:Example1">
    <!--  no data context -->

这是我们的观点:

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

数据绑定

还记得我说过的,我会选择突出的一个属性开始。在这个例子中,我们要显示的ArtistName。我选择了这个名字,因为它是一样的任何WPF属性。还有的例子是选择在网络上无数类,然后一个名称属性(该名称属性存在多个。NET WPF类)。也许是文章的作者根本没有意识到,这是对初学者特别混乱(谁是,奇怪的是,目标受众这些文章)。

有几十个有关数据绑定在那里的其他文章,所以我就不在这里。我希望这个例子是如此微不足道,你可以看到正在发生的事情。

绑定到ArtistName对我们的财产SongViewModel,我们简单地做到这一点的MainWindow.xaml

收起复制代码

   <Label Content="{Binding ArtistName}" />
 

绑定 '关键字绑定控件的内容,在这种情况下,标签,物业ArtistName通过返回的对象“ DataContext。正如你看到的上面,我们设置我们DataContext的实例SongViewModel,因此我们正在有效地显示_songViewModel.ArtistName标签

再次:单击该按钮不会更新任何事情,因为我们还没有完全实现数据绑定。GUI是没有收到该属性已更改的任何通知

实施例2INotifyPropertyChanged

这是我们必须实现的巧妙命名接口:INotifyPropertyChanged。因为它说,实现该接口的类,通 ​​知当一个属性已经改变了任何侦听器。因此,我们需要修改我们的SongViewModel类多一点点:

收起复制代码

public class SongViewModel : INotifyPropertyChanged
    {
        #region Construction
        /// Constructs the default instance of a SongViewModel
        public SongViewModel()
        {
            _song = new Song { ArtistName = "Unknown", SongTitle = "Unknown" };
        }
        #endregion
 
        #region Members
        Song _song;
        #endregion
 
        #region Properties
        public Song Song
        {
            get
            {
                return _song;
            }
            set
            {
                _song = value;
            }
        }
 
        public string ArtistName
        {
            get { return Song.ArtistName; }
            set
            {
                if (Song.ArtistName != value)
                {
                    Song.ArtistName = value;
                    RaisePropertyChanged("ArtistName");
                }
            }
        }
        #endregion
 
        #region INotifyPropertyChanged Members
 
        public event PropertyChangedEventHandler PropertyChanged;
 
        #endregion
 
        #region Methods
 
        private void RaisePropertyChanged(string propertyName)
        {
            // take a copy to prevent thread issues
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        #endregion
    }

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

所以,现在我们有一个模型,和一个视图模型。我们只需要定义我们的视图。这仅仅是我们的主窗口

收起复制代码

<Window x:Class="Example2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Example2"
        Title="Example 2"  SizeToContent="WidthAndHeight" ResizeMode="NoResize"
        Height="350" Width="525">
    <Window.DataContext>
        <!-- Declaratively create an instance of our SongViewModel -->
        <local:SongViewModel />
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        <Label Grid.Column="0" Grid.Row="0" Content="Example 2 - this works!" />
        <Label Grid.Column="0" Grid.Row="1" Content="Artist:  " />
        <Label Grid.Column="1" Grid.Row="1" Content="{Binding ArtistName}" />
        <Button Grid.Column="1" Grid.Row="2" Name="ButtonUpdateArtist"
        Content="Update Artist Name" Click="ButtonUpdateArtist_Click" />
    </Grid>
</Window>

要测试的数据绑定,我们可以采取传统的方式,创建一个按钮和线到其OnClick事件,所以上面的XAML有一个按钮,点击事件,给后面的代码:

收起复制代码

    public partial class MainWindow : Window
    {
        #region Members
        SongViewModel _viewModel;
        int _count = 0;
        #endregion
 
        public MainWindow()
        {
            InitializeComponent();
 
            //  We have declared the view model instance declaratively in the xaml.
            //  Get the reference to it here, so we can use it in the button click event.
            _viewModel = (SongViewModel)base.DataContext;
        }
 
        private void ButtonUpdateArtist_Click(object sender, RoutedEventArgs e)
        {
            ++_count;
            _viewModel.ArtistName = string.Format("Elvis ({0})", _count);
        }
    }    
 

这是确定的,但它不是我们应该如何使用WPF:首先,我们增加了我们的'更新艺术家的逻辑到我们的后台代码。它不属于那里。该窗口类是关注的窗口。第二个问题是,假设我们要移动逻辑上的**单击事件到不同的控制,例如,使一个菜单项。这意味着我们将cut'n'pasting,和编辑在多个地方。

这里是我们改进的视图,其中点击现在工作:

3:命令

绑定到GUI事件是有问题的。WPF为您提供一个更好的办法。这是ICommand。许多控件有一个命令属性。这些服从同样的方式绑定内容ItemsSource,除非你需要将其绑定到一个*属性*返回一个ICommand。为此,我们正在寻找在这里简单的例子,我们只需要实现一个简单的类称为“ RelayCommand 实现ICommand

ICommand需要用户定义了两个方法:BOOL CanExecute无效执行。该CanExecute方法真的只是说给用户,我可以执行这个命令?这是用于控制您可以执行GUI操作的上下文中。在我们的例子中,我们不关心,所以我们返回,这意味着该框架可以随时拨打我们的执行 '的方法。这可能是因为你有一个情况下,你必须绑定到按钮的命令,如果你选择在一个列表中的项目也只能执行。你会实现这个逻辑在“ CanExecute '方法。

既然我们要重用ICommand代码,我们使用RelayCommand包含所有我们不想继续写作的重复代码类。

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

实施例4:框架

现在,如果你已经仔细阅读,你可能会注意到,有很多这仅仅是重复的代码:养INPC,或创建命令。这主要是样板,并为INPC,我们可以将其移动到基类,我们称之为ObservableObject 。对于RelayCommand类,我们只是移动了到我们的。NET类库。这是怎么回事的MVVM框架,你发现在网络上开始(棱镜,Caliburn等)。

至于ObservableObjectRelayCommand类而言,他们是相当基本的和重构的必然结果。不出所料,这些类是几乎相同的那些由约什-史密斯

因此,我们将这些类放到一个小的类库,我们可以在未来重复使用。

该视图看起来非常像以前一样:

5:歌曲的集合,这样做是错误

正如我以前说过,为了显示项目的集合在你的视图(即XAML)中,你需要使用一个ObservableCollection。在这个例子中,我们创建了一个AlbumViewModel,这很好地收集我们的歌曲一起的东西,人们明白了。我们还介绍了一个简单的歌曲资料库,纯粹的,所以我们可以很快产生一些歌曲信息这个例子。

你的第一次尝试可能如下所示:

收起复制代码

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

你可能会想:我有一个不同的视图模型这一次,我想显示的歌曲作为AlbumViewModel,而不是一个SongViewModel 我们还创建了一些更ICommand,并将其附加在某些按钮:

收起复制代码

public ICommand AddAlbumArtist {}
 
public ICommand UpdateAlbumArtists {}

在这个例子中,点击添加艺术家的工作正常。但点击更新艺术家的名字',失败。如果你读了黄色突出显示记下这个上MSDN,它解释了为什么:

全面支持从源对象绑定到绑定目标传输数据值,在您的收藏支持可绑定属性的每个对象都必须实现适当的属性更改通知机制,如INotifyPropertyChanged接口。

我们的观点是这样的:

6:歌曲的集合,正道

在这最后的例子中,我们修复AlbumViewModel有一个ObservableCollectionSongViewModels我们前面创建:

收起复制代码

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

现在,我们所有的按钮绑定到命令我们收集工作。我们的代码隐藏在MainWindow.cs仍然是完全空的。

我们的观点是这样的:

结论

实例化你的视图模型

最后一点是值得一提的是,当你声明你ViewModelXAML中声明,你不能传递任何参数:换句话说,你ViewModel必须有一个隐含的或明确的默认构造函数。你如何添加状态到您的视图模型是由你。您可能会发现更容易申报ViewModelMainWindow.cs代码隐藏,在那里你可以通过在施工参数。

其他框架

有很多其他的MVVM框架的完全不同的复杂性和功能性,针对WPFWP7Silverlight和三者的任意组合。

终于...

但愿这六个例子显示你是多么容易使用MVVM写一个WPF应用程序。我一直试图掩盖一切,我觉得是很重要的,而且往往在多篇文章中讨论的要点。

如果您发现这篇文章有帮助,请随时投起来​​

如果您发现本文中的故障,还是我说的任何错误,或您有其他问题的时候,请在下面留下解释为什么一个评论,你将如何解决它。