1. 程式人生 > >WPF入門教程系列十四——依賴屬性(四)

WPF入門教程系列十四——依賴屬性(四)

nan out rmi strong too nim app controls ase

六、依賴屬性回調、驗證及強制值

我們通過下面的這幅圖,簡單介紹一下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主要是驗證該數據的有效性,最設置了值以後都會調用它來進行驗證,如果驗證不成功,則拋出異常。

WPF入門教程系列十四——依賴屬性(四)