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

时间:2022-08-26 22:31:16

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

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



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

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

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

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

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

现在程序结构看起来应该像这样子: 
【WPF】创建基于模板的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笔记&lpar;1&period;9 样式和控件模板&rpar;——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自定义控件与样式&lpar;9&rpar;-树控件TreeView与菜单Menu-ContextMenu

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

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

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

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

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

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

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

随机推荐

  1. qsort C&plus;&plus; VS2013 leetcode

    class Solution { private: static int compare(const void * a, const void * b) { return (*(int*)a - *( ...

  2. sql server还原数据库bak文件

    RESTORE DATABASE CCC FROM DISK = 'AAA.bak' with replace,  MOVE 'BBB' TO 'C:\Program Files\Microsoft ...

  3. python之内置类型&colon; 序列&comma; 字典

    序列: 元素之类有序的类型. Python 2.x支持6种内置序列: list, tuple, string, ustring, buffer, xrange (1)序列的定义: list: [] t ...

  4. WCF契约之---服务契约 、数据契约、 消息契约

    本篇博文只是简单说下WCF中的契约的种类.作用以及一些简单的代码示例.在WCF中契约分为服务契约.数据契约和消息契约.下面对这几种契约进行简单的介绍. 服务契约 服务契约描述了暴露给外部的类型(接口或 ...

  5. Git切换远程分支

         1. 切换git远程分支,使用命令:git checkout -b 分支名称.    注意:切换远程分支一定要带伤-b 参数,只有切换本地分支的时候才不需要 -b参数,-b 的意思是 bas ...

  6. 暂别SQL Server&comma;转战MySQL和Redis

    机缘巧合下找到一个愿意提供学习MySQL和Redis机会的岗位,于是要暂别SQL Server了. 后续一段时间会陆续总结三年来SQL Server相关的工作经验,当做是暂别前的总结.

  7. java操作elasticsearch实现查询删除和查询所有

    后期博客本人都只给出代码,具体的说明在代码中也有注释. 1.查询删除 //查询删除:将查询到的数据进行删除 @Test public void test8() throws UnknownHostEx ...

  8. python3&plus;requests:接口自动化测试(二)

    转载请注明出处:https://www.cnblogs.com/shapeL/p/9188495.html 前言:上篇文章python3+requests+unittest:接口自动化测试(一):ht ...

  9. XML的基礎結構

    1.xml是什麼? xml,Extensible Markup Language,扩展性标识语言,後綴名為.xml. 2.xml有什麼功能? xml功能是傳輸和儲存數據,用於不同的應用和平台數據共享和 ...

  10. puppet cron资源管理

    1.可用参数: ensure      ensure => {present|absent},   决定该计划任务的目标状态,present 如该cron不存在,则添加;absent 如该cro ...