原文:在WPF中自定义控件(3) CustomControl (下)

  在WPF中自定义控件(3) CustomControl (下)

                                   周银辉



1, 控件UI部分与逻辑部分的耦合.



这是一个容易被忽略但却非常重要的问题, 我们之所以使用CustomControl而不是UserControl,是因为我们希望自己的控件能向WPF内置控件一样,其UI能轻易地被其他用户定制或我们将来所改变.也就是说其视觉树不能与后台逻辑纠缠在一起,因为其视觉树中的元素完全可能被你的控件用户改变.比如,如果你的控件的视觉树中有一个Button,而你在该Button的Click事件中做了一些控件的逻辑处理,那么很可能你的控件打造失败了,因为该Button可能会在用户重新定义控件Template时被删除.



在讨论解决方案之前,需要提醒的是:一定要注意控件的逻辑与UI表现(Style,Template)各自职责的区分.不属于后台逻辑管的事情后台逻辑就不要管,不属于界面管的事情界面基本上也管不了或者说做起来很麻烦.一个简单的例子是:比如说你想鼠标移动到你的控件上的事情,控件稍稍变大一点,鼠标离开控件时控件大小又还原(或其他比较绚丽的效果),那么你在控件上的后台逻辑中添加的MouseEnter与MouseLeave事件的处理来达到这一效果.这时你的后台逻辑就管得过宽了,因为这种效果是Style的事情,你可以把它放在控件的默认Style中(在Generic.xaml中,你可以参考在WPF中自定义控件(3) CustomControl (上) )来提供给控件用户而不应该加在后台逻辑中而费力不讨好.这不但增加了耦合,而且在用户看来这也有些"强奸民意",因为他没有办法通过自定义的Style来覆盖掉你认为比较漂亮的控件效果.



虽然WPF将UI与后台逻辑的隔离已经做得很不错了,以便UI设计师能和我们更好的沟通和分工协助,但这并不意味着,WPF可以将UI与后台完全的隔离而互不影响.事实上,我们在编写后台逻辑的时候常常需要用到控件UI树中的某些元素才能完成,比如在编写ProgressBar时我们需要知道视觉树中的某个表示"总量"的元素的长度或高度,以便根据ProgressBar的当前Value来确定视觉树中另外一个表示"当前量"的元素的长度或高度.还有一种情况是,我们后台写好了一个不错的逻辑,但需要视觉树中的某个UI元素来明确调用,比如说,我们在ScrollBar控件中写好了LineDown()方法,但该方法需要用户点击控件视觉树中某个表示"向下滚动一行"的元素(比如一个向下的箭头)时来调用.



WPF提供了两种方案,一是利用TemplatePartAttribute,二是使用Command.



1.1 TemplatePartAttribute

TemplatePart适用于上面所说的第一种情况,其用于告知用户,在目前的情况下必须在控件的视觉树中存在指定类型和名称的元素才能是控件发挥完整的功能,否则可能导致功能丧失或需要用户自行处理删除视觉树中的该元素而带来的后遗症.如果我们是某个控件的使用者,且其注明了该属性,那么我们在修改控件的Template时就应该保证控件中是指存在其指明的特定类型和名称的元素,除非了了解自己的确不需要其关联的相关功能或你已另有处理.

在WPF内置控件中,这种类型的控件很多,比如ComboBox,PasswordBox,ProgressBar等等.

我们看看ComboxBox:

[TemplatePartAttribute(Name = "PART_EditableTextBox", Type = typeof(TextBox))]

[TemplatePartAttribute(Name = "PART_Popup", Type = typeof(Popup))]

[LocalizabilityAttribute(LocalizationCategory.ComboBox)]

[StyleTypedPropertyAttribute(Property = "ItemContainerStyle", StyleTargetType = typeof(ComboBoxItem))]

public class ComboBox : Selector

我们发现其有两个TemplatePart属性,一个是TextBox类型的"PART_EditableTextBox",另一个是Popup类型的PART_Popup,前者用于编辑文本,后者用于弹出列表项,如果某个用户在自定义该控件的Template时缺少了这两个元素,将失去相应的功能.

我们的控件也可以仿照ComboBox来规定必须的部件,并Override一些OnApplyTemplate()方法来取得相应元素:

            public override void OnApplyTemplate()

            {

                base.OnApplyTemplate();



                Button mybtn = base.GetTemplateChild("PART_BTN");



                if (mybtn != null)

                {

                    mybtn.Click += new RoutedEventHandler(mybtn_Click);

                }

            }

1.2 Command

这适合上面提到的第二种情况,即是我们后台写好了一个不错的逻辑,但需要视觉树中的某个UI元素来明确调用.比如ScrollBar的上端和下端的两个小箭头用来上下翻行,我们明显不能在这两个小箭头的鼠标点击事件中调用LineDown方法.那么正确的做法是,将后台逻辑中的LineDown和LineUp方法包装成LineDownCommand和LineUpCommand,然后将视觉树中的元素的Command属性绑定到相应的Command上.这样一来,即便用户修改视觉树中的上下小箭头为其他类型的元素,用户也可以通过命令绑定来与相应的功能联系起来.比如WPF内置的ScroolBar控件的向下小箭头的XAML代码便是如下书写的:

<RepeatButton IsEnabled="{TemplateBinding IsMouseOver}" Style="{StaticResource ScrollBarButton}" Grid.Row="2" Command="{x:Static ScrollBar.LineDownCommand}" Microsoft_Windows_Themes:ScrollChrome.ScrollGlyph="DownArrow"/>

关于Command你可以参考这里: WPF中的命令与命令绑定(一)  WPF中的命令与命令绑定(二)

2,"鹤立鸡群"并不总是好事

如果某天艺术细胞大爆发,打造了一个非常漂亮的控件,这自然是好事情,但我担心这与用户当前操作系统下的大多数界面显得过于鹤立鸡群而格格不入,毕竟在还是又不少人在Vista下使用"Windows经典"主题而非"Aero".为了打造与用户操作系统当前主题相容的控件UI,你可能需要为控件提供几套Style,比如一个比较相当较华丽的用于Aero主题,另一个较朴实用于Windows classical.为了实现效果随着用户操作系统主题改变而动态改变,你至少有两种方法来实现:(1)监听系统消息WM_THEMECHANGE,然后切控件界面.(2)将系统主题对应的Style放置在控件解决方案的themes文件夹下,比如与Vista Aero向对应的放在themes\Aero.NormalColor.xaml,与蓝色的Windows XP主题对应的放在themes\Luna.NormalColor.xaml,与Window经典主题相对应的放在themes\Classic.xmal,相信大家已经看出规律:themes\主题名.颜色名.xaml,其中经典主题没有颜色名.这样当用户切换主题时我们的控件就会切换到对应的Style,如果我们没有提供用户当前的主题所对应的样式则调用themes\Generic.xaml(这也就是为什么我在在WPF中自定义控件(3) CustomControl (上) 中说"Generic.xaml这个名称并非偶然"的原因)



3,关于控件资源的存储位置

一般说来,为了不破坏控件的封装性,我们不会把控件的资源放到控件以外的位置,比如,有一些资源在我们的应用中被频繁的使用,我们共享这些资源,我们可能会将这些资源移动到APP的资源字典中,但我们控件中的资源也被移出去,会破坏封装,并且这不利于控件被重用到其他APP. 但我们常常又会面临这样的问题:如果我把控件的资源完全放在该控件的资源字典中,但我们的应用很多地方使用了该控件,这就造成资源的频繁复制.一个典型的例子是,我们制作一个扑克牌游戏,我们的美工为我制作了一套漂亮的扑克牌图片,共54张图片,然后我建立一个扑克牌控件,控件实例将根据其当前点数和花色来选择其中一张图片并呈现出来,最后生成54个扑克牌控件实例来构成一套完整的扑克牌.如果我将美工提供的54张图片放置在控件的资源字典中,事实上对于一个扑克牌控件实例来说只使用了其中的一张图片,其余53张完全是多余的.而生成54张扑克牌控件实例时则相当于保存了54*54=2916张美工提供的图片.解决的办法是将资源转移到控件的themes\Generic.xaml中,这样既没有破坏封装又然资源得到了共享.



最后非常感谢大家关注我的博客,能在这里和大家分享工作与学习经验是件很美妙的事情.另外表示歉意的是:"在WPF中自定义控件"这个系列拖的时间太长了,差不多快一个半月了才算完成,因为这段时间我的确有太多的事情需要去完成.非常感谢大家.

在WPF中自定义控件(3) CustomControl (下)的更多相关文章

  1. 在WPF中自定义控件(3) CustomControl (上)

    原文:在WPF中自定义控件(3) CustomControl (上) 在WPF中自定义控件(3) CustomControl (上)                              周银辉 ...

  2. 在WPF中自定义控件

    一, 不一定需要自定义控件在使用WPF以前,动辄使用自定义控件几乎成了惯性思维,比如需要一个带图片的按钮,但在WPF中此类任务却不需要如此大费周章,因为控件可以嵌套使用以及可以为控件外观打造一套新的样 ...

  3. 在WPF中自定义控件(1)

    原文:在WPF中自定义控件(1)    在WPF中自定义控件(1):概述                                                   周银辉一, 不一定需要自定 ...

  4. 在WPF中自定义控件(2) UserControl

    原文:在WPF中自定义控件(2) UserControl 在WPF中自定义控件(2) UserControl                                               ...

  5. [转]在WPF中自定义控件 UserControl

    在这里我们将将打造一个UserControl(用户控件)来逐步讲解如何在WPF中自定义控件,并将WPF的一些新特性引入到自定义控件中来.我们制作了一个带语音报时功能的钟表控件, 效果如下: 在VS中右 ...

  6. WPF中在MVVM模式下,后台绑定ListCollectionView事件触发问题

    问题:WPF中MVVM模式下 ListView绑定ListCollectionView时,CurrentChanged无法触发 解决方案: 初期方案:利用ListView的SelectionChang ...

  7. wpf中dropdownButton控件下拉居中。。。

    设置模版中popup控件的HorizontalOffset属性来控制居中. 还是对popup控件不熟,折腾了一会.

  8. wpf 中自定义控件及其使用

    主要有3个步骤: 1. 首先创建一个自定义的控件,该控件继承 TextBox namespace EzIntePark.Presentation.Common { /// <summary> ...

  9. 在WPF中减少逻辑与UI元素的耦合

    原文:在WPF中减少逻辑与UI元素的耦合             在WPF中减少逻辑与UI元素的耦合 周银辉 1,    避免在逻辑中引用界面元素,别把后台数据强加给UI  一个糟糕的案例 比如说主界 ...

随机推荐

  1. June 04th 2017 Week 23rd Sunday

    It is not the mountain we conquer but outselves. 我们要征服的不是高山,而是我们自己. After days of hard working, I sl ...

  2. 我的HTML总结之常用基础便签

    HTML:是Hyper Text Markup Language(超级文本标记语言)的缩写,HTML不是一种程序,只是一种控制网页中数据显示的标识语言. HTML由一组标签组成. HTML的基本结构 ...

  3. win8中 用office 提示值不在预期的范围内

    原文:http://bbs.kafan.cn/thread-1401951-1-1.htmlhttp://bbs.kafan.cn/thread-1401951-1-1.html 错误如下: 名称:  ...

  4. 如何处理错误信息 Pricing procedure could not be determined

    当给一个SAP CRM Quotation文档的行项目维护一个产品时,遇到如下错误信息:Pricing procedure could not be determined 通过调试得知错误消息在fun ...

  5. react中使用react-transition-group实现动画

    css动画的方式,比较局限,涉及到一些js动画的时候没法处理了.react-transition-group是react的第三方模块,借住这个模块可以更方便的实现更加复杂的动画效果 https://g ...

  6. 了解Mysql与MariaDb的关系

    MariaDB是MySQL源代码的一个分支,随着Oracle买下Sun,MySQL也落入了关系型数据库王者之手.在意识到Oracle会对MySQL许可做什么后便分离了出来(MySQL先后被Sun.Or ...

  7. JS JavaScript闭包和作用域

    JavaScript高级程序设计中对闭包的定义:闭包是指有权访问另外一个函数作用域中变量的函数. 从概念上,闭包有两个特点: 1.函数 2.能访问另外一个函数的作用域中的变量 在ES6之前,JavaS ...

  8. linux上部署redis实现与Python上的redis交互(有坑)

    1.概念 Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库.缓存和消息中间件 2.linux安装redis 2.1yum源安装redis(不推荐) #前提得配置好阿里 ...

  9. 几个常用的 Git 高级命令

    Git 是一款开源优秀的版本管理工具,它最初由 Linus Torvalds 等人开发,用于管理 Linux Kernel 的版本研发.相关的书籍和教程网上琳琅满目,它们多数都详细的介绍其基本的使用和 ...

  10. oracle官网下载教程

    1.百度搜索oracle   也可以直接点击进入   oracle官网   或直接进入   下载页面 2.选择中文,看的更容易些 3.拉到最下面,选择所有下载和试用 4.选择数据库下载 5.点击下载对 ...