很久沒有更新博客了,今天向大家介紹一下Silverlight MVVM模式的使用。 MVVM即Model-View-ViewModel模式,它是一種輕量級的,靈活的方式分離數據實體與視圖之間的關係,可以更好的提高代碼的可重用性,便於項目的管理和測試。View層主要應用於頁面展現,Model爲數據的構造,ViewModel層用於邏輯的實現,並且使用數據綁定將三者之間很好的聯繫起來。
本項目中我們通過演示製作一個簡單的數據綁定的例子, 來講述MVVM模式程序的工作原理,使用過WPF和Silverlight的朋友可能都知道,在XAML中,一般的數據綁定有三種,
One-Time,One-Way,Two-way。
One-Time綁定模式的意思即爲從Data object綁定至UI這一層只進行一次綁定,程序不會繼續追蹤數據的在兩者中任何一方的變化,這種綁定方式很使用於報表數據,數據僅僅會加載一次。
One-Way綁定模式即爲單向綁定,即object-UI的綁定,只有當object中數據發生了變化,UI中的數據也將會隨之發生變化,反之不然。
Two-Way綁定模式爲雙向綁定,無論數據在Object或者是UI中發生變化,應用程序將會更新另一方,這是最爲靈活的綁定方式,同時代價也是最大的。
在這個程序中,我們將針對Two-Way數據綁定模式進行實驗。掌握了雙向綁定模式,其他兩種也很好理解了,希望大家能夠在實際項目中靈活使用這三種綁定方式。
本項目使用Visual Studio 2010 Ultimate和Silverlight 4 製作。
[本示例完整源碼下載(0分)] http://download.csdn.net/detail/aa466564931/3701792
首先創建一個名爲CSSL4DataGridBindingInMVVM的Silverlight程序。我們將使用MainPage.xaml作爲主界面,在Grid裏面添加一些TextBlock,TextBox和Button作爲簡單的數據呈現及修改,下面的Xaml的內容,添加了4個TextBlock,4個TextBox和兩個Button:
MainPage.xaml
<Grid x:Name="LayoutRoot" Background="White"
DataContext="{Binding Source={StaticResource viewModel}}" >
<Grid Name="personGrid" DataContext="{Binding Path=person, Mode=TwoWay}" >
<Grid.RowDefinitions>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"></ColumnDefinition>
<ColumnDefinition Width="120"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" Name="lbName" Grid.Row="0" Grid.Column="0" Text="Name:" />
<TextBox Name="tbName" Text="{Binding Path=Name, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}"
BindingValidationError="tbValidate" Grid.Column="1" Height="30" VerticalAlignment="Top"></TextBox>
<TextBlock VerticalAlignment="Center" Name="lbAge" Grid.Row="1" Grid.Column="0" Text="Age:" />
<TextBox Name="tbAge" Text="{Binding Path=Age, Mode=TwoWay, NotifyOnValidationError=True,ValidatesOnExceptions=True}"
BindingValidationError="tbValidate" Grid.Column="1" Grid.Row="1"></TextBox>
<TextBlock VerticalAlignment="Center" Name="lbTelephone" Grid.Row="2" Grid.Column="0" Text="Telephone:" />
<TextBox Name="tbTelephone" Text="{Binding Path=Telephone, Mode=TwoWay, NotifyOnValidationError=True,ValidatesOnExceptions=True}"
BindingValidationError="tbValidate" Grid.Column="1" Grid.Row="2"></TextBox>
<TextBlock VerticalAlignment="Center" Name="lbComment" Grid.Row="3" Grid.Column="0" Text="Comment:" />
<TextBox Name="tbComment" Text="{Binding Path=Comment, Mode=TwoWay,NotifyOnValidationError=True, ValidatesOnExceptions=True}"
BindingValidationError="tbValidate" Grid.Column="1" Grid.Row="3"></TextBox>
<Button Content="Change Model by code" Grid.Column="2" Grid.Row="4" Height="24" HorizontalAlignment="Left"
Name="btnChangeModel" VerticalAlignment="Top" Width="146" Margin="0,20,0,0"
Command="{Binding Path=DataContext.SetInformation, ElementName=LayoutRoot}"
CommandParameter="{Binding Path=Name}" />
<Button Content="Display" Grid.Column="1" Grid.Row="4" Height="23" HorizontalAlignment="Left"
Margin="0,21,0,0" Name="btnDisplay" VerticalAlignment="Top" Width="75"
Command="{Binding Path=DataContext.GetInformation, ElementName=LayoutRoot}"
CommandParameter="{Binding Path=Age}" />
<Grid Name="DisplayGrid" DataContext="">
</Grid>
</Grid>
</Grid>
MVVM模式中應儘量避免在code-behind文件中添加過多的代碼,邏輯代碼大多通過繼承ICommand類實現,這裏我們在Button的Command中添加Binding和BindingParameter,Button的單擊代碼實現寫在PersonCommand類中,下面我們將會介紹如何創建這個類,同時大家可以注意到這裏有一些Validation(驗證)的東西,在TextBox中有兩個比較特殊的屬性,NotifyOnValidationError和ValidationOnExceptions,Notify事件一般用於在model類的讀取和設置屬性時發生的異常(通常是類型轉換出現的異常)時會拋出錯誤,比方說你的TextBox中綁定了“年齡”這個屬性,並且在Model類中將它設爲Int類型,那麼當你使用雙向綁定時,給它輸入字符串類型的數據或者爲空值,那麼此時在Model的Set中變回拋出異常,你可以說自己在Set裏面進行類型檢測處理,但是通過這種方式工作量會比較大,所以我們推薦使用NotifyOnValidationError這個屬性,ValidationOnException這個屬性意思爲當數據字段在驗證時拋出異常的情況下會觸發BindValidationError這個事件,所以當數據發生異常時候我們可以定義自己的方法將錯誤呈現在頁面上。這裏我們將這兩個屬性均設置爲true。BindValidationError事件後的“tbValidate”是我們設置在MainPage.xaml.cs文件中用於呈現錯誤一個方法。
由於我們需要在異常中爲驗證設定一個狀態,保存在IsolatedStorageSettings中,這是系統爲Silverlight應用程序開闢的一個存儲空間,可以存放你需要的數據,我們這裏存入驗證的狀態位,在Command類中將會檢查此狀態位,決定是否執行更新,下面是MainPage,xaml.cs的代碼,比較簡單,但是也比較重要
MainPage.xaml.cs
public partial class MainPage : UserControl
{
private bool flag = true;
private IsolatedStorageSettings appSetting;
public MainPage()
{
InitializeComponent();
appSetting = IsolatedStorageSettings.ApplicationSettings;
if (!appSetting.Contains("validateFlag"))
{
appSetting.Add("validateFlag", this.flag);
}
}
/// <summary>
/// The method is used for catching binding exceptions.
/// We can also store validate variable with IsolatedStorageSettings.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void tbValidate(object sender, ValidationErrorEventArgs e)
{
if (e.Action == ValidationErrorEventAction.Added)
{
(e.OriginalSource as Control).Background = new SolidColorBrush(Colors.Yellow);
flag = false;
}
if (e.Action == ValidationErrorEventAction.Removed)
{
(e.OriginalSource as Control).Background = new SolidColorBrush(Colors.White);
flag = true;
}
appSetting["validateFlag"] = flag;
}
}
下面我們創建Model類,爲了演示方便,我們只創建四個屬性。此類命名爲PersonModel,注意由於是雙向綁定模式,我們需要繼承INotifyPropertyChanged接口,該接口的作用是當Model數據發生改變時候,將會通知客戶端,我們這裏需要實現名爲PropertyChangedEventHandler的事件和Changed方法,當屬性被重新設置時需要調用這個Changed方法來改變客戶端的值。
PersonModel.cs
/// <summary>
/// Person Modal class, contains name, age, telephone and comment properties.
/// The Model implement INotifyPropertyChanged interface for notifying clients
/// that properties has been changed.
/// </summary>
public class PersonModel : INotifyPropertyChanged
{
private string name;
private int age;
private string telephone;
private string comment;
private Regex regexInt = new Regex(@"^-?\d*{1}quot;);
public event PropertyChangedEventHandler PropertyChanged;
public PersonModel(string name, int age, string telephone, string comment)
{
this.name = name;
this.age = age;
this.telephone = telephone;
this.comment = comment;
}
public void Changed(string newValue)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(newValue));
}
}
public string Name
{
get
{
return name;
}
set
{
if (value == string.Empty)
throw new Exception("Name can not be empty.");
name = value;
Changed("Name");
}
}
public int Age
{
get
{
return age;
}
set
{
int outPut;
if (int.TryParse(value.ToString(), out outPut))
{
if (outPut < 0)
throw new Exception("Age must be greater than zero.");
age = outPut;//outPut.ToString();
Changed("Age");
}
else
{
throw new Exception("Age must be an integer number.");
}
}
}
public string Telephone
{
get
{
return telephone;
}
set
{
if (value == string.Empty)
throw new Exception("Telephone can not be empty.");
if (!regexInt.IsMatch(value))
throw new Exception("Telephone number must be comprised of numbers.");
telephone = value;
Changed("Telephone");
}
}
public string Comment
{
get
{
return comment;
}
set
{
if (value == string.Empty)
throw new Exception("Comment can not be empty.");
comment = value;
Changed("Comment");
}
}
}
接下來,我們創建ViewModel類,此類用於連接View和Model,並且被Command類調用,是MVVM模式的核心。創建一個class文件,命名爲PersonViewModel,此類含有PersonModel的實例,用於當實體類數據發生改變時通知客戶端程序,這裏我們創建兩個ICommand類型的屬性,分別用於獲取model數據和設置model數據。
PersonViewModel.cs
/// <summary>
/// The MainPage.xaml page bind this class with Grid control, PersonViewModel
/// class is the ViewModel layer in MVVM pattern design, this class contains
/// a model instances and invoke Command class.
/// </summary>
public class PersonViewModel
{
public PersonModel person
{
get;
set;
}
public PersonViewModel()
{
this.person = new PersonModel("John", 1, "13745654587", "Hello");
}
public PersonViewModel(string name, int age, string telephone, string comment)
{
this.person = new PersonModel(name, age, telephone, comment);
}
public ICommand GetInformation
{
get
{
return new PersonCommand(this);
}
}
public ICommand SetInformation
{
get
{
return new ChangeModelCommand(this);
}
}
public void UpdatePerson(PersonModel entity)
{
MessageBox.Show(String.Format("Name: {0} \r\n Age: {1} \r\n Telephone: {2} \r\n Comment: {3}",
person.Name,person.Age,person.Telephone,person.Comment),"Display Message", MessageBoxButton.OK);
}
下面我們創建這兩個Command類PersonCommand類和ChangeModelCommand類,因爲在本程序中,數據無論在後臺代碼或者是前臺UI中發生改變另一方也會隨之改變,PersonCommand類的作用是當用戶在點擊Display按鈕時出現對話框顯示當前model的值,由於用戶可以在TextBox中改變model的值(UI改變),所以我們需要建立一個ChangeModelCommand來響應Change Model by Code按鈕,意爲從object方向改變model的值,這樣來闡述用戶從兩方面來改變Model值都是可以成功的。Command類需要注意的幾個要點是這樣的:
1. 繼承ICommand接口。
2.實現CanExecute屬性,該屬性判斷Command類中的Execute方法能否被執行。
3.實現Execute方法,Command類的主要邏輯代碼。
4.包含ViewModel的一個實例,可以在Execute方法中直接調用ViewModel的方法,或是改變ViewModel中Model實例的數據。
PersonCommand.cs
/// <summary>
/// The class implements ICommand interface and displays a dialog box
/// to show data.
/// </summary>
public class PersonCommand : ICommand
{
public PersonViewModel viewModel;
public event EventHandler CanExecuteChanged;
private IsolatedStorageSettings appSetting;
public PersonCommand(PersonViewModel view)
{
this.viewModel = view;
appSetting = IsolatedStorageSettings.ApplicationSettings;
}
public bool CanExecute(object parameter)
{
bool validateFlag = false;
if (appSetting.Contains("validateFlag"))
{
validateFlag = (bool)appSetting["validateFlag"];
}
if (validateFlag)
{
return true;
}
else
{
return false;
}
}
public void Execute(object parameter)
{
viewModel.UpdatePerson(viewModel.person);
}
}
ChangeModelCommand.cs
public class ChangeModelCommand: ICommand
{
/// <summary>
/// This class is used to change model instance via code, and view layer will update
/// when background data source has been changed.
/// </summary>
private PersonViewModel viewModel;
public event EventHandler CanExecuteChanged;
public ChangeModelCommand(PersonViewModel viewModel)
{
this.viewModel = viewModel;
}
public bool CanExecute(object parameter)
{
if (parameter.ToString() != string.Empty)
{
return true;
}
else
{
return false;
}
}
/// <summary>
/// Change Model instance by Execute method.
/// </summary>
/// <param name="parameter"></param>
public void Execute(object parameter)
{
PersonModel model;
model = viewModel.person;
model.Name = "Default Name";
model.Age = 0;
model.Telephone = "11111111111";
model.Comment = "Default Comment";
}
}
至此,項目創建完畢,按Ctrl+F5看看結果吧。