程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 《Programming WPF》翻譯 第9章 3.自定義功能

《Programming WPF》翻譯 第9章 3.自定義功能

編輯:關於.NET

一旦你挑選好一個基類,你將要為你的控件設計一個API。大部分WPF元素提供屬性暴露了多數功能, 事件,命令,因為他們從框架中獲取廣泛的支持,以及易於使用XAML。WPF框架對routed event和命令提 供了自動支持,它的依賴屬性系統提供了數據半島和動畫支持。當然,你也可以寫方法——對於某一種功 能,方法是最好的途徑。(例如,ListBox有一個ScrollIntoView方法,保證了一個特定的項目是可見的 。這時從代碼中能夠做的方便的事情。)但是,我更喜歡在合理的地方使用屬性,事件以及命令。

9.3.1屬性

.NET類型系統提供了一個標准的方式為一個對象定義屬性。它指定了一個協定:提供了get和set的方 法訪問器,但是對於這些的實現,以及屬性值的存儲方式,都留給了開發者。在WPF中,元素通常使用依 賴屬性系統。.NET提供了代表性的樣式屬性訪問器,但是這些僅僅是對依賴屬性(DP)的包裝,增加了便 利。

DP系統添加了大量的特色——並不有標准.NET屬性提供。例如,DP從父元素中繼承了它的值。這與OO 意義上的繼承不同,DP是從其基類繼承其特征(雖然DP也支持OO意義的繼承性)。屬性值的繼承性是一個 更動態的特征,允許在一個單元素上設置屬性,以及自動傳播到它的所有子元素。例如,所有的元素有一 個Cursor屬性用來控制鼠標指針。這個屬性使用了值的繼承性,意味著一旦你在元素上看到Cursor,所有 的子元素將自動得到同樣的Cursor屬性值。(如果你使用Windows Forms,你將熟悉這個概念,這裡任意 的元素都具有相同的特征。)

DP在別處也自動獲取它們的值。DP支持數據綁定和樣式,它們提供一個定義默認值的機制。動畫系統 也依賴於DP,它使用了DP結構來即時的調整屬性值。

通過實現你的元素屬性,如DP,你不僅可以自動得到這些特征,而且DP系統還為你管理著值的存儲。 你不必為定義任何字段實例來存儲屬性值。

存儲管理器看起來是件小事情,畢竟,為類添加一個字段是多麼的困難?盡管如此,這種特征能提供 令人驚訝的有意義的內存存儲。

簡單的繼承於Control,你的元素可以支持多於40個屬性(加上任何附屬屬性)來改變復雜性,其中大 部分看起來都具有一個默認值對於大多數對象而言。如果每個元素都有自己的一組字段存儲這些值,每個 元素將占用數百字節。一個復雜的用戶界面可能需要成千的字節(即使UI有一個相當簡單的結構,可視化 樹可以顯著地增加元素的數量。)

多數元素的大部分屬性或者繼承與它們的父類或者設置為它們的默認值,然後使用元素按字段存儲這 些值,這將浪費成百上千的內存。更加高級的存儲方式暴露了這樣的事實:多數未設置的屬性是有效的。 而且隨著內存的便宜,在CPU中移入移出數據是昂貴的。CPU可以比數據轉換主內存, 更快的執行代碼。只 有內存緩存可以相當快的跟上處理器,而且大多數現代化的處理器典型地只有成百上千字節的緩存。甚至 高端系統僅有幾個兆字節的緩存。保存成百上千字節能夠顯著的提高性能。

采取DP系統,我們可以讓它更加有效地處理信息——通過僅對顯示設定的屬性值排序。

最後,DP系統跟蹤了值的改變。這意味著一旦任何感興趣的部分想知道一個屬性值何時改變,它能使 用DP系統注冊通知。(數據綁定取決於次。)我們不需要寫任何特殊的代碼使之發生。DP系統管理者我們 屬性值的存儲,因此它知道何時屬性改變。

任何你創建的WPF自定義元素,將會自動地支持DP的一切,因為FrameworkElement間接派生於 DependencyObject基類。為了在我們的自定義元素上定義一個新的屬性,我們必須在元素的靜態構造函數 中創建一個新的DependencyObject對象。作為慣例,我們暴露了對象屬性,通過在我們的類中,按一個公 有的靜態字段排序,正如示例9-1所示。

示例9-1

public class MyCustomControl : ContentControl {

    public static DependencyProperty FooProperty;

    static MyCustomControl( ) {
        PropertyMetadata fooMetadata = new PropertyMetadata(Brushes.Green);
        FooProperty = DependencyProperty.Register("Foo", typeof(Brush),
            typeof(MyCustomControl), fooMetadata);
    }

    public Brush Foo {
        get { return (Brush) GetValueBase(FooProperty); }
        set { SetValueBase(FooProperty, value); }
    }
}

自定義控件定義了一個單獨的名為Foo的DP,類型為Brush。當注冊一個屬性時,傳遞Brushes.Green這 個默認值在PropertyMetadata對象中。

#你可能想知道為什麼WPF發明了新類型來表現屬性和關聯元數據,當反射API已經提供了PropertyInfo 類以及一個擴展機制以自定義屬性的形式。不幸的是,反射API不能提供WPF需要的彈性和性能的聯合。這 是為什麼在DP元數據和反射之間有交疊的原因。

示例9-1還提供了一個標准的.NET屬性——成對的get和set。這些不是確實需要。你可以使用公有的繼 承自DependencyObject的GetValue和SetValue方法訪問屬性,如下:

myControl.SetValue(MuCustomControl.FooProperty, Brushes.Red);

盡管如此,在大多數.NET語言中,使用正規的CLR屬性將是很容易的,因此你通常要提供一個恰當的包 裝,如示例9-1。正如你看到的,訪問器簡單的使用繼承自DependencyObject基類的GetValueBase和 SetValueBase方法。這些方法被特殊的定義用來被屬性訪問器調用。

示例9-2顯示了在xaml中如何使用自定義屬性。(這裡假設命名空間包含了這個被關聯到XML命名空間 前綴local的控件。參見附錄A獲取更多關於.NET命名空間和XML命名空間之間關系的信息)

示例9-2

<local:MyCustomControl Foo="VerticalGradient Black Red" />

注意到因為我們的屬性是Brush,我們可以使用同樣的文字速記格式,用來表示我們在第7章看到的筆 刷。示例9-2用此來創建一個垂直漸變的筆刷。

9.3.1.1附屬屬性

如果你希望定義一個附屬屬性,一種是將其應用到元素而不是定義你使用DP系統注冊的元素,通過一 個不同的調用:RegisterAttached。正如示例9-3顯示,這個方法的調用方式與Register方法一樣。

示例9-3

public class ControlWithAttachedProp : Control {

    public static DependencyProperty IsBarProperty;

    static ControlWithAttachedProp ( ) {
        PropertyMetadata isbarMetadata = new PropertyMetadata(false);
        IsBarProperty = DependencyProperty.RegisterAttached("IsBar", typeof (bool),
            typeof(ControlWithAttachedProp), isbarMetadata);
    }

    public static bool GetIsBar(DependencyObject target) {
        return (bool) target.GetValueBase(IsBarProperty);
    }

    public static void SetIsBar(DependencyObject target, bool value) {
        target.SetValueBase(IsBarProperty, value);
    }

}

注意到,訪問器看上去不太一樣。.NET並未定義一個標准的暴露屬性的方式,這些屬性由一個類型定 義,但是可以應用到另一種類型。XAML和WPF承認示例9-3中的約定語法,其中我們定義了一對靜態方法 GetPropname和SetPropname。這些方法都是將目標對象傳遞給要應用到的屬性。

示例9-4展示了如何在xaml中應有一個自定義附屬屬性到一個Button元素。

示例9-4

<Button local:ControlWithAttachedProp.IsBar="true" />

9.3.1.2 值改變的通知機制

你不能總是使用方法訪問器設置屬性,例如,數據綁定和動畫使用了DP系統直接修改屬性值。如果你 需要知道屬性值什麼時候改變,你應該依賴於被調用的訪問器,因為它們不會經常改變。取代的,你應該 注冊無效的通知,在屬性注冊期間,通過傳遞一個回調到PropertyMetadata。這將以同樣的方式工作在正 常屬性和附屬屬性上。示例9-5顯示了對示例9-3的改動,從而當屬性改變時收到通知。

示例9-5

static ControlWithAttachedProp ( ) {
    PropertyInvalidatedCallback isBarInvalidated =
        new PropertyInvalidatedCallback(OnIsBarChanged);
    PropertyMetadata isbarMetadata = new PropertyMetadata(false, isBarInvalidated);
    IsBarProperty = DependencyProperty.RegisterAttached("IsBar", typeof(bool),
        typeof(ControlWithAttachedProp), isbarMetadata);
}

static void OnIsBarChanged(DependencyObject target) {
    Debug.WriteLine("IsBar just changed: " + GetIsBar(target));
}

無論何時屬性被改動,處理改動的函數都將被調用,不論是在示例9-3調用靜態的SetIsBar方法來改變 ,還是直接使用DP系統在代碼中改變。

9.3.2事件

讓我們看一下第三章中routed事件的處理。如果你希望為內容定義自定義事件,將它們實現為routed 事件就是有意義的。不僅使你的元素與其它WPF元素保持一致,而且你可以恰當地利用同樣的bubbing和 tunnel路由策略。

創建自定義的路由事件有點像創建自定義屬性。你簡單的創建它們在類的靜態構造函數中。方便起見 ,你還可以添加一個.NET樣式對底層的路由事件處理進行包裝。這些技術被示范在示例9-6中。

示例9-6

public class MyCustomControl : ContentControl {

    public static RoutedEvent BarEvent;
    public static RoutedEvent PreviewBarEvent;

    static MyCustomControl( ) {
        BarEvent = EventManager.RegisterRoutedEvent("Bar",
            RoutingStrategy.Bubble, typeof(EventHandler), typeof (MyCustomControl));
        PreviewBarEvent = EventManager.RegisterRoutedEvent("PreviewBar",
            RoutingStrategy.Tunnel, typeof(RoutedEventHandler),
            typeof(MyCustomControl));
    }

    public event RoutedEventHandler Bar {
        add { AddHandler(BarEvent, value); }
        remove { RemoveHandler(BarEvent, value); }
    }
    public event RoutedEventHandler PreviewBar {
        add { AddHandler(PreviewBarEvent, value); }
        remove { RemoveHandler(PreviewBarEvent, value); }
    }

    protected virtual void OnBar( ) {
        RoutedEventArgs args = new RoutedEventArgs( );
        args.RoutedEvent = PreviewBarEvent;
        RaiseEvent(args);
        if (!args.Handled) {
            args = new RoutedEventArgs( );
            args.RoutedEvent = BarEvent;
            RaiseEvent(args);
        }
    }

}

示例9-6顯示了定義一對事件:一個PreviewBar(對應tunneling)事件和一個Bar(對應bubbling)事 件。這樣就提供給.NET事件成員以便利——推遲到基類中的AddHandler和RemoveHandler方法。

這個事例還提供了OnBar方法來激發事件。這將激發preview事件,而且如果沒有標記為已處理,將會 繼續激發主要的Bar事件。RaiseEvent方法由使用routed事件的基類提供,會調用。注意到正如標准的CLR 事件,由異步RaiseEvent激發的routed事件,將會順序的調用事件句柄,直到全部執行完畢才會返回。

9.3.2.1附屬屬性

正如一些屬性可以被附屬到類型上——而不是直接定義的類型,事件也是這樣。不同於依賴屬性, routed事件不需要以一種不同的方式注冊來作為附屬事件工作。例如,你可以為示例9-6定義的 MyCustomControl.Bar事件附屬一個句柄到一個Button上,如示例9-7所示。

示例9-7

RoutedEventHandler handler = MyBarHandlerMethod;
myButton.AddHandler(MyCustomControl.BarEvent, handler);

這個示例提及的MyCustomControl是一個事件句柄方法,將會被調用,當這個按鈕上的Bar事件被激活 的時候。當然,這個按鈕並不知道Bar事件,因此我們需要寫一些代碼來激活事件,如示例9-8。

示例9-8

RoutedEventArgs re = new RoutedEventArgs( );
re.RoutedEvent = MyCustomControl.BarQuuxEvent;
myButton.RaiseEvent(re);

附屬事件支持你將你自己的事件引進到UI樹中,而不需要擔心樹中的元素是否知道這些事件。

9.3.3命令

我們在第3章看到了WPF的RoutedCommand類表示用戶的一個特定的動作,這個動作可能被任意數量的不 同輸入所調用。一個自定義控件有兩種辦法想和命令系統進行交互:可能定義新的命令類型;或者處理定 義在別處的命令。

示例9-9顯示了如何注冊一個自定義命令。

示例9-9

public class MyControl : Control {
    public static RoutedCommand FooCommand;

    static MyControl( ) {
        InputGestureCollection fooInputs = new InputGestureCollection( );
        fooInputs.Add(new KeyGesture(Key.F,
                                     ModifierKeys.Control|ModifierKeys.Shift));
        FooCommand = new RoutedCommand("Foo", typeof(MyControl), fooInputs);
    }

}

代表性的,你想要制作自己的控件處理任意自定義命令。你可能還向處理一個已有的命令。例如,你 可能希望響應一些由CommandLibrary提供的標准命令。在第三章,我們看到通過添加一個CommandBinding 到你的自定義控件的CommandBindings集合,可以達到這個目的。然而,對於自定義控件,這通常不是一 個恰當的技術。通常你想要你的控件的所有實例都按照同樣的方式響應命令,而且你可能為每一個實例設 立命令綁定,最好是注冊一個類的處理器。這使你在靜態構造函數中一次性設立一個命令處理聯合,這將 會為你的自定義元素的所有實例工作。示例9-10顯示了如何去做。

示例9-10

public class MyCustomControl : ContentControl {

    static MyCustomControl( ) {
        CommandBinding copyCommandBinding = new CommandBinding(
            CommandLibrary.Copy,
            HandleCopyCommand);
        CommandManager.RegisterClassCommandBinding(typeof(MyCustomControl),
            copyCommandBinding);
    }

    private static void HandleCopyCommand(object target, ExecuteEventArgs e) {
        MyCustomControl myControl = (MyCustomControl) target;

    }
}

注意到,處理器必須是靜態的方法。當你的靜態構造函數執行時,還沒有一個自定義控件的實例。除 此之外,處理器將會代表所有實例注冊一次,因此將其放在一個實例方法中是沒有意義的。當一個命令被 調用時,處理器將傳遞一個引用到目標元素作為它的第一個參數。

實例9-11顯示了在xaml中配置一個Button,當點擊的時候會調用這個自定義命令。

實例9-11

<Button Command="local:MyControl.FooCommand">Click me</Button>

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved