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

简介: 原文:《Programming WPF》翻译 第9章 3.自定义功能一旦你挑选好一个基类,你将要为你的控件设计一个API。大部分WPF元素提供属性暴露了多数功能,事件,命令,因为他们从框架中获取广泛的支持,以及易于使用XAML。
原文: 《Programming WPF》翻译 第9章 3.自定义功能

一旦你挑选好一个基类,你将要为你的控件设计一个API。大部分WPF元素提供属性暴露了多数功能,事件,命令,因为他们从框架中获取广泛的支持,以及易于使用XAMLWPF框架对routed event和命令提供了自动支持,它的依赖属性系统提供了数据半岛和动画支持。当然,你也可以写方法——对于某一种功能,方法是最好的途径。(例如,ListBox有一个ScrollIntoView方法,保证了一个特定的项目是可见的。这时从代码中能够做的方便的事情。)但是,我更喜欢在合理的地方使用属性,事件以及命令。

9.3.1属性

.NET类型系统提供了一个标准的方式为一个对象定义属性。它指定了一个协定:提供了getset的方法访问器,但是对于这些的实现,以及属性值的存储方式,都留给了开发者。在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属性——成对的getset。这些不是确实需要。你可以使用公有的继承自DependencyObjectGetValueSetValue方法访问属性,如下:

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

尽管如此,在大多数.NET语言中,使用正规的CLR属性将是很容易的,因此你通常要提供一个恰当的包装,如示例9-1。正如你看到的,访问器简单的使用继承自DependencyObject基类的GetValueBaseSetValueBase方法。这些方法被特殊的定义用来被属性访问器调用。

示例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元素保持一致,而且你可以恰当地利用同样的bubbingtunnel路由策略。

创建自定义的路由事件有点像创建自定义属性。你简单的创建它们在类的静态构造函数中。方便起见,你还可以添加一个.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章看到了WPFRoutedCommand类表示用户的一个特定的动作,这个动作可能被任意数量的不同输入所调用。一个自定义控件有两种办法想和命令系统进行交互:可能定义新的命令类型;或者处理定义在别处的命令。

示例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 >

 
目录
相关文章
|
前端开发 C# Windows
在WPF程序中实现PropertyGrid功能
【11月更文挑战第15天】PropertyGrid 是一个用户界面组件,用于直观地查看和编辑对象属性。在 WPF 中可通过组合 Expander 和 DataGrid 实现基本功能,或使用第三方库 PropertyTools 获得更强大特性,包括属性验证和类型特定编辑器。
1456 3
|
网络协议 网络安全 C#
基于 WPF 开发的简约,功能强大的终端模拟器
基于 WPF 开发的简约,功能强大的终端模拟器 前言今天大姚给大家推荐一款基于 WPF 开发的简约,功能强大的终端模拟器:ModengTerm。项目介绍ModengTerm是一款基于 WPF 开发的简约,功能强大的终端模拟器,可以用来连接SSH服务器,串口,TCP服务器,Windows命令行等。项目功能支持与SSH服务器,串口,Windows命令行进行交互。可以保存会话信息,方便下次直接登录。支持将终端内容导出为txt和html格式。根据关键字/正则表达式进行历史记录的查找。同步输入功能、历史记录、度可定制化的颜色主题、实时记录日志功能等。项目源码运行设置ModengTerm为启动项目运行:
455 0
|
开发框架 缓存 前端开发
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(11) -- 下拉列表的数据绑定以及自定义系统字典列表控件
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(11) -- 下拉列表的数据绑定以及自定义系统字典列表控件
|
开发框架 前端开发 JavaScript
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(3)--自定义用户控件
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(3)--自定义用户控件
|
C# 机器学习/深度学习 搜索推荐
WPF与机器学习的完美邂逅:手把手教你打造一个具有智能推荐功能的现代桌面应用——从理论到实践的全方位指南,让你的应用瞬间变得高大上且智能无比
【8月更文挑战第31天】本文详细介绍如何在Windows Presentation Foundation(WPF)应用中集成机器学习功能,以开发具备智能化特性的桌面应用。通过使用Microsoft的ML.NET框架,本文演示了从安装NuGet包、准备数据集、训练推荐系统模型到最终将模型集成到WPF应用中的全过程。具体示例代码展示了如何基于用户行为数据训练模型,并实现实时推荐功能。这为WPF开发者提供了宝贵的实践指导。
662 0
|
开发者 C# UED
WPF与多媒体:解锁音频视频播放新姿势——从界面设计到代码实践,全方位教你如何在WPF应用中集成流畅的多媒体功能
【8月更文挑战第31天】本文以随笔形式介绍了如何在WPF应用中集成音频和视频播放功能。通过使用MediaElement控件,开发者能轻松创建多媒体应用程序。文章详细展示了从创建WPF项目到设计UI及实现媒体控制逻辑的过程,并提供了完整的示例代码。此外,还介绍了如何添加进度条等额外功能以增强用户体验。希望本文能为WPF开发者提供实用的技术指导与灵感。
991 0
|
存储 开发者 C#
WPF与邮件发送:教你如何在Windows Presentation Foundation应用中无缝集成电子邮件功能——从界面设计到代码实现,全面解析邮件发送的每一个细节密武器!
【8月更文挑战第31天】本文探讨了如何在Windows Presentation Foundation(WPF)应用中集成电子邮件发送功能,详细介绍了从创建WPF项目到设计用户界面的全过程,并通过具体示例代码展示了如何使用`System.Net.Mail`命名空间中的`SmtpClient`和`MailMessage`类来实现邮件发送逻辑。文章还强调了安全性和错误处理的重要性,提供了实用的异常捕获代码片段,旨在帮助WPF开发者更好地掌握邮件发送技术,提升应用程序的功能性与用户体验。
500 0
|
API C# Shell
WPF与Windows Shell完美融合:深入解析文件系统操作技巧——从基本文件管理到高级Shell功能调用,全面掌握WPF中的文件处理艺术
【8月更文挑战第31天】Windows Presentation Foundation (WPF) 是 .NET Framework 的关键组件,用于构建 Windows 桌面应用程序。WPF 提供了丰富的功能来创建美观且功能强大的用户界面。本文通过问题解答的形式,探讨了如何在 WPF 应用中集成 Windows Shell 功能,并通过具体示例代码展示了文件系统的操作方法,包括列出目录下的所有文件、创建和删除文件、移动和复制文件以及打开文件夹或文件等。
586 0
|
C# Windows 监控
WPF应用跨界成长秘籍:深度揭秘如何与Windows服务完美交互,扩展功能无界限!
【8月更文挑战第31天】WPF(Windows Presentation Foundation)是 .NET 框架下的图形界面技术,具有丰富的界面设计和灵活的客户端功能。在某些场景下,WPF 应用需与 Windows 服务交互以实现后台任务处理、系统监控等功能。本文探讨了两者交互的方法,并通过示例代码展示了如何扩展 WPF 应用的功能。首先介绍了 Windows 服务的基础知识,然后阐述了创建 Windows 服务、设计通信接口及 WPF 客户端调用服务的具体步骤。通过合理的交互设计,WPF 应用可获得更强的后台处理能力和系统级操作权限,提升应用的整体性能。
547 0
|
C#
WPF 自定义可拖动标题栏
WPF 自定义可拖动标题栏
302 0