[Aaronyang] 写给自己的WPF4.5 笔记13[二维自定义控件技巧-可视化状态实战,自定义容器,注册类命令,用户控件补充]

 我的文章一定要做到对读者负责,否则就是失败的文章  ---------   www.ayjs.net    aaronyang技术分享

博文摘要:欢迎大家来支持我的《2013-2015 Aaronyang的又一总结,牧童遥指纳尼村》绝对好文章

关于《写给自己的WPF4.5 笔记14,已在官网发布

1.讲解了自定义控件加入命令支持的两种手段,补充用户控件的客户定义模板

2.实战的方式讲解了无外观控件,可以让使用者定义模板,讲解模板PART,使用可视化状态组,动画的使用

效果演示:

[Aaronyang] 写给自己的WPF4.5 笔记13[二维自定义控件技巧-可视化状态实战,自定义容器,注册类命令,用户控件补充]

用户自己在原有的模版上自定义模板内容后

[Aaronyang] 写给自己的WPF4.5 笔记13[二维自定义控件技巧-可视化状态实战,自定义容器,注册类命令,用户控件补充]

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>

效果图:

[Aaronyang] 写给自己的WPF4.5 笔记13[二维自定义控件技巧-可视化状态实战,自定义容器,注册类命令,用户控件补充]

 

   =============潇洒的版权线==========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"/>

效果图:

[Aaronyang] 写给自己的WPF4.5 笔记13[二维自定义控件技巧-可视化状态实战,自定义容器,注册类命令,用户控件补充]

关于模板中 TemplatedParent,大家领悟到了,如果模板层次很多,想拿到根节点,就是用这个快捷。如果就在一级,可直接 TemplateBinding

Tip: 查找默认控件样式      

  Style a = (Style)Application.Current.FindResource(typeof(Button));

   =============潇洒的版权线==========www.ayjs.net===== Aaronyang ========= AY =========== 安徽 六安 杨洋 ==========   未经允许不许转载 =========

4. 在3的基础上,可视化状态组TemplateVisualState

除了系统自带的控件的状态组,我们自定义一些状态

[Aaronyang] 写给自己的WPF4.5 笔记13[二维自定义控件技巧-可视化状态实战,自定义容器,注册类命令,用户控件补充]

 我们修改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的登陆,点击右上角的三角,窗体会反转到对面,然后对面可以在返回回来,我们窗体是不带这两种状态的。所以需要添加自己状态,或者你自己写触发器或者事件

[Aaronyang] 写给自己的WPF4.5 笔记13[二维自定义控件技巧-可视化状态实战,自定义容器,注册类命令,用户控件补充]

窗体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[Aaronyang] 写给自己的WPF4.5 笔记13[二维自定义控件技巧-可视化状态实战,自定义容器,注册类命令,用户控件补充]  ViewState[Aaronyang] 写给自己的WPF4.5 笔记13[二维自定义控件技巧-可视化状态实战,自定义容器,注册类命令,用户控件补充]

以上图片,可以反编译vs的类的部分源码得到的。装个Reflector即可

[Aaronyang] 写给自己的WPF4.5 笔记13[二维自定义控件技巧-可视化状态实战,自定义容器,注册类命令,用户控件补充]

 后台增加是否左边依赖属性

        /// <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);

[Aaronyang] 写给自己的WPF4.5 笔记13[二维自定义控件技巧-可视化状态实战,自定义容器,注册类命令,用户控件补充]

因为切换效果很多地方使用,我们重构个方法

     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"/>

[Aaronyang] 写给自己的WPF4.5 笔记13[二维自定义控件技巧-可视化状态实战,自定义容器,注册类命令,用户控件补充]

添加后台代码

   private void btnLeftShowAyButon_Click(object sender, RoutedEventArgs e)
        {
            ayControl1.IsLeft = true;
        }

        private void btnRightShowAyButon_Click(object sender, RoutedEventArgs e)
        {
            ayControl1.IsLeft = false;
        }

动态效果图演示

[Aaronyang] 写给自己的WPF4.5 笔记13[二维自定义控件技巧-可视化状态实战,自定义容器,注册类命令,用户控件补充]

使用状态组,比故事板的好处,个人感觉,不会重复执行动画,而且更简单方便。

创建了无外观控件之后,用户就可以编辑模板,在原有的我们提供的控件基础上加减东西。

 

----我们开始在客户端重新定义模板我在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}"/>

效果图:

[Aaronyang] 写给自己的WPF4.5 笔记13[二维自定义控件技巧-可视化状态实战,自定义容器,注册类命令,用户控件补充]

虽然用户可以自定义模板,但是目前还有个问题,我必须把基础的模板提供给用户。

我的意思我写的控件,用户可以右击,然后编辑模板,然后把默认的模板全都生成给用户,花了2个小时,也没找到解决方案 
 
 
关于自定义容器
继承容器后,例如 APanel:Panel
主要有2个方法:
调整大小
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[二维自定义控件技巧-可视化状态实战,自定义容器,注册类命令,用户控件补充]

上一篇:mongodb——高性能、高可用之副本集、读写分离、分片、操作 (转)


下一篇:Django的Orm操作数据库