通過上篇文章<WPF-MVVM模式學習筆記2——MVVM簡單樣例>中舉了一個例子,我對MVVM大概有了一個比較淺顯的意思。同時,看過前兩篇文章的人,也知道我的這個系列的文章大多數來源於其他的博客,我其實只是起了一個彙總的作用,畢竟我也是在學習,肯定是要去網絡上學習別人的筆記嘍。本篇文章將以溫故而知新的方式再次去理解MVVM,力求對MVVM的認識再深一個層次。
1.再看"M-V-VM"
M:即Model,由現實世界抽象出來的模型。
V:即View,視圖,界面,該界面與用戶輸入設備進行交互。
2.View與Model如何進行交互?
此時,Binding便可以發揮作用了,比如視圖上的某一個文本框中的文本和Model中的"用戶名"關聯起來, 用戶便可以通過操作該文本框來訪問和修改Model的"用戶名"了。但是在實際編程時,我們發現,Model中的屬性(或者方法)往往不那麼容易與View中的界面控件關聯起來。比如定義了一個時間的類Time,該時間類Time具有年、月、日三個屬性,如下
public class Time
{
private string year;
public string Year
{
get
{
return this.year;
}
set
{
this.year = value;
}
}
private string month;
public string Month
{
get
{
return this.month;
}
set
{
this.month = value;
}
}
private string day;
public string Day
{
get
{
return this.day;
}
set
{
this.day = value;
}
}
public Time(string year, string month, string day)
{
this.year = year;
this.month = month;
this.day = day;
}
}
同時還有一個界面TimeView,如下
我想實現按下按鈕1,時間以年-月-日的形式展示;按下按鈕2,時間以月-日-年的形式展示。此時就需要進行一些“額外的操作”,即模型Time中的數據經過一些額外的處理才能傳給視圖,反之亦然。現在,我們意識到這個View似乎需要一個"Helper"類來處理一些額外工作。這個Helper所包含的代碼可以放在Model(例如這裏的Time)外的很多地方,比如對應的View.cs中。而且我之前確實是這麼做的,將絕大數處理邏輯放在所謂的CodeBehind中。
3.後來...
後來,在各種設計模式書籍中所看到的一樣,爲了將View和Model剝離開來,實現View可替換,便有了MVC。有了MVC以後,就開始有了MVP、MVVM等等M-V-XX。但是幾乎(我還是加個幾乎吧,因爲其他設計模式我也沒有接觸過)所有這些設計模式都主要圍繞兩個問題:
一是Model與View之間的關係,完全隔離的?單向的還是雙向的?
二是這個"XX"需要完成哪些功能,簡單流程調度?複雜規則處理?
這些問題都沒有關係, 是否採用某種模式取決於你的開發所處的環境(比如語言特性,框架特性)以及你的業務特性以及所面臨的主要變化點等等。
4.但是...
但與MVC,MVP所不同的是,MVVM的引入不僅僅是技術上的原因(解除耦合應對變化等老生常談),另外一個很大原因是:軟件團隊開發方式的改變。我們需要一種方式將View層的代碼邏輯抽取出來,並View層很純粹以便完全讓美工去打造它。相應地,,需要將View層的相應邏輯抽取到一個代碼層上,以便讓程序員專注在這裏。這就需要:
① 你擁有能夠熟練運用Blend等工具能爲程序員輸出XAML的美工,他專注於純粹的UI/UE,另外他還必須具有一定的"程序員"思維,以便輸出的東西能很好地作爲程序的一部分而運轉起來,而不是僅僅"看上去"是那樣的;
② 你需要能夠脫離View層但仍能編寫出高質量代碼的程序員。
5. Binding、Command、AttachBehavior
回想一下, 我們之所以要在View(Xaml)背後寫一些代碼(C#),無非是想傳遞一些數據以及傳遞數據時的數據的處理或在用戶與界面控件進行交互時執行一些操作。最簡單的例子是在MVC中當界面發生交互時View去調用Controler中的某個方法,以便將該操作的相應"指示"傳遞到"後臺"去。在以前的技術中, 這樣的"銜接性"的代碼是必須的。而在WPF中, 則可以通過另外的技術來進行層與層之間的"銜接", 這就是"Binding" 和"Command", 以及"AttachBehavior"。
通過Binding,我們可以實現數據的傳遞;通過Command,我們可以實現操作的調用。Binding和Command是可以寫在XAML中的, 這樣看來XAML後面對於的CS文件可以被完全拋棄或不予理會了,這樣的XAML文件正是美工所需要的。而這些對於Binding以及Command的定義描述以及其他相關信息的代碼應該放在那裏呢,當然不是View, 更不是Model,是“ViewModel”。ViewModel是爲這個View所量身定製的,它包含了Binding所需的相關信息,比如Converter以及爲View的Binding提供DataContext,它包含了Command的定義以便View層可以直接使用, 另外,它還是一個變種的Controler, 它得負責業務流程的調度。
上圖非常直觀的展示View\ViewModel\Model的交互圖,真的很佩服繪出這幅圖的作者,太直觀了。
那麼AttachBehavior又是什麼呢?一般情況下利用Command, Binding, AttachProperty等WPF特性, View和ViewModel之間能配合工作得很好。假設我們有一個Button,當該Button被點擊的時候我們要完成一些操作,很簡單,將該操作封裝成一個Command並綁定到該Button上就可以了。但如果我們要在Button被Load的時候執行另外一些操作呢? 由於Button沒有直接被Load事件所觸發的Command, 所以不能使用Command了。不能直接將Load事件處理器寫在Button所在的Xaml所對應的CS文件裏, 這和我們剛纔對MVVM的設計是相矛盾的。 一個不太好的方案是繼承一下Button, 並撰寫一個由Load所觸發的Command,這可行, 但明顯不好, 正如一個控件沒有某個屬性並且在不繼承的情況下而採用AttachProperty一樣,我們可以採用AttachBehavior。
6.收尾
到這裏,又瞭解了MVVM,同時也知道了ViewModel的作用,那麼久可以利用這個模式解決上面的時間顯示的問題了,
添加類TimeViewModel,內容如下
public class TimeViewModel : NotificationObject
{
private Time time;
public Time Time
{
get
{
return this.time;
}
set
{
this.time = value;
this.RaisePropertyChanged(() => this.time);
}
}
public TimeViewModel()
{
time = new Time("1991","02","23");
birthday = time.Year + "-" + time.Month + "-" + time.Day;
}
private string birthday;
public string Birthday
{
get
{
return this.birthday;
}
set
{
this.birthday = value;
this.RaisePropertyChanged("Birthday");
}
}
public bool CanSubmit
{
get
{
return true;
}
}
public void Submit1()
{
Birthday = time.Year + "-" + time.Month + "-" + time.Day;
}
public void Submit2()
{
Birthday = time.Month + "-" + time.Day + "-" + time.Year;
}
}
在這個ViewModel裏,定義了一個用於顯示日期格式的字符串Birthday,同時兩個方法Submit1()和Submit2()分別是兩個按鈕綁定的Click事件。
此時View視圖內容爲下
<UserControl x:Class="MVVMDemo.View.TimeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:vm="clr-namespace:MVVMDemo.ViewModel"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="gridLayout">
<Grid.DataContext>
<vm:TimeViewModel />
</Grid.DataContext>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="5*" />
<ColumnDefinition Width="5*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="5*" />
<RowDefinition Height="5*" />
<RowDefinition Height="5*" />
<RowDefinition Height="5*" />
</Grid.RowDefinitions>
<TextBlock Text="Time:" Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBlock Text="{Binding Path=Birthday,Mode=Default}" Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Left"/>
<Button x:Name="Btn1" Content="按鈕1:年-月-日" IsEnabled="{Binding CanSubmit}" Grid.Row="2" Grid.Column="0" Width="150" Height="50" VerticalAlignment="Center" HorizontalAlignment="Right">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:CallMethodAction TargetObject="{Binding}" MethodName="Submit1"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
<Button x:Name="Btn2" Content="按鈕2:月-日-年" IsEnabled="{Binding CanSubmit}" Grid.Row="2" Grid.Column="1" Width="150" Height="50" VerticalAlignment="Center" HorizontalAlignment="Right">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:CallMethodAction TargetObject="{Binding}" MethodName="Submit2"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</Grid>
</UserControl>
編譯運行程序(該工程點此下載)
(2.23就快到我的生日了,不知道還有沒有人會記得呢,呵呵)
文章來源
感謝這位朋友!!你講的很易懂!!