我的文章一定要做到对读者负责,否则就是失败的文章 --------- www.ayjs.net aaronyang技术分享
博文摘要:欢迎大家来支持我的《2013-2015 Aaronyang的又一总结,牧童遥指纳尼村》绝对好文章
1.讲解了自定义控件加入命令支持的两种手段,补充用户控件的客户定义模板
2.实战的方式讲解了无外观控件,可以让使用者定义模板,讲解模板PART,使用可视化状态组,动画的使用
效果演示:
用户自己在原有的模版上自定义模板内容后
3.深入浅出 自定义容器,继承控件的额外知识,未涉及自定义绘图元素的讲解
4.源码下载:下载
=============潇洒的版权线==========www.ayjs.net===== Aaronyang ========= AY =========== 安徽 六安 杨洋 ========== 未经允许不许转载 =========
文章正文:
1. 用户控件手段3 添加命令,类似window的添加命令,如果命令不熟悉:查看我写的命令文章
类似参考代码,跟窗体中一样,这里使用wpf内置的命令,你也可以自定义命令(自定义命令值,文本,快捷键,快捷键文本等操作),然后客户端使用就使用命令的知识触发命令:
public AyImageButton() { InitializeComponent(); SetUpCommands(); } private void SetUpCommands() { CommandBinding binding = new CommandBinding(ApplicationCommands.Undo, UndoCommand_Executed, UndoCommand_CanExecute); this.CommandBindings.Add(binding); }
private static void UndoCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e){} private static void UndoCommand_Executed(object sender, ExecutedRoutedEventArgs e){}
硬编码命令,可以像文本框那样设置Cut,Copy命令,而让用户无法获得,而CommandBinding用户可以拿到
CommandManager.RegisterClassCommandBinding(typeof(AyImageButton), new CommandBinding(ApplicationCommands.Undo, UndoCommand_Executed, Undocommand_CanExecute));
也可以硬编码事件,类事件处理程序总在实例事件处理程序之前调用
EventManager.RegisterClassHandler()
2.每个UserControl控件,也算是个ContentPresenter 接着你可以在外层加东西
<Window.Resources> <Style TargetType="{x:Type local:AyImageButton}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate> <Border BorderBrush="Red" BorderThickness="1" CornerRadius="5"> <ContentPresenter Content="{TemplateBinding ContentControl.Content}"/> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> </Window.Resources>
效果图:
=============潇洒的版权线==========www.ayjs.net===== Aaronyang ========= AY =========== 安徽 六安 杨洋 ========== 未经允许不许转载 =========
3.无外观的控件,类与xaml样式分离。使用DefaultStyleKeyProperty的方法清空外观。
新建AyControl类继承Control类
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Media; namespace WpfApplication4 { [TemplatePart(Name = "PART_TbColor", Type = typeof(TextBox))] [TemplatePart(Name = "PART_YuLanBrush", Type = typeof(SolidColorBrush))] public class AyControl:Control { static AyControl() { System.Windows.FrameworkElement.DefaultStyleKeyProperty.OverrideMetadata(typeof(AyControl), new FrameworkPropertyMetadata(typeof(AyControl))); } } }
使用TemplatePart定义模板必须元素,指定类型Type,命名规则:PART_
我们指定模板中必须有这两个指定类型的元素,名称都取好了,然后重写 OnApplyTemplate()模板,找到该元素,设置绑定
public Color CurrColor { get { return (Color)GetValue(CurrColorProperty); } set { SetValue(CurrColorProperty, value); } } // Using a DependencyProperty as the backing store for CurrColor. This enables animation, styling, binding, etc... public static readonly DependencyProperty CurrColorProperty = DependencyProperty.Register("CurrColor", typeof(Color), typeof(AyControl), new PropertyMetadata(Colors.Black)); public override void OnApplyTemplate() { SolidColorBrush brush = GetTemplateChild("PART_YuLanBrush") as SolidColorBrush; if (brush != null) { Binding binding = new Binding("CurrColor"); binding.Source = brush; binding.Mode = BindingMode.OneWayToSource; this.SetBinding(AyControl.CurrColorProperty, binding); } base.OnApplyTemplate(); }
新建Themes文件夹,新建AyControl.xaml。然后新建generic.xaml必须这个名字
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="/WpfApplication4;component/themes/AyControl.xaml" /> </ResourceDictionary.MergedDictionaries> </ResourceDictionary>
OK,我们来写个输入输入例如 #f00 右侧转换成对应的颜色
原理:利用定义的TemplatePart,获取模板中的控件然后操作,例如绑定等
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Media; using System.Windows.Shapes; namespace WpfApplication4 { [TemplatePart(Name = "PART_TbColor", Type = typeof(TextBox))] [TemplatePart(Name = "PART_YuLanRec", Type = typeof(Rectangle))] public class AyControl:Control { static AyControl() { System.Windows.FrameworkElement.DefaultStyleKeyProperty.OverrideMetadata(typeof(AyControl), new FrameworkPropertyMetadata(typeof(AyControl))); } public Color CurrColor { get { return (Color)GetValue(CurrColorProperty); } set { SetValue(CurrColorProperty, value); } } // Using a DependencyProperty as the backing store for CurrColor. This enables animation, styling, binding, etc... public static readonly DependencyProperty CurrColorProperty = DependencyProperty.Register("CurrColor", typeof(Color), typeof(AyControl), new PropertyMetadata(Colors.Black)); public override void OnApplyTemplate() { //SolidColorBrush brush = GetTemplateChild("PART_YuLanBrush") as SolidColorBrush; //if (brush != null) //{ // Binding binding = new Binding("CurrColor"); // binding.Source = brush; // binding.Mode = BindingMode.OneWayToSource; // this.SetBinding(AyControl.CurrColorProperty, binding); //} TextBox txt = GetTemplateChild("PART_TbColor") as TextBox; txt.TextChanged += txt_TextChanged; base.OnApplyTemplate(); } void txt_TextChanged(object sender, TextChangedEventArgs e) { TextBox txt = sender as TextBox; if (txt != null) { string text = txt.Text.Trim(‘#‘); if (text.Length == 3 | text.Length == 6) { BrushConverter converter = new BrushConverter(); try { Brush newFill = (Brush)converter.ConvertFromString("#" + text); CurrColor = ((SolidColorBrush)newFill).Color; } catch { } } } } } }
AyControl.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApplication4"> <Style TargetType="{x:Type local:AyControl}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:AyControl}"> <StackPanel Orientation="Horizontal"> <TextBox Width="100" Height="30" VerticalAlignment="Center" Text="" Name="PART_TbColor" Margin="0,0,2,0"></TextBox> <Rectangle Margin="{TemplateBinding Padding}" Width="50" Stroke="Black" StrokeThickness="1" Name="PART_YuLanRec"> <Rectangle.Fill> <SolidColorBrush Color="{Binding Path=CurrColor, RelativeSource={RelativeSource TemplatedParent}}"></SolidColorBrush> </Rectangle.Fill> </Rectangle> </StackPanel> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>
使用:Canvas只是容器的附加属性,无需关注
<local:AyControl CurrColor="Yellow" Canvas.Left="27" Canvas.Top="129"/>
效果图:
关于模板中 TemplatedParent,大家领悟到了,如果模板层次很多,想拿到根节点,就是用这个快捷。如果就在一级,可直接 TemplateBinding
Tip: 查找默认控件样式
Style a = (Style)Application.Current.FindResource(typeof(Button));
=============潇洒的版权线==========www.ayjs.net===== Aaronyang ========= AY =========== 安徽 六安 杨洋 ========== 未经允许不许转载 =========
4. 在3的基础上,可视化状态组TemplateVisualState
除了系统自带的控件的状态组,我们自定义一些状态
我们修改xaml的布局为canvas,方便好动画控制
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApplication4"> <Style TargetType="{x:Type local:AyControl}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:AyControl}"> <Canvas> <TextBox Canvas.Left="0" Width="100" Height="30" VerticalAlignment="Center" Text="" Name="PART_TbColor"></TextBox> <Rectangle Canvas.Left="102" Margin="{TemplateBinding Padding}" Width="50" Height="30" Stroke="Black" StrokeThickness="1" Name="PART_YuLanRec"> <Rectangle.Fill> <SolidColorBrush Color="{Binding Path=CurrColor, RelativeSource={RelativeSource TemplatedParent}}"></SolidColorBrush> </Rectangle.Fill> </Rectangle> </Canvas> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>
我们的需求: 颜色框左侧状态,颜色框右侧状态,复杂一点的例如qq2015的登陆,点击右上角的三角,窗体会反转到对面,然后对面可以在返回回来,我们窗体是不带这两种状态的。所以需要添加自己状态,或者你自己写触发器或者事件
窗体3d没研究过,有时间去看看,一定分享
[TemplateVisualState(Name = "LeftShow", GroupName = "ViewStates")] [TemplateVisualState(Name = "RightShow", GroupName = "ViewStates")] public class AyControl:Control
然后对应的xaml文件写出基本结构
<VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="ViewStates"> <VisualStateGroup.Transitions> <VisualTransition GeneratedDuration="0:0:0.7" To="LeftShow"> <Storyboard> </Storyboard> </VisualTransition> <VisualTransition GeneratedDuration="0:0:0.7" To="RightShow"> <Storyboard> </Storyboard> </VisualTransition> </VisualStateGroup.Transitions> <VisualState x:Name="LeftShow"> <Storyboard> </Storyboard> </VisualState> <VisualState x:Name="RightShow"> <Storyboard> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups>
VisualTransition ViewState
以上图片,可以反编译vs的类的部分源码得到的。装个Reflector即可
后台增加是否左边依赖属性
/// <summary> /// 是否左边 /// </summary> public bool IsLeft { get { return (bool)GetValue(IsLeftProperty); } set { SetValue(IsLeftProperty, value); } } // Using a DependencyProperty as the backing store for IsLeft. This enables animation, styling, binding, etc... public static readonly DependencyProperty IsLeftProperty = DependencyProperty.Register("IsLeft", typeof(bool), typeof(AyControl), new PropertyMetadata(false));
打开Blend,录制简单的动画,把动画代码扣下来<blend的用法很简单,录制帧>
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions" x:Class="WpfApplication5.MainWindow" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <Storyboard x:Key="LeftShowStory"> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(Canvas.Left)" Storyboard.TargetName="PART_YuLanRec"> <EasingDoubleKeyFrame KeyTime="0" Value="102"/> <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(Canvas.Left)" Storyboard.TargetName="PART_TbColor"> <EasingDoubleKeyFrame KeyTime="0" Value="0"/> <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="52"/> </DoubleAnimationUsingKeyFrames> </Storyboard> <Storyboard x:Key="RightShowStory"> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(Canvas.Left)" Storyboard.TargetName="PART_TbColor"> <EasingDoubleKeyFrame KeyTime="0" Value="52"/> <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(Canvas.Left)" Storyboard.TargetName="PART_YuLanRec"> <EasingDoubleKeyFrame KeyTime="0" Value="0"/> <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="102"/> </DoubleAnimationUsingKeyFrames> </Storyboard> </Window.Resources> <Canvas> <TextBox Canvas.Left="0" Width="100" Height="30" VerticalAlignment="Center" Text="" x:Name="PART_TbColor"/> <Rectangle Canvas.Left="102" Width="50" Height="30" Stroke="Black" StrokeThickness="1" x:Name="PART_YuLanRec" Fill="#ccc"/> <Button Content="切换状态 左侧" Canvas.Left="218" Canvas.Top="30" Width="84"> <i:Interaction.Triggers> <i:EventTrigger EventName="Click"> <ei:ControlStoryboardAction Storyboard="{StaticResource LeftShowStory}"/> </i:EventTrigger> </i:Interaction.Triggers> </Button> <Button Content="切换状态 右侧" Canvas.Left="343" Canvas.Top="30" Width="84"> <i:Interaction.Triggers> <i:EventTrigger EventName="Click"> <ei:ControlStoryboardAction Storyboard="{StaticResource RightShowStory}"/> </i:EventTrigger> </i:Interaction.Triggers> </Button> </Canvas> </Window>
然后我在ViewState中添加默认状态,当然你也可以直接在里面写动画,全部转移过去,关于Transitions中,也可以写,是个全局过度,From不写表示从任何状态,到指定状态,都执行以下动画,当然这个demo比较简单,其实可以直接在ViewState中写就好了,但是也为了演示VisualStateGroup.Transitions的用法,还是写的复杂点,我们把过渡的代码的写到VisualStateGroup.Transitions里面。
第一步:
先标记两个状态值的最终状态,如下代码,左侧时候,颜色框距离左边0.文本框是52, 右侧时候,颜色框距离左边102,文本框是0
<VisualState x:Name="LeftShow"> <Storyboard> <DoubleAnimation Storyboard.TargetName="PART_YuLanRec" Storyboard.TargetProperty="(Canvas.Left)" To="0" Duration="0" ></DoubleAnimation> <DoubleAnimation Storyboard.TargetName="PART_TbColor" Storyboard.TargetProperty="(Canvas.Left)" To="52" Duration="0" ></DoubleAnimation> </Storyboard> </VisualState> <VisualState x:Name="RightShow"> <Storyboard> <DoubleAnimation Storyboard.TargetName="PART_YuLanRec" Storyboard.TargetProperty="(Canvas.Left)" To="102" Duration="0" ></DoubleAnimation> <DoubleAnimation Storyboard.TargetName="PART_TbColor" Storyboard.TargetProperty="(Canvas.Left)" To="0" Duration="0" ></DoubleAnimation> </Storyboard> </VisualState>
当我们使用过度的时候,任何状态to 指定状态,执行下面的动画,以达到模板内指定控件应该最终的值,动画跟上面一样,只是加个过度,但是这样有个好处,即使不管颜色框和文本框的位置,都可以执行动画,流畅的过渡到对应的值,没必要考虑一开始的位置
<VisualStateGroup.Transitions> <VisualTransition GeneratedDuration="0:0:0.5" To="LeftShow"> <Storyboard> <DoubleAnimation Storyboard.TargetName="PART_YuLanRec" Storyboard.TargetProperty="(Canvas.Left)" To="0" Duration="0:0:0.5" ></DoubleAnimation> <DoubleAnimation Storyboard.TargetName="PART_TbColor" Storyboard.TargetProperty="(Canvas.Left)" To="52" Duration="0:0:0.5" ></DoubleAnimation> </Storyboard> </VisualTransition> <VisualTransition GeneratedDuration="0:0:0.5" To="RightShow"> <Storyboard> <DoubleAnimation Storyboard.TargetName="PART_YuLanRec" Storyboard.TargetProperty="(Canvas.Left)" To="102" Duration="0:0:0.5" ></DoubleAnimation> <DoubleAnimation Storyboard.TargetName="PART_TbColor" Storyboard.TargetProperty="(Canvas.Left)" To="0" Duration="0:0:0.5" ></DoubleAnimation> </Storyboard> </VisualTransition> </VisualStateGroup.Transitions>
写好xaml的状态组后,我们需要在对应的控件代码里使用,例如如下方法调用,第三个参数表示是否使用过渡,第二个是xaml中定义的VisualState的名字,如果不使用我们定义的过渡动画,直接左侧或者右侧了,相当于赋值的效果
VisualStateManager.GoToState(this, "RightShow", true或者false);
因为切换效果很多地方使用,我们重构个方法
private void ChangeVisualState(bool useTransitions) { if (!this.IsLeft) { VisualStateManager.GoToState(this, "RightShow", useTransitions); } else { VisualStateManager.GoToState(this, "LeftShow", useTransitions); } }
大致需要在2个地方调用,第一个过渡值,第二个OnApplyTemplate()方法里的base.OnApplyTemplate()之后,指定默认的状态,且不使用过渡效果,直接显示,不然太做作了。
/// <summary> /// 是否左边 /// </summary> public bool IsLeft { get { return (bool)GetValue(IsLeftProperty); } set { SetValue(IsLeftProperty, value); ChangeVisualState(true);//false表示是否调用Transitions } } private void ChangeVisualState(bool useTransitions) { if (!this.IsLeft) { VisualStateManager.GoToState(this, "RightShow", useTransitions); } else { VisualStateManager.GoToState(this, "LeftShow", useTransitions); } }public override void OnApplyTemplate() { //SolidColorBrush brush = GetTemplateChild("PART_YuLanBrush") as SolidColorBrush; //if (brush != null) //{ // Binding binding = new Binding("CurrColor"); // binding.Source = brush; // binding.Mode = BindingMode.OneWayToSource; // this.SetBinding(AyControl.CurrColorProperty, binding); //} TextBox txt = GetTemplateChild("PART_TbColor") as TextBox; txt.TextChanged += txt_TextChanged; base.OnApplyTemplate(); this.ChangeVisualState(false); //Style a = (Style)Application.Current.FindResource(typeof(Button)); }
使用DEMO,我们只需要更改IsLeft属性即可,就可以触发状态
<local:AyControl CurrColor="Yellow" Canvas.Left="27" Canvas.Top="129" x:Name="ayControl1"/> <Button Content="←颜色框左侧" Canvas.Top="190" Width="94" x:Name="btnLeftShowAyButon" Click="btnLeftShowAyButon_Click"/> <Button Content="颜色框右侧→" Canvas.Left="99" Canvas.Top="190" Width="94" x:Name="btnRightShowAyButon" Click="btnRightShowAyButon_Click"/>
添加后台代码
private void btnLeftShowAyButon_Click(object sender, RoutedEventArgs e) { ayControl1.IsLeft = true; } private void btnRightShowAyButon_Click(object sender, RoutedEventArgs e) { ayControl1.IsLeft = false; }
动态效果图演示
使用状态组,比故事板的好处,个人感觉,不会重复执行动画,而且更简单方便。
创建了无外观控件之后,用户就可以编辑模板,在原有的我们提供的控件基础上加减东西。
----我们开始在客户端重新定义模板我在blend中画好了动画后,将动画代码移到了ViewStates中去了,你发现此时已经没有VisualStateGroup.Transitions了,因为我们是重写模板的。就是假设我们是控件使用人。
<ControlTemplate x:Key="AyControlTemplate" TargetType="local:AyControl"> <Canvas> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="ViewStates"> <VisualState x:Name="LeftShow"> <Storyboard> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)" Storyboard.TargetName="PART_YuLanRec"> <EasingDoubleKeyFrame KeyTime="0" Value="1"/> <EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="0.2"/> <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="1"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)" Storyboard.TargetName="PART_YuLanRec"> <EasingDoubleKeyFrame KeyTime="0" Value="1"/> <EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="0.2"/> <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="1"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(Canvas.Left)" Storyboard.TargetName="PART_YuLanRec"> <EasingDoubleKeyFrame KeyTime="0" Value="102"/> <EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="51"/> <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[1].(SkewTransform.AngleX)" Storyboard.TargetName="PART_TbColor"> <EasingDoubleKeyFrame KeyTime="0" Value="0"/> <EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="0"/> <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)" Storyboard.TargetName="PART_TbColor"> <EasingDoubleKeyFrame KeyTime="0" Value="1"/> <EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="0.2"/> <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="1"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)" Storyboard.TargetName="PART_TbColor"> <EasingDoubleKeyFrame KeyTime="0" Value="1"/> <EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="0.2"/> <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="1"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(Canvas.Left)" Storyboard.TargetName="PART_TbColor"> <EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="0"/> <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="52"/> </DoubleAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x:Name="RightShow"> <Storyboard> <DoubleAnimation Storyboard.TargetName="PART_YuLanRec" Storyboard.TargetProperty="(Canvas.Left)" To="102" Duration="0:0:0.5" ></DoubleAnimation> <DoubleAnimation Storyboard.TargetName="PART_TbColor" Storyboard.TargetProperty="(Canvas.Left)" To="0" Duration="0:0:0.5" ></DoubleAnimation> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <TextBox Canvas.Left="0" Width="100" Height="30" VerticalAlignment="Center" Text="" Name="PART_TbColor" RenderTransformOrigin="0.5,0.5"> <TextBox.RenderTransform> <TransformGroup> <ScaleTransform/> <SkewTransform/> <RotateTransform/> <TranslateTransform/> </TransformGroup> </TextBox.RenderTransform> </TextBox> <Rectangle Canvas.Left="102" Margin="{TemplateBinding Padding}" Width="50" Height="30" Stroke="Black" StrokeThickness="1" Name="PART_YuLanRec" RenderTransformOrigin="0.5,0.5"> <Rectangle.RenderTransform> <TransformGroup> <ScaleTransform/> <SkewTransform/> <RotateTransform/> <TranslateTransform/> </TransformGroup> </Rectangle.RenderTransform> <Rectangle.Fill> <SolidColorBrush Color="{Binding Path=CurrColor, RelativeSource={RelativeSource TemplatedParent}}"></SolidColorBrush> </Rectangle.Fill> </Rectangle> </Canvas> </ControlTemplate>
接着使用这个模板
<local:AyControl CurrColor="Yellow" Canvas.Left="27" Canvas.Top="129" x:Name="ayControl1" Template="{StaticResource AyControlTemplate}"/>
效果图:
虽然用户可以自定义模板,但是目前还有个问题,我必须把基础的模板提供给用户。
protected override Size MeasureOverride(Size constraint)
排版元素
protected override Size ArrangeOverride(Size arrangeBounds)
拿到容器内的控件
UIElementCollection elements = base.InternalChildren;
容器一般经常用到附加属性,给Children标记,然后好调整位置等
关于附加属性,默认值行为,表示添加元素或者减少元素是否Arrange是否Measure,可以理解是否调用上面的2个方法,重新排版元素
FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata(); metadata.AffectsArrange = true; metadata.AffectsMeasure = true;
关于继承指定控件,例如Textbox的一段代码可以启发
FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata(); metadata.CoerceValueCallback = 事件; TextProperty.OverrideMetadata(typeof(MaskedTextBox), metadata);
关于CoerceValueCallback,MSDN地址 表示验证对象的值
关于自定义绘图元素,就比较难理解了,暂时还不太会。也无法讲解,典型的案例,例如Visio做流程图,一块一块元素其实是定义好的,可以很diy绘制。
===========潇洒的版权线========www.ayjs.net===== Aaronyang ====== AY ======= 安徽 六安 杨洋 ====== 未经允许不许转载 =======
---------------小小的推荐,作者的肯定,读者的支持。推不推荐不重要,重要的是希望大家能把WPF推广出去,别让这么好的技术消失了,求求了,让我们为WPF技术做一份贡献。---------------
[Aaronyang] 写给自己的WPF4.5 笔记13[二维自定义控件技巧-可视化状态实战,自定义容器,注册类命令,用户控件补充]