知识回顾
接上篇,回顾这三篇讲了什么东西
首先说明了属性的现存问题,然后介绍了依赖属性的基本用法及其解决方案,由于依赖属性以静态属性的方式存在,进而又介绍了可重写的属性元数据的使用.这是以上三篇所说明的问题.当然依赖属性的特性依然没有说完整.这两天也一直在想依赖属性,然后就是头疼,呵呵.由于WPF的依赖属性系统与整体功能都有关联,所以接下来等讲到某个特性的时候然后再来讲依赖属性.这篇我们来个简单的实践,增加点乐趣.
定义RadioButtonList和CheckBoxList
WPF内置没有这两个控件,但实际开发时,用到的会比较多.下面我们一起来创建这两个控件.
- 明确控件需求
- 定义依赖属性
- 重写默认样式属性元数据
- 用xaml定义控件样式
一.明确控件需求
控件功能如下:
- 可以垂直,水平排列
- 可以分行,列
- 为子项设置Margin
二.定义依赖属性
为避免重复定义属性,为两个控件定义公共类ListControl,从ListBox继承
然后定义依赖属性
namespace WPF.Controls { /// <summary> /// this is a base class for CheckBoxList and RadioButtonList /// this class define some common property /// </summary> public abstract class ListControl : ListBox { #region Orientation /// <summary> /// Gets or sets a value that indicates the dimension by which child elements are stacked. /// This is a dependency property. /// </summary> public Orientation Orientation { get { return (Orientation)GetValue(OrientationProperty); } set { SetValue(OrientationProperty, value); } } public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register("Orientation", typeof(Orientation), typeof(ListControl), new UIPropertyMetadata(Orientation.Vertical, new PropertyChangedCallback(OrientationChangedCallback))); public static void OrientationChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) { ListControl control = d as ListControl; if (control.Orientation == Orientation.Horizontal) { control.Columns = 0; control.Rows = 1; } else if (control.Orientation == Orientation.Vertical) { control.Rows = 0; control.Columns = 1; } } #endregion #region Columns /// <summary> /// Get or set a value that indicates which Columns list item should appear in. /// This is a dependency property. /// </summary> public int Columns { get { return (int)GetValue(ColumnsProperty); } set { SetValue(ColumnsProperty, value); } } public static readonly DependencyProperty ColumnsProperty = DependencyProperty.Register("Columns", typeof(int), typeof(ListControl), new UIPropertyMetadata(1)); #endregion #region Rows /// <summary> /// Get or set a value that indicates which Rows list item should appear in. /// This is a dependency property. /// </summary> public int Rows { get { return (int)GetValue(RowsProperty); } set { SetValue(RowsProperty, value); } } public static readonly DependencyProperty RowsProperty = DependencyProperty.Register("Rows", typeof(int), typeof(ListControl), new UIPropertyMetadata(0)); #endregion public Thickness SubMargin { get { return (Thickness)GetValue(SubMarginProperty); } set { SetValue(SubMarginProperty, value); } } public static readonly DependencyProperty SubMarginProperty = DependencyProperty.Register("SubMargin", typeof(Thickness), typeof(ListControl), new UIPropertyMetadata(new Thickness(2,2,2,0))); } }
以上依赖属性定义的非常简单,之所以定义成依赖属性是因为可以进行属性绑定操作.
三.重写默认样式属性元数据
/// <summary> /// Represents a control that a user can choose one from list options in a group radiobutton /// </summary> public class RadioButtonList : ListControl { static RadioButtonList() { DefaultStyleKeyProperty.OverrideMetadata(typeof(RadioButtonList) , new FrameworkPropertyMetadata(typeof(RadioButtonList))); } } /// <summary> /// Contains a list of selectable CheckBox items /// Represents a control that a user can choose from a list options in a group of CheckBox /// </summary> public class CheckBoxList : ListControl { static CheckBoxList() { DefaultStyleKeyProperty.OverrideMetadata(typeof(CheckBoxList) , new FrameworkPropertyMetadata(typeof(CheckBoxList))); } }
四.用xaml定义控件样式
默认项目中会有一个Generic.xaml文件,我们要把样式定义在此文件中
控件样式定义
(1)ListControl
<Style TargetType="{x:Type local:ListControl}" BasedOn="{StaticResource {x:Type ListBox}}"> <Setter Property="ItemsPanel"> <Setter.Value> <ItemsPanelTemplate> <UniformGrid HorizontalAlignment="Left" VerticalAlignment="Top" Rows="{Binding Path=Rows,RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type local:ListControl}}}" Columns="{Binding Path=Columns,RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type local:ListControl}}}"></UniformGrid> </ItemsPanelTemplate> </Setter.Value> </Setter> <Setter Property="BorderThickness" Value="0" /> <Setter Property="Background" Value="Transparent" /> </Style>(2)RadioButtonList
<Style TargetType="{x:Type local:RadioButtonList}" BasedOn="{StaticResource {x:Type local:ListControl}}"> <Style.Resources> <Style TargetType="ListBoxItem"> <Setter Property="Margin" Value="2, 2, 2, 0" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ListBoxItem"> <RadioButton Margin="{Binding Path=SubMargin,RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type local:RadioButtonList}}}" IsChecked="{Binding IsSelected, RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}"> <RadioButton.Content> <ContentPresenter/> </RadioButton.Content> </RadioButton> </ControlTemplate> </Setter.Value> </Setter> </Style> </Style.Resources> </Style>(3)CheckBoxList
<Style TargetType="{x:Type local:CheckBoxList}" BasedOn="{StaticResource {x:Type local:ListControl}}"> <Setter Property="SelectionMode" Value="Multiple"></Setter> <Style.Resources> <Style TargetType="ListBoxItem"> <Setter Property="Margin" Value="2, 2, 2, 0" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ListBoxItem"> <CheckBox Margin="{Binding Path=SubMargin,RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type local:RadioButtonList}}}" IsChecked="{Binding IsSelected, RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}"> <CheckBox.Content> <ContentPresenter/> </CheckBox.Content> </CheckBox> </ControlTemplate> </Setter.Value> </Setter> </Style> </Style.Resources> </Style>
OK,到这里就完事了.我们发现我们并不需要写多少代码,就可以实现一个控件.而且RadioButtonList和CheckBoxList几乎没有代码,仅仅只是重写了样式而已,这也是WPF定义控件的基本概念,一些变的都是如此简单.