六、依賴屬性回調、驗證及強制值
我們通過下面的這幅圖,簡單介紹一下WPF屬性系統對依賴屬性操作的基本步驟:
借用一個常見的圖例,介紹一下WPF屬性系統對依賴屬性操作的基本步驟:
- 第一步,確定Base Value,對同一個屬性的賦值可能發生在很多地方。比如控件的背景(Background),可能在Style或者控件的構造函數中都對它進行了賦值,這個Base Value就要確定這些值中優先級最高的值,把它作為Base Value。
- 第二步,估值。如果依賴屬性值是計算表達式(Expression),比如說一個綁定,WPF屬性系統就會計算表達式,把結果轉化成一個實際值。
- 第三步,動畫。動畫是一種優先級很高的特殊行為。如果當前屬性正在作動畫,那么因動畫而產生的值會優于前面獲得的值,這個也就是WPF中常說的動畫優先。
- 第四步,強制。如果我們在FrameworkPropertyMetadata中傳入了 CoerceValueCallback委托,WPF屬性系統會回調我們傳入的的delagate,進行屬性值的驗證,驗證屬性值是否在我們允許的范圍之內。例如強制設置該值必須大于于0小于10等等。在屬性賦值過程中,Coerce擁有 最高的優先級,這個優先級要大于動畫的優先級別。
- 第五步,驗證。驗證是指我們注冊依賴屬性如果提供了ValidateValueCallback委托,那么最后WPF會調用我們傳入的delegate,來驗證數據的有效性。當數據無效時會拋出異常來通知。
那么應該如何使用這些功能呢?
前面我們講了基本的流程,下面我們就用一個小的例子來進行說明:
XAML的代碼如下:
<Window x:Class="WpfApp1.WindowValid" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title=" WindowValid " Height="300" Width="400"> <Grid> <StackPanel> <Button Name="btnDPTest" Click="btnDPTest_Click" >屬性值執行順序測試</Button> </StackPanel> </Grid> </Window>
C#的代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; using System.Windows.Threading; using WpfApp1.Models; namespace WpfApp1 { /// <summary> /// WindowThd.xaml 的交互邏輯 /// </summary> public partial class WindowValid: Window { public WindowValid () { InitializeComponent(); } private void btnDPTest_Click(object sender, RoutedEventArgs e) { SimpleDP test = new SimpleDP(); test.ValidDP = 1; } } } using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; namespace WpfApp1.Models { public class SimpleDP : DependencyObject { public static readonly DependencyProperty ValidDPProperty = DependencyProperty.Register("ValidDP", typeof(int), typeof(SimpleDP), new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.None, new PropertyChangedCallback(OnValueChanged), new CoerceValueCallback(CoerceValue)), new ValidateValueCallback(IsValidValue)); public int ValidDP { get { return (int)GetValue(ValidDPProperty); } set { SetValue(ValidDPProperty, value); } } private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { Console.WriteLine("當屬性值的OnValueChanged方法被調用,屬性值為: {0}", e.NewValue); } private static object CoerceValue(DependencyObject d, object value) { Console.WriteLine("當屬性值的CoerceValue方法被調用,屬性值強制為: {0}", value); return value; } private static bool IsValidValue(object value) { Console.WriteLine("當屬性值的IsValidValue方法被調用,對屬性值進行驗證,返回bool值,如果返回True表示嚴重通過,否則會以異常的形式拋出: {0}", value); return true; } } }
結果如下:
當ValidDP屬性變化之后,PropertyChangeCallback就會被調用。可以看到結果并沒有完全按照我們先前的流程先 Coerce后Validate的順序執行,有可能是WPF內部做了什么特殊處理,當屬性被修改時,首先會調用Validate來判斷傳入的value是 否有效,如果無效就不繼續后續的操作,這樣可以更好的優化性能。從上面的結果上看出,CoerceValue后面并沒有立即ValidateValue, 而是直接調用了PropertyChanged。這是因為前面已經驗證過了value,如果在Coerce中沒有改變value,那么就不用再驗證了。如 果在 Coerce中改變了value,那么這里還會再次調用ValidateValue操作,和前面的流程圖執行的順序一樣,在最后我們會調用 ValidateValue來進行最后的驗證,這就保證最后的結果是我們希望的那樣了。
上面簡單介紹了處理流程,下面我們就以一個案例來具體看一看上面的流程到底有沒有出入。
依賴屬性代碼文件如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; namespace WpfApp1.Controls { class MyValiDP:System.Windows.Controls.Control { //注冊Current依賴屬性,并添加PropertyChanged、CoerceValue、ValidateValue的回調委托 public static readonly DependencyProperty CurrentValueProperty = DependencyProperty.Register( "CurrentValue", typeof(double), typeof(MyValiDP), new FrameworkPropertyMetadata( Double.NaN, FrameworkPropertyMetadataOptions.None, new PropertyChangedCallback(OnCurrentValueChanged), new CoerceValueCallback(CoerceCurrentValue) ), new ValidateValueCallback(IsValidValue) ); //屬性包裝器,通過它來暴露Current的值 public double CurrentValue { get { return (double)GetValue(CurrentValueProperty); } set { SetValue(CurrentValueProperty, value); } } //注冊Min依賴屬性,并添加PropertyChanged、CoerceValue、ValidateValue的回調委托 public static readonly DependencyProperty MinValueProperty = DependencyProperty.Register( "MinValue", typeof(double), typeof(MyValiDP), new FrameworkPropertyMetadata( double.NaN, FrameworkPropertyMetadataOptions.None, new PropertyChangedCallback(OnMinValueChanged), new CoerceValueCallback(CoerceMinValue) ), new ValidateValueCallback(IsValidValue)); //屬性包裝器,通過它來暴露Min的值 public double MinValue { get { return (double)GetValue(MinValueProperty); } set { SetValue(MinValueProperty, value); } } //注冊Max依賴屬性,并添加PropertyChanged、CoerceValue、ValidateValue的回調委托 public static readonly DependencyProperty MaxValueProperty = DependencyProperty.Register( "MaxValue", typeof(double), typeof(MyValiDP), new FrameworkPropertyMetadata( double.NaN, FrameworkPropertyMetadataOptions.None, new PropertyChangedCallback(OnMaxValueChanged), new CoerceValueCallback(CoerceMaxValue) ), new ValidateValueCallback(IsValidValue) ); //屬性包裝器,通過它來暴露Max的值 public double MaxValue { get { return (double)GetValue(MaxValueProperty); } set { SetValue(MaxValueProperty, value); } } //在CoerceCurrent加入強制判斷賦值 private static object CoerceCurrentValue(DependencyObject d, object value) { MyValiDP g = (MyValiDP)d; double current = (double)value; if (current < g.MinValue) current = g.MinValue; if (current > g.MaxValue) current = g.MaxValue; return current; } //當Current值改變的時候,調用Min和Max的CoerceValue回調委托 private static void OnCurrentValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { d.CoerceValue(MinValueProperty); d.CoerceValue(MaxValueProperty); } //當OnMin值改變的時候,調用Current和Max的CoerceValue回調委托 private static void OnMinValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { d.CoerceValue(MaxValueProperty); d.CoerceValue(CurrentValueProperty); } //在CoerceMin加入強制判斷賦值 private static object CoerceMinValue(DependencyObject d, object value) { MyValiDP g = (MyValiDP)d; double min = (double)value; if (min > g.MaxValue) min = g.MaxValue; return min; } //在CoerceMax加入強制判斷賦值 private static object CoerceMaxValue(DependencyObject d, object value) { MyValiDP g = (MyValiDP)d; double max = (double)value; if (max < g.MinValue) max = g.MinValue; return max; } //當Max值改變的時候,調用Min和Current的CoerceValue回調委托 private static void OnMaxValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { d.CoerceValue(MinValueProperty); d.CoerceValue(CurrentValueProperty); } //驗證value是否有效,如果返回True表示驗證通過,否則會提示異常 public static bool IsValidValue(object value) { Double v = (Double)value; return (!v.Equals(Double.NegativeInfinity) && !v.Equals(Double.PositiveInfinity)); } } }
XAML代碼如下:
<Window x:Class="WpfApp1.WindowProcess" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApp1.Controls" Title="WindowProcess" Height="400" Width="500"> <Grid> <StackPanel Orientation="Vertical"> <local:MyValiDP x:Name="myValiDP1" MaxValue="500" MinValue="0" /> <Label Content="可以設置最小值為0和最小大值為500" Height="30"/> <StackPanel Orientation="Horizontal" Height="60"> <Label Content="當前值為 : "/> <Label Background="Yellow" BorderBrush="Black" BorderThickness="1" IsEnabled="False" Content="{Binding ElementName=myValiDP1, Path=CurrentValue}" Height="25" VerticalAlignment="Top" /> </StackPanel> <WrapPanel > <Label Content="最小值" /> <Slider x:Name="sliderMin" Minimum="-200" Maximum="100" Width="300" ValueChanged="sliderMin_ValueChanged" SmallChange="10" /> <Label Content="{Binding ElementName=sliderMin, Path=Value}" /> </WrapPanel> <WrapPanel > <Label Content="最大值" /> <Slider x:Name="sliderMax" Minimum="200" Maximum="800" Width="300" ValueChanged="sliderMax_ValueChanged" SmallChange="10" /> <Label Content="{Binding ElementName=sliderMax, Path=Value}" /> </WrapPanel> </StackPanel> </Grid> </Window>
C#代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; namespace WpfApp1 { /// <summary> /// WindowProcess.xaml 的交互邏輯 /// </summary> public partial class WindowProcess : Window { public WindowProcess() { InitializeComponent(); //設置Current的值 myValiDP1.CurrentValue = 100; } private void sliderMin_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) { //設置Current的值 myValiDP1.CurrentValue = (int)sliderMin.Value; } private void sliderMax_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) { //設置Current的值 myValiDP1.CurrentValue = (int)sliderMax.Value; } } }
示例效果如下圖。
在上面的例子中,一共有三個依賴屬性相互作用——CurrentValue、MinValue和MaxValue,這些屬性相互作 用,但它們的規則是MinValue≤CurrentValue≤MaxValue。根據這個規則,當其中一個依賴屬性變化時,另外兩個依賴 屬性必須進行適當的調整,這里我們要用到的就是CoerceValue這個回調委托,那么實現起來也非常的簡單,注冊MaxValue的時候加入 CoerceValueCallback,在CoerceMaxValue函數中做處理:如果Maximum的值小于MinValue,則使 MaxValue值等于MinValue;同理在CurrentValue中也加入了CoerceValueCallback進行相應的強制 處理。然后在MinValue的ChangedValueCallback被調用的時候,調用CurrentValue和MaxValue的 CoerceValue回調委托,這樣就可以達到相互作用的依賴屬性一變應萬變的”千機變“。
換句話說,當相互作用的幾個依賴屬性其中一個發生變化時,在它的PropertyChangeCallback中調用受它影響的依賴屬性的CoerceValue,這樣才能保證相互作用關系的正確性。 前面也提高ValidateValue主要是驗證該數據的有效性,最設置了值以后都會調用它來進行驗證,如果驗證不成功,則拋出異常。
文章列表