WPF 之 依賴屬性與附加屬性(五)


一、CLR 屬性

​ 程序的本質是“數據+算法”,或者說用算法來處理數據以期得到輸出結果。在程序中,數據表現為各種各樣的變量,算法則表現為各種各樣的函數(操作符是函數的簡記法)。

類的作用是把散落在程序中的變量和函數進行歸檔封裝並控制它們的訪問。被封裝在類里的變量稱為字段(Field),它表示的是類或實例的狀態;被封裝在類里的函數稱為方法(Method),它表示類或實例的功能。

​ 字段(Field)被封裝在實例里,要么能被外界訪問(非 Private修飾),要么不能(使用 Private 修飾),這種直接把數據暴露給外界的做法很不安全,很容易把錯誤的數值寫入字段。為了解決此問題,.NET Framework 推出了屬性(Property),這種 .NET Framework 屬性又稱為 CLR 屬性。

​ 屬性是一種成員,它提供靈活的機制來讀取、寫入或計算私有字段的值。 屬性可用作公共數據成員,但它們實際上是稱為訪問器的特殊方法。 這使得我們不僅可以輕松訪問數據,還有助於提高方法的安全性和靈活性。具體使用如下:

private double _seconds;

   public double Hours
   {
       get { return _seconds / 3600; }
       set {
          if (value < 0 || value > 24)
             throw new ArgumentOutOfRangeException(
                   $"{nameof(value)} must be between 0 and 24.");

          _seconds = value * 3600;
       }
   }

二、依賴屬性(Dependency Property)

​ 實例中每個 CLR 都包裝着一個非靜態的字段(或者說由一個非靜態的字段在后台支持)。如果一個 TextBox 有 100 個屬性,每個屬性都包裝着一個 4 byte 的字段,那如果程序運行創建 10000 個 TexBox 時,屬性將占用 100*4**10000≈3.8M 的內存。在這 100 個屬性中,最常用的是 Text 屬性,這意味着大多數的內存都會被浪費掉。為了解決此問題,WPF 推出了依賴屬性。

依賴屬性(Dependency Property),就是一種可以自己沒有值,但能通過 Binding 從數據源獲得值(依賴在別人身上)的屬性。擁有依賴屬性的對象被稱為“依賴對象”。

​ WPF 中允許對象在被創建的時候並不包含用於存儲數據的空間(即字段所占用的空間)、只保留在需要用到數據時能夠獲得默認值、借用其他對象數據或實時分配空間的能力——這種對象被稱為“依賴對象(Dependency Object)”,這種實時獲取數據的能力依靠依賴屬性(Dependency Property)來實現。

​ WPF 中,必須使用依賴對象作為依賴屬性的宿主,使二者結合起來,才能形成完整的 Binding 目標被數據所驅動。依賴對象的概念由 DependencyObject 類實現,依賴屬性的概念由 DependencyProperty 類實現。DependencyObject 類具有 GetValue 和 SetValue 兩個方法。具體實現一個依賴屬性如下圖所示(在 Visual Studio 中可以使用 “propdp” 按 Tab 鍵快捷生成):

 public class StudentObject : DependencyObject
    {
        // CLR包裝
        public int MyProperty
        {
            get { return (int)GetValue(MyPropertyProperty); }
            set { SetValue(MyPropertyProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MyPropertyProperty =
            DependencyProperty.Register("MyProperty", typeof(int), typeof(StudentObject), new PropertyMetadata(0));

    }

​ WPF 中控件的屬性大多數都為依賴屬性,例如,Window 窗體的Title 屬性,我們查看代碼后如下:

 /// <summary>獲取或設置窗口的標題。</summary>
    /// <returns>
    ///   一個 <see cref="T:System.String" /> ,其中包含窗口的標題。
    /// </returns>
    [Localizability(LocalizationCategory.Title)]
    public string Title
    {
      get
      {
        this.VerifyContextAndObjectState();
        return (string) this.GetValue(Window.TitleProperty);
      }
      set
      {
        this.VerifyContextAndObjectState();
        this.SetValue(Window.TitleProperty, (object) value);
      }
    }

​ WPF 中控件的繼承關系: Control -----> FrameworkElement -----> UIElment -----> Visual -----> DependencyObject 。即 WPF 中所有 UI 控件都是依賴對象,UI 控件的大多數屬性都已經依賴化了。

​ 當我們為依賴屬性添加 CLR 包裝時,就相當於為依賴對象准備了暴露數據的 Binding Path,即該依賴對象具備扮演數據源(Source)和數據目標(Target)的能力。該依賴對象雖然沒有實現 INotifyPropertyChanged 接口,但當屬性的值發生改變的時候與之關聯的 Binding 對象依然可以得到通知,依賴屬性默認帶有這樣的功能,具體如下:

​ 我們聲明一個自定義控件,控件的依賴屬性為 DisplayText:

 public class MorTextBox : TextBox
    {
        public string DipalyText
        {
            get { return (string)GetValue(DipalyTextProperty); }
            set { SetValue(DipalyTextProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DipalyTextProperty =
            DependencyProperty.Register("DipalyText", typeof(string), typeof(MorTextBox), new PropertyMetadata(""));

        public new BindingExpressionBase SetBinding(DependencyProperty dp, BindingBase binding)
        {
            return BindingOperations.SetBinding(this, dp, binding);
        }
    }

​ 我們先把該依賴屬性作為數據目標獲取第一個 TextBox 的 Text 屬性,然后把自己的 DisplayText 依賴屬性的值作為第二個 TextBox 的 Text 屬性的數據源:

  <StackPanel>
        <TextBox Margin="5" Height="50" x:Name="t1"></TextBox>
        <!--自定義依賴屬性作為 Target-->
        <local:MorTextBox x:Name="t2" Visibility="Collapsed" DipalyText="{Binding ElementName=t1,Path=Text,UpdateSourceTrigger=PropertyChanged}"></local:MorTextBox>
        <!--自定義依賴屬性作為 Source-->
        <local:MorTextBox Margin="5" Height="50"  Text="{Binding ElementName=t2,Path=DipalyText,UpdateSourceTrigger=PropertyChanged}"></local:MorTextBox>
    </StackPanel>

​ 當我們運行程序后,第二個 TextBox 的數值隨着第一個 TextBox 數值的改變而改變。

三、附加屬性(Attached Properties)

​ 實際開發中,我們會經常遇到這樣的情況,一個人在學校的時候需要記錄班級等信息,在公司需要記錄職業等信息,那么如果我們在設計 Human 類的時候,在類里面直接定義 Grade、Position 屬性合適嗎?

​ 顯然不合適!首先,當我們在學校上學的時候完全用不到公司等信息,那么Position 所占的內存就被浪費了。為了解決此問題,我們首先想到依賴屬性,但解決了內存浪費問題,還存在一個問題,即一旦流程改變,那么 Human 類就需要做出改動,例如:當我們乘車的時候,有車次信息;去醫院看病的時候,有排號信息等。這意味着應用場景的不斷變化,導致我們所屬的信息不斷發生變化。為了解決此問題,.NET 推出了附加屬性(Attached Properties)。

附加屬性(Attached Properties)是說一個屬性本來不屬於某個對象,但由於某種需求而被后來附加上。也就是說把對象放入一個特定環境后對象才具有的屬性(表現出來就是被環境賦予的某種屬性)。上述例子,我們可以使用附加屬性去解決這個問題(添加附加屬性時,可以在 Visual studio 中輸入 "propa" 然后按 Tab 鍵快捷生成):

 class Human : DependencyObject
    {
        public string Name { get; set; }

        public int Age { get; set; }
    }

    class School : DependencyObject
    {
        public static string GetGrade(DependencyObject obj)
        {
            return (string) obj.GetValue(GradeProperty);
        }

        public static void SetGrade(DependencyObject obj, string value)
        {
            obj.SetValue(GradeProperty, value);
        }

        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty GradeProperty =
            DependencyProperty.RegisterAttached("Grade", typeof(string), typeof(School), new PropertyMetadata(""));
    }

    class Company : DependencyObject
    {
        public static string GetPosition(DependencyObject obj)
        {
            return (string) obj.GetValue(PositionProperty);
        }

        public static void SetPosition(DependencyObject obj, string value)
        {
            obj.SetValue(PositionProperty, value);
        }

        // Using a DependencyProperty as the backing store for Position.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty PositionProperty =
            DependencyProperty.RegisterAttached("PPosition", typeof(string), typeof(Company), new PropertyMetadata(""));
    }

​ 使用依賴屬性的方式如下:

            // Attached Properties
            {
                Human human0 = new Human() { Name = "John", Age = 10, };
                School.SetGrade(human0, "四年級二班");
                Human human1 = new Human() { Name = "Andy", Age = 26, };
                Company.SetPosition(human1, "軟件工程師");
                Human human2 = new Human() { Name = "Kolity", Age = 25, };
                Company.SetPosition(human2, "產品經理");
                TextBoxAttached.Text += $"{human0.Name},{human0.Age},{School.GetGrade(human0)}\r\n";
                TextBoxAttached.Text += $"{human1.Name},{human1.Age},{Company.GetPosition(human1)}\r\n";
                TextBoxAttached.Text += $"{human2.Name},{human2.Age},{Company.GetPosition(human2)}\r\n";
            }

​ 輸出結果,如下所示:

John,10,四年級二班
Andy,26,軟件工程師
Kolity,25,產品經理

​ 從附加屬性的實現中,我們可以看出附加屬性(Attached Properties)的本質就是依賴屬性(Dependency Property)。附加屬性通過聲明與依賴屬性相關的 Get 與 Set 方法實現寄宿在宿主類(例如:Human)上,這意味宿主類也必須實現 DependencyObject 類。

​ 其實,WPF 控件的布局控件的許多屬性就為附加屬性,例如:當把一個 TextBox 放入 Grid中時,對於 TextBox 而言我們可以使用 Grid 的 Row 和 Column 屬性,如下:

        <Grid >
            <TextBox Grid.Row="0" Grid.Column="0"></TextBox>
        </Grid>

​ 放入 Canvas 中,可以使用 Canvas 的 Left 等附加屬性:

        <Canvas>
            <TextBox Canvas.Left="0" Canvas.Right="100" Canvas.Bottom="20" Canvas.Top="8"></TextBox>
        </Canvas>

​ 附加屬性(Attached Properties)的本質是依賴屬性(Dependency Property),因此,附加屬性也可以使用 Binding 依賴在其他對象的數據上,例如:我們通過兩個 Slider 來控制矩形在 Canvas 中的橫縱坐標:

  <Canvas x:Name="c1">
            <Slider x:Name="s1" Width="200" Height="50" Canvas.Top="10" Canvas.Left="50" Maximum="300"></Slider>
            <Slider x:Name="s2" Width="200" Height="50" Canvas.Top="40" Canvas.Left="50" Maximum="400"></Slider>
            <Rectangle Fill="CadetBlue" Width="30" Height="30" Canvas.Left="{Binding ElementName=s1,Path=Value}" Canvas.Top="{Binding ElementName=s2,Path=Value}"></Rectangle>
        </Canvas>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM