最近在做WPF,记录一下自定义控件的制作过程,源码请点击:源码

1、目标

实现一个如图所示的可增减的数字框:
数字框

2、先画Template

可以在Generic.xaml中画,也可以用MergedDictionary连接到其他xaml中。

<ResourceDictionary.MergedDictionaries>
    <ResourceDictionary Source="/CustomControls;component/Themes/UpDownNumeric.xaml"/>
</ResourceDictionary.MergedDictionaries>

3、再实现UpDownNumeric

基类选择Control,也可以根据目标就近选择基类(比如ItemsControl、HeaderedContentControl之类的)。想好要暴露哪些属性、事件和命令,比如在这个例子里,只要暴露1个ValueProperty、2个CommandProperty(只贴增的,减类似不重复贴了)、和1个ValueChanged事件即可。

public static readonly DependencyProperty ValueProperty =
    DependencyProperty.Register("Value", typeof(int), typeof(UpDownNumeric),
    new PropertyMetadata(new PropertyChangedCallback(OnValueChanged)));

public static readonly DependencyProperty UpCommandProperty =
    DependencyProperty.Register("UpCommand", typeof(ICommand), typeof(UpDownNumeric));

public static readonly RoutedEvent ValueChangedEvent =
    EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble,
    typeof(RoutedPropertyChangedEventHandler<int>), typeof(UpDownNumeric));

再把这些属性包装成我们熟悉的.net的属性和事件,基于DelegateCommand实现命令、和触发ValueChanged的事件即可。

4、坑1:如果逻辑里的属性与控件的属性类型不匹配,要实现相应的IValueConverter

在这个例子里, 我把int型的Value绑定到TextBox的Text,int到string不能直接绑定,因此要实现一个Int2StringConverter,并在绑定时指定。否则不会显示值。

<TextBox Text="{TemplateBinding Value, Converter={StaticResource Int2StringConverter}}"/>

5、坑2:Image.Source的Pack Uri路径

这个路径如果没写对,要么编译不通过,要么虽然编译成功但运行的时候显示不出来。这里有几点关键:

5.1、右键图片->属性->生成操作->务必选择“资源”(Resource)

请不要选择“内容”或“嵌入的资源”,也不要将图片添加到“项目资源”中。“复制到输出目录”选择“不复制”。我分别尝试了这几种生成方式,如下:
资源:生成到.g.resources
资源
内容:不生成到dll里
内容
嵌入的资源:生成到Resources文件夹中,与
.g.resources同级
嵌入的资源
项目资源:生成到*.Properties.Resources.resources
项目资源

5.2、Pack Uri路径

网上到处是copy来的一大堆理论,看的晕。我试下来,在vs的Image.Source属性窗口里选,由vs自动生成的路径总是OK的,如下:
vs
手动输入容易出错。只要生成为“资源”,下面3种写法都是正确的。
绝对路径:pack:///application:,,,/CustomControls;component/Resources/up.png
绝对路径-缩写版:/CustomControls;component/Resources/up.png
相对路径:../Resources/up.png

6、坑3:Template与控件Logic务必分离

这里的分离是指:Logic不应知道具体的控件名,Logic只暴露依赖属性(包括属性+命令),Template负责绑定属性。这种分离的好处是:如果用户不喜欢你的Template,自己再定制一个,只需绑定到同样的属性+命令,即可实现控件的行为。否则用户就无法改写Template了。

6.1、错误的实现方式
private Button upBtn;
public override void OnApplyTemplate(){
  upBtn = (Button)this.Template.FindName("btnUp", this);
  upBtn.Click = btn_Click;
}

这样写,就把Logic和Template绑死了。

6.2、正确的实现方式
//后台定义UpCommandProperty,实现为DelegateCommand或RoutedCommand,前台Template去绑定
public static readonly DependencyProperty UpCommandProperty =
  DependencyProperty.Register("UpCommand", typeof(ICommand), typeof(UpDownNumeric));

<Button Command="{TemplateBinding UpCommand}"/>
 posted on 2015-08-07 16:21  AlexanderYao  阅读(1273)  评论(1编辑  收藏  举报