原文:《Programming WPF》翻译 第9章 3.自定义功能

一旦你挑选好一个基类,你将要为你的控件设计一个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>

《Programming WPF》翻译 第9章 3.自定义功能的更多相关文章

  1. 《Programming WPF》翻译 第9章 5.默认可视化

    原文:<Programming WPF>翻译 第9章 5.默认可视化 虽然为控件提供一个自定义外观的能力是有用的,开发者应该能够使用一个控件而不用必须提供自定义可视化.这个控件应该正好工作 ...

  2. 《Programming WPF》翻译 第9章 4.模板

    原文:<Programming WPF>翻译 第9章 4.模板 对一个自定义元素最后的设计考虑是,它是如何连接其可视化的.如果一个元素直接从FrameworkElement中派生,这将会适 ...

  3. 《Programming WPF》翻译 第9章 2.选择一个基类

    原文:<Programming WPF>翻译 第9章 2.选择一个基类 WPF提供了很多类,当创建一个自定义元素时,你可以从这些类中派生.图9-1显示了一组可能作为类--可能是合适的基类, ...

  4. 《Programming WPF》翻译 第9章 1.自定义控件基础

    原文:<Programming WPF>翻译 第9章 1.自定义控件基础 在写一个自定义控件之前,你需要问的第一个问题是,我真的需要一个自定义控件吗?一个写自定义控件的主要原因是为了用户界 ...

  5. 《Programming WPF》翻译 第8章 3.Storyboard

    原文:<Programming WPF>翻译 第8章 3.Storyboard Storyboard是动画的集合.如果你使用了标记,所有的动画必须要被定义在一个Storyboard中.(在 ...

  6. 《Programming WPF》翻译 第8章 1.动画基础

    原文:<Programming WPF>翻译 第8章 1.动画基础 动画包括在一段时间内改变用户界面的某些可见的特征,如它的大小.位置或颜色.你可以做到这一点,非常困难的通过创建一个tim ...

  7. 《Programming WPF》翻译 第7章 5.可视化层编程

    原文:<Programming WPF>翻译 第7章 5.可视化层编程 形状元素能提供一种便利的方式与图形一起工作,在一些情形中,添加表示绘图的元素到UI树中,可能是比它的价值更加麻烦.你 ...

  8. 《Programming WPF》翻译 第7章 1.图形基础

    原文:<Programming WPF>翻译 第7章 1.图形基础 WPF使得在你的应用程序中使用图形很容易,以及更容易开发你的显卡的能力.这有很多图形构架的方面来达到这个目标.其中最重要 ...

  9. 《Programming WPF》翻译 第6章 1.创建和使用资源

    原文:<Programming WPF>翻译 第6章 1.创建和使用资源 资源这个词具有非常广泛的意义.任何对象都可以是一个资源.一个在用户界面中经常使用的Brush或者Color可以是一 ...

随机推荐

  1. Qt编程之通过鼠标滚轮事件缩放QGraphicsView里面的Item

    首先自己subclass QGraphicsView的一个类,叫DiagramView,然后重新实现它的滚轮事件函数,然后发送一个缩放信号: oid DiagramView::wheelEvent(Q ...

  2. Linux下查看文件夹或目录大小

    当磁盘大小超过标准时会有报警提示,这时如果掌握df和du命令是非常明智的选择. df可以查看一级文件夹大小.使用比例.档案系统及其挂入点,但对文件却无能为力.    du可以查看文件及文件夹的大小.d ...

  3. bzoj 1196

    http://www.lydsy.com/JudgeOnline/problem.php?id=1196 二分+并查集 一共有2*M条路径,我们首先将这2*M条路径按费用排序. 然后二分最大费用的公路 ...

  4. 40 个超棒的免费 Bootstrap HTML5 网站模板

    Bootstrap 是快速开发Web应用程序的前端工具包.它是一个CSS和HTML的集合,它使用了最新的浏览器技术,给你的Web开发提供了时尚的版式,表单,buttons,表格,网格系统等等. 目前 ...

  5. myeclipse实现Servlet实例(2) 继承GenericServet类实现,需要重写service方法

    1.在myeclipse新建web project,配置Tomcat(在myeclipse的Window--preferences) 2.然后在src新建servlet文件( 此处放在com.tsin ...

  6. VB.NET中DataGridView控件

    VB.NET中对于表格数据的显示经常使用到DataGridView控件,其以丰富多样的数据表呈现形式被程序猿喜爱. 本人在做一个小系统中运用DataGridView控件的部分属性,这些功能的使用在使用 ...

  7. 生命周期-初识IOS

    经常因为生命周期的事情,而视图顺序加载错误,或者出现一系列的小错误并且修改不出来,程序员不知道生命周期确实挺可悲的. IOS生命周期: 自上而下的执行,并且viewDidLoad只会执行一次,所以我们 ...

  8. Android 自定义UI--指南针

    有了之前的基础,下面开始实现一个简单的指南针.首先来看一下效果图, 我们可以粗略将这个指南针分为三个部分,一是圆形背景,二是刻度,三是文本.那么在写代码的时候,就可以声明三个Paint画笔来画以上三个 ...

  9. android调试系列--使用ida pro调试原生程序

    1.工具介绍 IDA pro: 反汇编神器,可静态分析和动态调试. 模拟机或者真机:运行要调试的程序. 样本:自己编写NDK demo程序进行调试 2.前期准备 2.1  准备样本程序(假设已经配置好 ...

  10. js 闭包和回调

    原文:http://www.cnblogs.com/yuyuj/p/4525530.html 之前的工作都是基于老大搭建的框架,仿照他写的例子写的请求,很多东东也都做好了封装,只需要了解下直接调用就好 ...