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)中。