原文:【WPF】创建基于模板的WPF控件(经典)

WPF可以创建两种控件,它们的名字也很容易让人混淆:用户控件(User Control)和定制控件(Customer Control),之所以如此命名,是因为用户控件更面向控件的“使用者”,以方面他们利用现成的控件组合成新的控件,而客户控件,更便于定制化(Customization),方便创建有别于现有控件的定制控件。 



定制控件提供了行为和表现完全分离的开发模式,具有很高的灵活性,当然,也更难一些。这里我们通过创建个简单的搜索控件来看看如何开发定制控件: 

 

首先我们创建一个WPF应用,在同一个solution里,再添加一个用户WPF控件库。 

系统会自动在控件库里创建一个UserControl1.XAML,这个文件可以直接删除。在WPF控件库里添加一个新的项目,注意:应该选择定制控件而不是用户控件,如图: 

 

现在程序结构看起来应该像这样子: 
 

定制控件的模板会为我们建立FilterTextBox.cs和Generic.xaml文件。 

前者内容如下: 
  1. public class FilterTextBox : Control{   
  2.    static FilterTextBox()  
  3.     {  
  4.         DefaultStyleKeyProperty.OverrideMetadata(typeof(FilterTextBox),   
  5.            new FrameworkPropertyMetadata(typeof(FilterTextBox)));  
  6.     }}  

generic.xaml是定制控件的外观表现,默认在themes目录下 

  1. <Style TargetType="{x:Type local:FilterTextBox}">  
  2.   <Setter Property="Template">  
  3.     <Setter.Value>  
  4.       <ControlTemplate TargetType="{x:Type local:FilterTextBox}">  
  5.         <Border Background="{TemplateBinding Background}"   
  6.                BorderBrush="{TemplateBinding   
  7. BorderBrush}"  
  8.                 BorderThickness="{TemplateBinding BorderThickness}">  
  9.         </Border>  
  10.       </ControlTemplate>  
  11.     </Setter.Value>  
  12.   </Setter>  
  13. </Style>  

现在generic.xaml的border还是空的。 

接下来,我们先为控件创建其行为。首先我们添加一个叫"Text"的依赖属性,这是用户输入的搜索文本。这儿我们创建了个Callback,这样属性改变时就会被调用。注意不要在CLR属性的getter和setter添加任何代码,因为在运行时,WPF会忽略这些属性而直接调用GetValue和SetValue.但是在xaml里使用属性时,你仍需要CLR属性。 

  1. public static readonly DependencyProperty TextProperty =  
  2.     DependencyProperty.Register("Text",  
  3.                                 typeof(String),  
  4.                                 typeof(FilterTextBox),  
  5.                                 new UIPropertyMetadata(null,  
  6.                                 new PropertyChangedCallback(OnTextChanged),  
  7.                                 new CoerceValueCallback(OnCoerceText)));  
  8.   
  9. private static object OnCoerceText(DependencyObject o, Object value)  
  10. {  
  11.     FilterTextBox filterTextBox = o as FilterTextBox;  
  12.     if (filterTextBox != null)  
  13.         return filterTextBox.OnCoerceText((String)value);  
  14.     else  
  15.         return value;  
  16. }  
  17.   
  18. private static void OnTextChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)  
  19. {  
  20.     FilterTextBox filterTextBox = o as FilterTextBox;  
  21.     if (filterTextBox != null)  
  22.         filterTextBox.OnTextChanged((String)e.OldValue, (String)e.NewValue);  
  23. }  
  24.   
  25. protected virtual String OnCoerceText(String value)  
  26. {  
  27.     return value;  
  28. }  
  29.   
  30. protected virtual void OnTextChanged(String oldValue, String newValue)  
  31. {  
  32. }  
  33.   
  34. public String Text  
  35. {  
  36.     // IMPORTANT: To maintain parity between setting a property in XAML     
  37.     // and procedural code, do not touch the getter and setter inside     
  38.     // this dependency property!     
  39.     get  
  40.     {  
  41.         return (String)GetValue(TextProperty);  
  42.     }  
  43.     set  
  44.     {  
  45.         SetValue(TextProperty, value);  
  46.     }  
  47. }     

接着我们还要暴露出一些事件,这样当文本被修改时,控件的用户就会被通知到,这儿为控件添加一个"TextChangeEvent"。 

  1. public static readonly RoutedEvent TextChangedEvent = EventManager.RegisterRoutedEvent("TextChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(FilterTextBox));  
  2.   
  3. public event RoutedEventHandler TextChanged  
  4. {  
  5.     add { AddHandler(TextChangedEvent, value); }  
  6.     remove { RemoveHandler(TextChangedEvent, value); }  
  7. }  

事件在OnTextChange方法里被触发: 

  1. protected virtual void OnTextChanged(String oldValue, String newValue)  
  2. {  
  3.     this.RaiseEvent(new RoutedEventArgs(FilterTextBox.TextChangedEvent, this));  
  4. }  

到这里,关于控件行为的代码已经基本完成了,我们继续前进,为我们的控件创建一个外观。我们添加一个DockPanel,并在其中加入一个文本框和一个按钮。按钮Dock在右边。然后我们把文本框的Text属性和我们控件的Text属性绑定起来。同时设置UpdateSourceTrigge为TextChanged,这样每次用户在文本框输入点什么东西,就会触发我们写的TextChanged事件。注意,文本框没有border,因为这儿不需要2个Border。 

  1. <ControlTemplate TargetType="{x:Type local:FilterTextBox}">  
  2.     <Border  
  3.         Background="{TemplateBinding Background}"  
  4.         BorderBrush="{TemplateBinding BorderBrush}"  
  5.         BorderThickness="{TemplateBinding BorderThickness}"  
  6.         CornerRadius="3">  
  7.         <DockPanel  
  8.             LastChildFill="True"  
  9.             Margin="1">  
  10.             <Button  
  11.                 x:Name="PART_ClearFilterButton"  
  12.                 Content="X"  
  13.                 Width="20"  
  14.                 ToolTip="Clear Filter"  
  15.                 DockPanel.Dock="Right" />  
  16.             <TextBox  
  17.                 x:Name="PART_FilterTextBox"  
  18.                 Text="{Binding Path=Text,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged,RelativeSource={RelativeSource TemplatedParent}}"  
  19.                 BorderBrush="{x:Null}"  
  20.                 BorderThickness="0"  
  21.                 VerticalAlignment="Center" />  
  22.         </DockPanel>  
  23.     </Border>  
  24. </ControlTemplate>  

特别要注意的是这些控件的名称。他们都以"PART_"开头,这是WPF的标准方法用来表示那些需要被替换的控件,当我们要修改控件的模板的时候。如果有人为你的控件编写模板,你需要验证所用的部件的类型是控件所必需的。可以通过TemplatePart Attribute,并添加部件的名称和类型。 

  1. [TemplatePart(Name = "PART_FilterTextBox", Type = typeof(TextBox))]  
  2. [TemplatePart(Name = "PART_ClearFilterButton", Type = typeof(Button))]  
  3. public class FilterTextBox : Control{  
  4.     ...  
  5. }  

我们还设想只有在文本框里有文本的时候,一个“清空”的按钮才显示出来。我们创建一个DataTriger来实现这个想法: 

  1. <ControlTemplate TargetType="{x:Type local:FilterTextBox}">  
  2.   <Border>  
  3.     ...  
  4.   </Border>    
  5.  <ControlTemplate.Triggers>  
  6.     <DataTrigger Binding="{Binding Path=Text.Length, ElementName=PART_FilterTextBox}" Value="0">  
  7.       <Setter TargetName="PART_ClearFilterButton" Property="Visibility" Value="Collapsed" />  
  8.     </DataTrigger>  
  9.   </ControlTemplate.Triggers>  
  10. </ControlTemplate>  

现在要处理的是当用户点击"清空"按钮时候的动作,可以通过重写OnApplyTemplate来实现。通过控件名调用GetTemplateChild可以得到控件的引用。当用户点击“清空”按钮时,我们想删除文本框里的所有文本。得到文本框的引用和上面的方法相同。 

  1. public override void OnApplyTemplate()  
  2. {  
  3.     base.OnApplyTemplate();   
  4.     Button clearFilterButton = base.GetTemplateChild("PART_ClearFilterButton") as Button;  
  5.     if (clearFilterButton != null)  
  6.     {  
  7.         clearFilterButton.Click += new RoutedEventHandler(ClearFilterButton_Click);  
  8.     }  
  9. }  
  10.   
  11. private void ClearFilterButton_Click(Object sender, RoutedEventArgs e)  
  12. {  
  13.     TextBox textBox = base.GetTemplateChild("PART_FilterTextBox") as TextBox;  
  14.     if (textBox != null)  
  15.     {  
  16.         textBox.Text = String.Empty;  
  17.     }  
  18. }  

现在可以在WPF应用里跑一下了! 

【WPF】创建基于模板的WPF控件(经典)的更多相关文章

  1. WPF笔记(1.9 样式和控件模板)——Hello,WPF!

    原文:WPF笔记(1.9 样式和控件模板)--Hello,WPF! 资源的另一个用途是样式设置: <Window >  <Window.Resources>    <St ...

  2. WPF关于控件 父级控件,子级控件,控件模板中的控件,等之间的相互访问

    原文:WPF关于控件 父级控件,子级控件,控件模板中的控件,等之间的相互访问 1,在菜单中访问 弹出菜单的控件 var mi = sender as MenuItem;//菜单条目 MenuItem ...

  3. WPF备忘录(5)怎样修改模板中的控件

    首先,想问大家一个问题,你们如果要给一个Button添加背景图片会怎么做?(呵呵,这个问题又点小白哈) 是这样吗? <Button Height="57" Horizonta ...

  4. WPF:理解ContentControl——动态添加控件和查找控件

    WPF:理解ContentControl--动态添加控件和查找控件 我认为WPF的核心改变之一就是控件模型发生了重要的变化,大的方面说,现在窗口中的控件(大部分)都没有独立的Hwnd了.而且控件可以通 ...

  5. 【WPF学习】第二十章 内容控件

    内容控件(content control)是更特殊的控件类型,它们可包含并显示一块内容.从技术角度看,内容控件时可以包含单个嵌套元素的控件.与布局容器不同的是,内容控件只能包含一个子元素,而布局容器主 ...

  6. WPF自定义控件与样式(9)-树控件TreeView与菜单Menu-ContextMenu

    一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接. 本文主要内容: 菜单M ...

  7. Git使用总结 Asp.net生命周期与Http协议 托管代码与非托管代码的区别 通过IEnumerable接口遍历数据 依赖注入与控制反转 C#多线程——优先级 AutoFac容器初步 C#特性详解 C#特性详解 WPF 可触摸移动的ScrollViewer控件 .NET(C#)能开发出什么样的APP?盘点那些通过Smobiler开发的移动应用

    一,原理 首先,我们要明白Git是什么,它是一个管理工具或软件,用来管理什么的呢?当然是在软件开发过程中管理软件或者文件的不同版本的工具,一些作家也可以用这个管理自己创作的文本文件,由Linus开发的 ...

  8. Jquery如何序列化form表单数据为JSON对象 C# ADO.NET中设置Like模糊查询的参数 从客户端出现小于等于公式符号引发检测到有潜在危险的Request.Form 值 jquery调用iframe里面的方法 Js根据Ip地址自动判断是哪个城市 【我们一起写框架】MVVM的WPF框架(三)—数据控件 设计模式之简单工厂模式(C#语言描述)

    jquery提供的serialize方法能够实现. $("#searchForm").serialize();但是,观察输出的信息,发现serialize()方法做的是将表单中的数 ...

  9. WPF中不规则窗体与WindowsFormsHost控件的兼容问题完美解决方案

    首先先得瑟一下,有关WPF中不规则窗体与WindowsFormsHost控件不兼容的问题,网上给出的解决方案不能满足所有的情况,是有特定条件的,比如  WPF中不规则窗体与WebBrowser控件的兼 ...

随机推荐

  1. March 24 2017 Week 12 Friday

    Our lives are brief, that is why it's important to search for meaning. 人生短暂,所以才要寻找它的意义. What can we ...

  2. 一个查看UI5控件所有公有方法的小技巧

    一个很小的tip:比如我想把UI5表格控件里的每列设置成宽度根据显示的内容自适应,需要知道应该调用控件的哪个方法来实现. 一种办法当然是查SAP帮助文档,得知需要调用控件的公有方法setAutoSiz ...

  3. Python语言程序设计基础(1)—— 程序设计基本方法

    Everybody in this country should learn how to program a computer,because it teaches you how to think ...

  4. JDK下载

    1.进入Java官网,方式不限,如百度“Java 官网”,www.oracle.com,找到Java SE -> download.链接如下: http://www.oracle.com/tec ...

  5. 2018.10.3 MianShiBaoDian JavaWeb后端部分

    MSBD 四.JavaWeb后端部分 1.Tomcat的优化经验 去掉对web.xml的监控,吧jsp提前编写成servlet,由于物理内存的情况,加大Tomcat使用的jvm的内存 2.HTTP请求 ...

  6. SpringBoot 使用(三): 配置文件详解

    代码从开发到测试要经过各种环境,开发环境,测试环境,demo环境,线上环境,各种环境的配置都不一样,同时要方便各种角色如运维,接口测试, 功能测试,全链路测试的配置,hardcode 肯定不合适,如S ...

  7. Latex 编辑器安装

    MiKTex + TexStudio 1. 下载安装MiKTex,从http://www.miktex.org/下载basic-miktex-2.9.5872-x64.exe,一路默认安装... 或者 ...

  8. 【洛谷P1288】取数游戏II

    取数游戏II 题目链接 显然,由于一定有一个0,我们可以求出从初始点到0的链的长度 若有一条链长为奇数,则先手可以每次取完一条边上所有的数, 后手只能取另一条边的数,先手必胜: 反之若没有奇数链,后手 ...

  9. Android学习笔记_19_广播接收者 BroadcastReceiver及其应用_窃听短信_拦截外拨电话

    一.广播接收者类型: 广播被分为两种不同的类型:“普通广播(Normal broadcasts)”和“有序广播(Ordered broadcasts)”. 普通广播是完全异步的,可以在同一时刻(逻辑上 ...

  10. 中小学信息学奥林匹克竞赛-理论知识考点--IP地址

    IP地址同身份证号一样,具有唯一性! 每个人都有一个唯一的标识:身份证号. 互联网中的计算机也一样,具有一个唯一的标识:IP地址. IP地址是一个32位的二进制数,通常被分割为4个“8位二进制数”(也 ...