WPF/MVVM 快速开发

http://www.codeproject.com/Articles/165368/WPF-MVVM-Quick-Start-Tutorial

 

这篇文章醍醐灌顶,入门良药啊!

 

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.

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:

  • 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).

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.

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).

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.

The Basics

  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.

The Key Points You Need to Know

  1. The collection you should use to hold your data is the ObservableCollection<>. Not a list, not adictionary, 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.

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:

public class Song
{#region Membersstring _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 } 

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'.

Since we've created a Song as a reference type, copies are cheap and light on memory. We can create ourSongViewModel 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:

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:

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

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

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

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

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

And removing your DataContext element in the XAML:

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

This is our view:

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).

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:

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

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 ofSongViewModel, therefore we are effectively displaying _songViewModel.ArtistName in the Label.

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.

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:

public class SongViewModel : INotifyPropertyChanged{#region Construction/// Constructs the default instance of a SongViewModelpublic 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 }

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.

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

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

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:

public partial class MainWindow : Window
{#region MembersSongViewModel _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); } } 

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.

Here is our improved view, where clicking now works:

Example 3: Commands

Binding to GUI events is problematic. WPF offers you a better way. This is ICommand. Many controls have aCommand 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.

ICommand requires the user to define two methods: bool CanExecute, and void Execute. The CanExecutemethod 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.

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.

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.

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.).

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.

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 anObservableCollection. 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.

Your first attempt might be as follows:

class AlbumViewModel
{#region MembersObservableCollection<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".

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

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:

Example 6: Collections of Songs, the Right Way

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

class AlbumViewModel
{#region MembersObservableCollection<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 inMainWindow.cs is still completely empty.

Our view looks like this:

 

Conclusion

Instantiating Your 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 theViewModel in the MainWindow.cs code-behind, where you can pass in construction parameters.

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.

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.

If you find this article helpful, please feel to vote it up.

If you find faults in this article, or I've said anything wrong, or you have some other issue with it, please leave a comment below explaining why, and how you would fix it.

转载于:https://www.cnblogs.com/smartsensor/p/5066471.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/295652.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

钢厂冒的白烟到底有没有污染?东北大学教授的试验结果让你大吃一惊

全世界只有3.14 % 的人关注了爆炸吧知识对于大多数人来讲钢厂的污染首先是来自于冒的气体钢铁企业污染严重的时候烟气有黄烟、黑烟、红烟、褐色烟等和这些烟相比一部分人认为钢厂冒的白烟是水蒸气没有污染另一部分则认为白烟也是污染避之而不及钢厂冒的白烟到底有没有污染呢&am…

IE6下margin-left双倍bug问题

熟悉 CSS 盒模型朋友相信对于 IE 双倍浮动边界 BUG 不会陌生&#xff0c;这个 BUG 只会产生在浮动行的第一个浮动元素&#xff08;此处说法不准确&#xff0c;并不是第一个浮动元素&#xff0c;假如浮动的元素在一行显示不下&#xff0c;占用了多行&#xff0c;那么每一行的第一…

基于事件驱动架构构建微服务第11部分:持续集成

原文链接&#xff1a;https://logcorner.com/building-microservices-through-event-driven-architecture-part12-continuous-integration/在本教程中&#xff0c;我将展示如何设置docker持续集成以在docker容器内构建和运行单元测试。因为我将使用kubernetes在容器中运行微服务…

使用Visual Studio 创建可视Web Part部件

使用Visual Studio 创建可视Web Part部件 可视Web Part部件是很强大的Web 部件。它提供内置设计器创建你的用户界面。本文主要解说怎样使用Visual Studio 创建可视Web Part部件.准备&#xff0c;创建一个自己定义列表Stats&#xff0c;加入某些栏目&#xff0c;填充部分数据。当…

如果在我爸的朋友圈当杠精,会挨揍吗?

1 这位爷爷推这么大力是想换老伴了吧&#xff1f;▼2 撞玻璃门撞出心理阴影的狗子▼3 她和她爸交朋友的方式就是以爸的口吻评论她爸的朋友圈▼4 你们还记得那个学鸭子嘟嘴的小可爱吗&#xff1f;▼5 看看狗子委屈的......▼6 去游个泳&#xff0c;结果收获了一群小鸭子▼…

Git的理解和使用

Git介绍 Git&#xff08;the stupid content tracker&#xff09;是一个源自Linux内核项目的源码管理工具。和传统的CVS、SVN不同&#xff0c;git是一个分布式源码管理工具。 Git命令简单说明git init初始化一个本地的代码仓库。git clone从远程复制一个代码仓库。git configgi…

【v3.6.2】iNeuOS工业互联网操作系统,发布实时存储方式:实时存储、变化存储、定时存储,设备振动状态和电能状态监测驱动...

目 录1. 概述... 12. 平台演示... 23. 存储方式... 24. 设备状态和用电状态监控驱动... 31. 概述本次升级主要增加了对设备实时数据的存储方式、设备振动状态驱动和用电状态监控的驱动&#xff0c;并且优化了部分核心代码。2. 平台演示在线演示&…

查询子串_SQL视图、子查询和常见函数的应用

一、视图含义&#xff1a;虚拟表&#xff0c;和普通表一样使用&#xff0c;通过表动态生成的数据。创建语法的关键字是否实际占用物理空间使用视图CREATE VIEW没有&#xff08;只保存了SQL逻辑&#xff09;增删改查&#xff0c;一般不能增删改表CREATE TABLE占用&#xff08;保…

The application could not be verified

2019独角兽企业重金招聘Python工程师标准>>> //调试的时候出现 The application could not be verified删除已安装的app&#xff0c;再Run 转载于:https://my.oschina.net/liuchuanfeng/blog/550025

git之you can‘t overwrite the remote branch问题解决

今天使用smartGit提交代码的时候出现这个错误&#xff0c;如图&#xff0c; remote是远程的意思&#xff0c;branch是分支的意思&#xff0c;you cant overwrite the remote branch英文的意思是我的remote branch被移动位置了&#xff0c;应该选中edit下面的prefrences下面的al…

数据分析和数据挖掘的理论研究必要性

2019独角兽企业重金招聘Python工程师标准>>> 数据分析&#xff0c;并不抽象&#xff0c;传统的数据分析&#xff0c;包括很多。例如信号处理中的DCT&#xff0c;滤波&#xff0c;IDCT变换。由于确定了滤波窗口的特性&#xff0c;使得对一个时间轴上的数据进行了频谱…

这个国家太奇怪了!全球最落后的国家之一,却又是世界上最幸福的国家!

全世界只有3.14 % 的人关注了爆炸吧知识中国和印度之间喜马拉雅山脉附近存在着一个弹丸小国国家人口仅有75万左右国土面积不足4万平方公里这里没有军队只有人们彼此诚挚的信任这里是最快乐的国度也是世间最神秘纯净的世外桃源这里是徒步天堂没有车水马龙的街道和红绿灯没有高楼…

嵌套饼图_旭日图的效率,高到饼图都羡慕

在展示占比情况时&#xff0c;饼图是我们最常用的选择。但是&#xff0c;饼图只能展示单层数据的占比情况&#xff0c;在面对多层级数据时&#xff0c;我们真的要用10个饼图进行可视化吗&#xff1f;不用挠头苦思&#xff0c;今天我们就来看看多个饼图的组合升级版——旭日图是…

Android之BaseAdapter—convertView回收机制与动态控件响应

前言&#xff1a;对于listView的BaseAdapter的派生&#xff0c;难度比较大。最难理解的莫过于getView(int position, View convertView, ViewGroup parent)这个函数是如何产生每条记录的&#xff0c;有些博客中利用holderView&#xff0c;有些博客却没有用&#xff0c;种种方法…

刚刚还在做菜,瞬间人就没了!厨房里一定不要再做这些事

全世界只有3.14 % 的人关注了爆炸吧知识厨房是一个家最有生活气息的地方&#xff0c;即使在外面&#xff0c;只要闻到别人家厨房飘出的饭菜香味&#xff0c;就很让人想家。但是&#xff0c;如果不注意细节&#xff0c;厨房也容易酿成悲剧&#xff01;今天&#xff0c;给大家看几…

poj 3125 Printer Queue(STL注意事项)

http://poj.org/problem?id3125 这道题没什么突出的地方&#xff0c;是一道很水的题&#xff0c;可以用list&#xff0c;也可以用queue来解决。&#xff08;用list解决的代码我就不写了&#xff09;把它写上来&#xff0c;只是因为我在使用STL的时候犯了一个小错误&#xff0c…

TCTDB存储结构

TCTDB是tokyo cabinet家族中的表格数据库&#xff08;如上图&#xff09;&#xff0c;其实现基于TCHDB&#xff08;hash database&#xff09;和TCBDB(B-tree database)。TCHDB参考&#xff1a;http://blog.chinaunix.net/space.php?uid20196318&doblog&id327754 TCBD…

Android之px 与 dp, sp换算公式

px: pixels(像素). 不同设备显示效果相同&#xff0c;一般我们HVGA代表320x480像素&#xff0c;这个用的比较多。 pt: point&#xff0c;是一个标准的长度单位&#xff0c;1pt&#xff1d;1/72英寸&#xff0c;用于印刷业&#xff0c;非常简单易用&#xff1b; sp: scaled pi…

05Prism WPF 入门实战 - Navigation

1.概要源码及PPT地址&#xff1a;https://github.com/JusterZhu/wemail视频地址&#xff1a;https://www.bilibili.com/video/BV1KQ4y1C7tg?share\sourcecopy\web本章分为以下三个部分来了解&#xff1a;Part1 视图导航、参数传递Part2 确认导航Part3 导航日志2.详细内容Part1…

明明没PS,看起来却像PS过的32张照片

全世界只有3.14 % 的人关注了爆炸吧知识现在PS太普遍&#xff0c;以至于人们看到不同寻常的东西&#xff0c;第一时间会怀疑经过技术处理。但也有一些照片&#xff0c;真的没有PS过&#xff01;1、这些轮胎痕迹令人产生了3D的错觉&#xff1a;2、截然分开的几种景观&#xff0c…