WPF编程宝典--自定义元素上
写在前面
说出来不怕大家笑话,我昨天看完这节的前半部分,然后根据书上的例子,自己写了一个颜色控件,居然没成功。。。今天这篇文章就给大家讲讲怎么写一个颜色控件和在这个过程中主要的几个问题。
构建基本的用户控件
创建项目
今天我们要运用我们之前学的内容来写一个颜色选择器。首先我们要创建一个用户控件库,这里就是第一个大坑,千万别学书上说的创建一个自定义控件库。如果你不信邪你可以试试。然后项目里添加一个用户控件,当然也可以使用他帮你初始化好的空白的用户控件。
定义属性,方法和事件
用户控件最简单的起点就是设计用户控件对外界公开的公共接口。就是使用者能够使用的部分。这部分我们要使用依赖项属性来定义,因为很多WPF的特性需要依赖项属性才能使用。
一个颜色选择器,主要的属性当然是Color啦。然后还有检索颜色值的属性,书上用的是RGB值,我们就沿用书上的选择。
//依赖项属性
public static DependencyProperty ColorProperty;
public static DependencyProperty RedProperty;
public static DependencyProperty GreenProperty;
public static DependencyProperty BlueProperty;
千万别忘了依赖项属性的结尾是-Property,这是程序员之间的约定。除非你想被大家群起而攻之。
为属性定义静态字段只是第一步,还需要有静态构造函数。因为依赖项属性是要注册的。
static ColorPicker()
{
ColorProperty = DependencyProperty.Register(
"Color", typeof(Color), typeof(ColorPicker),
new FrameworkPropertyMetadata(Colors.Black,
new PropertyChangedCallback(OnColorChanged)));
RedProperty = DependencyProperty.Register(
"Red", typeof(byte), typeof(ColorPicker),
new FrameworkPropertyMetadata(
new PropertyChangedCallback(OnColorRGBChanged)));
GreenProperty = DependencyProperty.Register(
"Green", typeof(byte), typeof(ColorPicker),
new FrameworkPropertyMetadata(
new PropertyChangedCallback(OnColorRGBChanged)));
BlueProperty = DependencyProperty.Register(
"Blue", typeof(byte), typeof(ColorPicker),
new FrameworkPropertyMetadata(
new PropertyChangedCallback(OnColorRGBChanged)));
}
如果这步也完成了,那么恭喜你,还差一步你就成功了,使用标准的属性封装器,让他们更加容易被访问。
public Color Color
{
get { return (Color)GetValue(ColorProperty); }
set { SetValue(ColorProperty, value); }
}
public byte Red
{
get { return (byte)GetValue(RedProperty); }
set { SetValue(RedProperty, value); }
}
public byte Green
{
get { return (byte)GetValue(GreenProperty); }
set { SetValue(GreenProperty, value); }
}
public byte Blue
{
get { return (byte)GetValue(BlueProperty); }
set { SetValue(BlueProperty, value); }
}
这样就成了,你为你的用户控件创建了四个依赖项属性。
等等,还没发现问题所在么?
我们在注册依赖项属性的时候,分别为他们注册了自己的属性变化回调事件。因为是使用RGB值来检索颜色,那么RGB变化的时候,Color属性就要相应的发生变化。
private static void OnColorRGBChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ColorPicker colorPicker = (ColorPicker)d;
Color color = colorPicker.Color;
if (e.Property == RedProperty)
{
color.R = (byte)e.NewValue;
}
else if (e.Property == GreenProperty)
{
color.G = (byte)e.NewValue;
}
else if (e.Property == BlueProperty)
{
color.B = (byte)e.NewValue;
}
colorPicker.Color = color;
}
同样的,既让RGB值变化会影响Color属性,那么Color属性变化也肯定会影响RGB值了。
private static void OnColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Color newcolor = (Color)e.NewValue;
Color oldcolor = (Color)e.OldValue;
ColorPicker colorPicker = (ColorPicker)d;
colorPicker.Red = newcolor.R;
colorPicker.Green = newcolor.G;
colorPicker.Blue = newcolor.B;
}
这里有些同学就会问了,这样Color变化影响RGB值,RGB值变化影响Color,那不是陷入了死循环么,问的好!WPF里有一个机制“不重复进入属性变化回调函数”。就是说属性变化回调函数中发生的属性变化不会触发接下来的属性变化回调函数,有点绕啊。。多读几遍你就懂了。
那么好属性和方法都定义好了,接下来就是事件了,事件的意义就是通知。所以这里我们定义一个路由事件,这样可以让事件具有冒泡或者隧道特性。因为是一个颜色选择器,所以是在Color属性发生变化的时候通知。步骤创建依赖项属性差不多,这里就不分段讲解了
//创建静态对象
public static readonly RoutedEvent ColorChangedEvent;
//静态的构造函数
static ColorPicker()
{
ColorChangedEvent = EventManager.RegisterRoutedEvent(
"ColorChanged", RoutingStrategy.Bubble,
typeof(RoutedPropertyChangedEventHandler<Color>), typeof(ColorPicker));
}
//封装器
public event RoutedPropertyChangedEventHandler<Color> ColorChanged
{
add { AddHandler(ColorChangedEvent, value); }
remove { RemoveHandler(ColorChangedEvent, value); }
}
//什么时候触发
private static void OnColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
RoutedPropertyChangedEventArgs<Color> args = new RoutedPropertyChangedEventArgs<Color>(oldcolor, newcolor);
args.RoutedEvent = ColorPicker.ColorChangedEvent;
colorPicker.RaiseEvent(args);
}
添加标记
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Slider Name="sliderRed" Minimum="0" Maximum="255" Value="{Binding ElementName=colorPicker,Path=Red}" Margin="5"></Slider>
<TextBlock Name="txtRed" Margin="5" Grid.Column="1" Width="20" Text="{Binding ElementName=sliderRed,Path=Value}"></TextBlock>
<Slider Name="sliderGreen" Minimum="0" Maximum="255" Value="{Binding ElementName=colorPicker,Path=Green}" Grid.Row="1" Margin="5"></Slider>
<TextBlock Name="txtGreen" Margin="5" Grid.Row="1" Grid.Column="1" Width="20" Text="{Binding ElementName=sliderGreen,Path=Value}"></TextBlock>
<Slider Name="sliderBlue" Minimum="0" Maximum="255" Value="{Binding ElementName=colorPicker,Path=Blue}" Grid.Row="2" Margin="5"></Slider>
<TextBlock Name="txtBlue" Margin="5" Grid.Row="2" Grid.Column="1" Width="20" Text="{Binding ElementName=sliderBlue,Path=Value}"></TextBlock>
<Rectangle Grid.Column="2" Grid.RowSpan="3" Width="50" Stroke="Black" StrokeThickness="1" Margin="5">
<Rectangle.Fill>
<SolidColorBrush Color="{Binding ElementName=colorPicker,Path=Color}"></SolidColorBrush>
</Rectangle.Fill>
</Rectangle>
</Grid>
使用教程
可能很多同学看到这个使用教程就不想看了,心想使用还不简单么,不好意思还真不简单,我就是在这一步上卡住了,用户控件的使用,首先要编译用户控件库生成一个.dll文件,然后就像使用一般的控件一样使用。但是要注意的是,我们在使用后,可能觉得这里不太满意哪里不太满意。就会更改用户控件,这个时候使用此控件的页面,控件上会出现一个感叹号。这个时候要重新编译用户控件更新.dll文件才行。
写在结尾
今天的教学就到这里了,之所以是上是因为后面有一半内容还没看,可能看过觉得不太主要我就不写了,而且感觉自己最近对待学习有些懈怠了,最近可能要调整一下自己的心态了,更新时间可能会长一点。干我们这行的不怕你不会就怕你不学,因为软件这行更新换代的时间太短了,你可能一没注意就落后了一个版本了,但有时候一打游戏就停不下来,同学们有什么克制自己的办法也可以和我分享一下,与君共勉!