WPF使用MVVM(一)-屬性綁定
簡單介紹MVVM
MVVM是Model(數據類型),View(界面),ViewModel(數據與界面之間的橋梁)的縮寫,是一種編程模式,優點一勞永逸,初步增加一些邏輯和工作量,但是為后期維護增加了極大的便利性,減少編程的關注點。
如:界面顯示某一數據,在數據有變動的情況下,傳統方式是更新此數據,同時需要手動更新界面中的數據顯示。在MVVM的模式下只需關心數據變更即可,數據可通過綁定的模式進行刷新或不刷新。
關於窗口中的行為(點擊、鼠標移入、移出。。。),也可以通過MVVM的方式進行綁定,后期就算窗體重新設計,依然可以在對應的地方綁定對應的行為,維護方式也非常靈活。
項目搭建
有了大概的層級描述之后, 我們可以嘗試建立一個新的WPF程序,此程序用來顯示某個員工的信息(姓名、年齡、職業),這里先創建我們的Model(數據模型),字段不多就三個,就叫EmployeeModel:
public class EmployeeModel
{
/// <summary>
/// 姓名
/// </summary>
public string Name { get; set; }
/// <summary>
/// 年齡
/// </summary>
public int Age { get; set; }
/// <summary>
/// 職業
/// </summary>
public string Profession { get; set; }
}
然后來布局一下我們的View(界面),這里叫做MainWindow:
界面代碼如下(老樣子,注意命名空間與你的項目保持一致):
<Window
x:Class="Test.MainWindow"
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:local="clr-namespace:Test"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800"
Height="450"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="50"
Text="姓名:" />
<TextBlock
Grid.Row="0"
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="50"
Foreground="#0078d4"
Text="王虎" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="50"
Text="年齡:" />
<TextBlock
Grid.Row="1"
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="50"
Foreground="#0078d4"
Text="35" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="50"
Text="工種:" />
<TextBlock
Grid.Row="2"
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="50"
Foreground="#0078d4"
Text="程序員" />
<Button
Grid.Row="3"
Grid.ColumnSpan="2"
Margin="20"
Content="更新一下信息"
FontSize="30"
FontWeight="Bold" />
</Grid>
</Window>
可以看到,上面的數據都是寫死的,需要我們將顯示的地方,綁定到我們的數據中,在開始綁定之前呢,這里需要建立一下我們的橋梁,也就是ViewModel,為了和界面能對應上,名字叫做MainWindowVM,在這個ViewModel中,我們使用剛才創建的數據模型EmployeeModel,並在構造函數中,初始化一個員工的信息:
public class MainWindowVM
{
private EmployeeModel _employee;
/// <summary>
/// 員工數據
/// </summary>
public EmployeeModel EmployeeM
{
get { return _employee; }
set { _employee = value; }
}
public MainWindowVM()
{
EmployeeM = new EmployeeModel();
EmployeeM.Name = "小明(綁定)";
EmployeeM.Age = 44;
EmployeeM.Profession = "程序員";
}
}
現在我們只需要界面綁定一下MainWindowVM中數據EmployeeM的對應屬性就行了,修改界面代碼如下:
<Window
x:Class="Test.MainWindow"
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:local="clr-namespace:Test"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800"
Height="450"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="50"
Text="姓名:" />
<TextBlock
Grid.Row="0"
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="50"
Foreground="#0078d4"
Text="{Binding EmployeeM.Name}" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="50"
Text="年齡:" />
<TextBlock
Grid.Row="1"
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="50"
Foreground="#0078d4"
Text="{Binding EmployeeM.Age}" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="50"
Text="工種:" />
<TextBlock
Grid.Row="2"
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="50"
Foreground="#0078d4"
Text="{Binding EmployeeM.Profession}" />
<Button
Grid.Row="3"
Grid.ColumnSpan="2"
Margin="20"
Content="更新一下信息"
FontSize="30"
FontWeight="Bold" />
</Grid>
</Window>
這時候當我們運行項目的時候,發現界面空空如也,什么數據也沒有:
這是因為,雖然我們已經有了MainWindowVM(ViewModel),但是並沒有將它與MainWindow(View)進行一個關聯,所以需要在MainWindow的后台指定一下當前的ViewModel是誰,設置MainWindow的DataContext即可:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowVM();
}
}
這下就正常了,下面我們要來演示下MVVM的魔力!
屬性變更
之前已經說了,MVVM是數據驅動的,我們已經綁定了后台的數據,現在改動下后台數據來看看界面的更新效果。
為了演示,我們給界面中的Button按鈕,添加一個點擊事件:
<Button
Grid.Row="3"
Grid.ColumnSpan="2"
Margin="20"
Click="Button_Click"
Content="更新一下信息"
FontSize="30"
FontWeight="Bold" />
在點擊事件中,我們修改當前綁定的MainWindowVM(ViewModel)中的EmployeeM屬性,觀察界面的變化,為了在Click事件中獲取綁定的實例,需要為MainWindowVM創建一個外部的字段,代碼如下:
public partial class MainWindow : Window
{
MainWindowVM mainWindowVM;
public MainWindow()
{
InitializeComponent();
mainWindowVM = new MainWindowVM();
this.DataContext = mainWindowVM;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
EmployeeModel model = new EmployeeModel();
model.Name = "王明(修改)";
model.Age = 38;
model.Profession = "外賣員";
mainWindowVM.EmployeeM = model;
}
}
此時,我們在按鈕的點擊事件中重新給EmployeeM賦值一個新值,運行程序,點擊按鈕,發現什么都沒變,斷點跟進去,發現屬性的值已經被修改,但是界面的數據並未發生更改:
這跟之前說的MVVM模式介紹有出入,按理說應該是數據更新界面也更新。
原來綁定屬性才是第一步,僅僅綁定還不行,還需要讓屬性具備通知界面更新的能力。
那我們就來給屬性添加一下這個能力。
WPF中讓屬性具備通知界面更新的能力,需要讓我們的ViewModel也就是MainWindowVM,繼承類型INotifyPropertyChanged,並實現它的PropertyChanged屬性:
public class MainWindowVM: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
完成這一步呢,還沒完, 還差一個小地方,那就是我們EmployeeM屬性的Set中,需要再補上一句,這樣當我們的EmployeeM被設置值的時候,就會調用更新界面的方法RaisePropertyChanged:
private EmployeeModel _employee;
/// <summary>
/// 員工數據
/// </summary>
public EmployeeModel EmployeeM
{
get { return _employee; }
set
{
_employee = value;
//當前屬性的名稱
RaisePropertyChanged("EmployeeM");
}
}
大功告成,現在運行項目,點擊按鈕, 就能夠看到界面進行更新了。
這時候有些老哥就有疑問了,你這給EmployeeM賦值怎么不一樣呢,為啥要新建一個數據模型,再賦值呢,我實際的情況是想在原有的數據上改個名字而已:
private void Button_Click(object sender, RoutedEventArgs e)
{
mainWindowVM.EmployeeM.Name = "王明(原屬性修改)";
}
這種修改方式,界面還是沒變呢!
那我們就再來看看類型屬性修改怎么通知界面:
類型屬性修改
上面我們知道數據變動通知界面更新,是我們在EmployeeM的Set中調用RaisePropertyChanged方法實現的,所以要想實現類型屬性在原有數據的基礎上進行界面通知,我們就需要為該類型(EmployeeModel)中屬性(Name)的Set中調用RaisePropertyChanged方法,也就是:
/// <summary>
/// 姓名
/// </summary>
public string Name
{
set
{
//value;
RaisePropertyChanged("Name")
}
}
這樣需要什么條件呢,需要我們讓EmployeeModel也要繼承INotifyPropertyChanged的接口,並實現對應的方法。很先然違背了我們設計的初衷,畢竟我們希望所有的處理都放在橋梁MainWindowVM(ViewModel)中去弄,所以看來只能另辟蹊徑了!
不過這難不倒我們,現在我這邊有兩種方式可以這么弄,一種中規中矩的,一種比較暴力,先看中規中矩的吧!
既然我們的在屬性的Set中調用通知界面更新的方法,那么我們完全可以在MainWindowVM(ViewModel)新建幾個屬性,這些屬性返回我們模型數據的屬性,如下:
/// <summary>
/// 名稱
/// </summary>
public string EmployeeName
{
get { return EmployeeM.Name; }
set
{
EmployeeM.Name = value;
RaisePropertyChanged("EmployeeName");
}
}
此時我們的界面綁定的值也不是EmployeeM.Name,而是直接改為EmployeeName:
<TextBlock
Grid.Row="0"
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="50"
Foreground="#0078d4"
Text="{Binding EmployeeName}" />
現在我們直接修改Name屬性就可以了
private void Button_Click(object sender, RoutedEventArgs e)
{
//mainWindowVM.EmployeeM.Name = "王明(原屬性修改)";
mainWindowVM.Name = "王明(原屬性修改)";
}
另外一種方式更加直接,更加暴力的方式,上面都不用修改,直接重新賦值一遍:
private void Button_Click(object sender, RoutedEventArgs e)
{
mainWindowVM.EmployeeM.Name = "王明(原屬性修改)";
mainWindowVM.EmployeeM = mainWindowVM.EmployeeM;
}
既然它沒有走Set,我們手動幫助他走一遍!
總的來說,兩種方式都不是特別理想,這邊也想不出比較不錯的方式,如果有好的意見,還請分享下!非常感謝!
下一節來說一下命令,讓我們的界面后台看起來更加的干凈!也就是將Button_Click移動到MainWindowVM(ViewModel)中。