MVVM在WPF中的應用
Binding用於繫結控制元件屬性的值。
Binding的模型
從Binding模型中可以看出,Binding物件作為目標和源之間的橋樑,除了著名的雙向繫結特徵外,WPF還在Binding中添加了一些機制方便我們更加方便的處理資料,比如校驗器和轉換器。
下面就來看看Binding物件到底實現了哪些屬性?
1.TargetObject和Property屬性預設不需要設定,在設定Binding的時候,WPF框架本身會根據你設定的屬性及其控制元件自動引用。
2.Source屬性設定:繫結一個數據源,一般是一個物件。常用的是DataContext和ItemSource。
①DataContext:每一個UI元素都有一個DataContext屬性,這個屬性屬於依賴屬性,也就是他是依賴在UI控制元件樹的,當前控制元件沒有指定DataContext時就會去父控制元件繼續繫結。一般我們會將ViewModel賦給DataContext。
②ItemSource:列表控制元件繫結的物件。
另外還有XML資料、RelativeSource(相對自身可以指定層級的資料來源)等形式繫結資料來源。
3.Path屬性:繫結資料來源內的屬性。
4.ElementName可以指定繫結UI元素為源,Converter轉換器(資料來源的屬性型別轉換成目標屬性的型別),Validation校驗器(設定一些規則校驗資料),Mode繫結模式。
示例:
(一)ElementName、Mode
<StackPanel Orientation="Vertical" VerticalAlignment="Center"> <TextBox x:Name="box1" /> <TextBox x:Name="box2" Text="{Binding ElementName=box1,Path=Text,Mode=TwoWay}"/> </StackPanel>
執行此示例,可以發現雖然Mode繫結為TwoWay模式,還是隻能實現box2隨box1內容同步,而在box1獲取焦點時,才會同步box2的內容。從而看出,TwoWay並不是表示實時雙向繫結。
可以選擇box1和box2相互繫結的方式實現雙向繫結。
<StackPanel Orientation="Vertical" VerticalAlignment="Center"> <TextBox x:Name="box1" Text="{Binding ElementName=box2,Path=Text}"/> <TextBox x:Name="box2" Text="{Binding ElementName=box1,Path=Text}"/> </StackPanel>
(二)Source、Converter
首先我們先新建一個Student的Model及Gender列舉型別表示學生的性別。
public class Student
{
public int Number { get; set; }
public string Name { get; set; }
public Gender Gender { get; set; }
public string Address { get; set; }
}
public enum Gender
{
Male,Female
}
介面需要展示學生的編號、姓名、性別及住址,我們可以使用Binding繫結相應TextBlock的Text屬性。
<Window.Resources>
<Style TargetType="TextBlock">
<Setter Property="Height" Value="30"/>
<Setter Property="Width" Value="300"/>
<Setter Property="Margin" Value="5"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</Window.Resources>
<Grid>
<StackPanel Orientation="Vertical" VerticalAlignment="Center">
<TextBlock Text="{Binding Number}"/>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Gender}"/>
<TextBlock Text="{Binding Address}"/>
</StackPanel>
</Grid>
在後臺程式碼中,我們只需要在構造當前Window時,給當前Window的DataContext屬性新增一個Student型別的Model,這個Model可以來自http請求或其他的控制元件、本地資料庫等等。
public MainWindow()
{
InitializeComponent();
DataContext = new Student
{
Number = 1,Name = "張三",Gender = Gender.Male,Address = "福州市"
};
}
執行程式,看到我們正確的在介面顯示了繫結的屬性的值:
可以看到,列舉型別並沒有正確的顯示成我們想要的男或女的文字形式,這時候我們就可以用到Converter。新建一個類,繼承IValueConverter。
public class GenderToStringConverter : IValueConverter
{
public object Convert(object value,Type targetType,object parameter,CultureInfo culture)
{
var gender = (Gender)value;
switch (gender)
{
case Gender.Male:
return "男";
case Gender.Female:
return "女";
}
return null;
}
public object ConvertBack(object value,CultureInfo culture)
{
throw new NotImplementedException();
}
}
在介面中需要在Resources中引用,並設定一個key,然後在相應的Binding中新增Converter引用。
<Window.Resources>
<local:GenderToStringConverter x:Key="GenderToStringConverter"/>
<Style TargetType="TextBlock">
<Setter Property="Height" Value="30"/>
<Setter Property="Width" Value="300"/>
<Setter Property="Margin" Value="5"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</Window.Resources>
<Grid>
<StackPanel Orientation="Vertical" VerticalAlignment="Center">
<TextBlock Text="{Binding Number}"/>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Gender,Converter={StaticResource GenderToStringConverter}}"/>
<TextBlock Text="{Binding Address}"/>
</StackPanel>
</Grid>
這下按照我們的想法正確的展示了。
有這樣一個需求,在同一個介面中,有多個地方都Binding了Student的Name屬性,那麼我希望後臺更改Student物件的Name值的時候,前臺介面所繫結的地方全部都要更新。
現在我們實現了Binding資料,但是如果資料有變化,我們介面上的相應屬性並不會實時的發生變化,這時候我們就需要引入一個ViewModel層連線View和Model,使View和Model能夠實時雙向通訊。ViewModel繼承INotifyPropertyChanged,用於通知屬性變化。
INotifyPropertyChanged用於通知屬性變化
用於通知屬性更改的介面,需要實現PropertyChanged事件,一般常在屬性的set訪問器中。做到當屬性值發生改變之後,通知相應名稱的屬性的屬性值變化。
①新建一個MainWindowViewModel
public class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
if (this.PropertyChanged != null)
{
this.PropertyChanged(this,new PropertyChangedEventArgs(nameof(Name)));
}
}
}
}
②View介面程式碼修改成
<StackPanel Orientation="Vertical" VerticalAlignment="Center">
<TextBlock Text="{Binding Name}"/>
<TextBox Text="{Binding Name}" TextChanged="TextBox_TextChanged"/>
<TextBox Text="{Binding Name}" TextChanged="TextBox_TextChanged"/>
</StackPanel>
③View.cs後臺介面程式碼為
public partial class MainWindow : Window
{
private MainWindowViewModel MainWindowViewModel;
public MainWindow()
{
InitializeComponent();
MainWindowViewModel = new MainWindowViewModel();
DataContext = MainWindowViewModel;
}
private void TextBox_TextChanged(object sender,TextChangedEventArgs e)
{
if (sender is TextBox textBox)
{
MainWindowViewModel.Name = textBox.Text;
}
}
}
最終我們實現效果
可以看到我們成功地實現了雙向繫結,這使得所有繫結ViewModel中Name的屬性都會實時更新,並且在後臺程式碼中我們並沒有去改變View的值,試想,我們從服務端獲取到一個新的Model資料,那麼我們只需要把新Model的屬性賦值給ViewModel中的對應屬性,ViewModel就會更新介面元素。