c# WPF中自定义加载时实现带动画效果的Form和FormItem

背景

  今天我们来谈一下我们自定义的一组WPF控件Form和FormItem,然后看一下如何自定义一组完整地组合WPF控件,在我们很多界面显示的时候我们需要同时显示文本、图片并且我们需要将这些按照特定的顺序整齐的排列在一起,这样的操作当然通过定义Grid和StackPanel然后组合在一起当然也是可以的,我们的这一组控件就是将这个过程组合到一个Form和FormItem中间去,从而达到这样的效果,我们首先来看看这组控件实现的效果。

一 动画效果

  看了这个效果之后我们来看看怎么来使用Form和FormItem控件,后面再进一步分析这两个控件的一些细节信息。

<xui:TabControl Canvas.Left="356" Canvas.Top="220">
                   <xui:TabItem Header="Overview">
                       <xui:Form Margin="2" >
                           <xui:FormItem Content="Test1" Height="30"></xui:FormItem>
                           <xui:FormItem Content="Test2" Height="30"></xui:FormItem>
                           <xui:FormItem Content="Test3" Height="30"></xui:FormItem>
                       </xui:Form>
                   </xui:TabItem>
                   <xui:TabItem Header="Plumbing">
                       <xui:Form Margin="2" Columns="2" Rows="2">
                           <xui:FormItem Content="Demo1" Height="30" Margin="5 2"></xui:FormItem>
                           <xui:FormItem Content="Demo2" Height="30" Margin="5 2"></xui:FormItem>
                           <xui:FormItem Content="Demo3" Height="30" Margin="5 2"></xui:FormItem>
                           <xui:FormItem Content="Demo4" Height="30" Margin="5 2"></xui:FormItem>
                       </xui:Form>
                   </xui:TabItem>
                   <xui:TabItem Header="Clean">
                       <xui:TextBox Text="Test2" Width="220" Height=" 30" Margin="5"></xui:TextBox>
                   </xui:TabItem>
               </xui:TabControl>

  这个里面xui命名控件是我们的自定义控件库的命名空间,这个里面的TableControl也是一种特殊的自定义的TableControl,关于这个TableControl我们后面也会进一步分析。

二 自定义控件实现

  按照上面的顺序我们先来分析Form控件,然后再分析FormItem控件的实现细节

  2.1 Form

  通过上面的代码我们发现Form是可以承载FormItem的,所以它是一个可以承载子控件的容器控件,这里Form是集成ItemsControl的,我们来看看具体的代码

public class Form : ItemsControl
    {
        static Form()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(Form), new FrameworkPropertyMetadata(typeof(Form)));
        }
 
        public double HeaderWidth
        {
            get { return (double)GetValue(HeaderWidthProperty); }
            set { SetValue(HeaderWidthProperty, value); }
        }
 
        // Using a DependencyProperty as the backing store for HeaderWidth.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty HeaderWidthProperty =
            DependencyProperty.Register("HeaderWidth", typeof(double), typeof(Form), new PropertyMetadata(70D));
 
        public int Rows
        {
            get { return (int)GetValue(RowsProperty); }
            set { SetValue(RowsProperty, value); }
        }
 
        // Using a DependencyProperty as the backing store for Rows.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty RowsProperty =
            DependencyProperty.Register("Rows", typeof(int), typeof(Form), new PropertyMetadata(0));
 
        public int Columns
        {
            get { return (int)GetValue(ColumnsProperty); }
            set { SetValue(ColumnsProperty, value); }
        }
 
        // Using a DependencyProperty as the backing store for Columns.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ColumnsProperty =
            DependencyProperty.Register("Columns", typeof(int), typeof(Form), new PropertyMetadata(1));
 
    }  

  然后我们再来看看Form的样式文件

<Style TargetType="local:Form">
        <Setter Property="Padding" Value="10"></Setter>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:Form">
                    <ScrollViewer Background="#eee" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
                        <UniformGrid Columns="{TemplateBinding Columns}" Rows="{TemplateBinding Rows}" IsItemsHost="True" Background="Transparent" VerticalAlignment="Top" Margin="{TemplateBinding Padding}"></UniformGrid>
                    </ScrollViewer>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="ItemTemplate">
            <Setter.Value>
                <DataTemplate>
                    <ContentPresenter Content="{Binding}" Focusable="False"></ContentPresenter>
                </DataTemplate>
            </Setter.Value>
        </Setter>
    </Style>  

  这里我们使用UniformGrid作为内容承载容器,所以我们现在清楚了为什么需要定义Columns和Rows这两个依赖项属性了,这个UniformGrid是嵌套在ScrollerViewer中的,所以如果其子控件超出了一定范围,其子控件外面是会显示滚动条的。

  2.2 FormItem

  FormItem是从ListBoxItem继承而来,而ListBoxItem又是从ContentControl继承而来的,所以可以添加到任何具有Content属性的控件中去,常见的ListBoxItem可以放到ListBox中,也可以放到ItemsControl中去,ListBoxItem可以横向和TreeViewItem进行比较,只不过TreeViewItem是直接从HeaderedItemsControl继承过来的,然后再继承自ItemsControl。两者有很多的共同之处,可以做更多的横向比较,我们今天只是来讲ListBoxItem,首先看看我们使用的样式,这里贴出前端代码:

<Style TargetType="local:FormItem">
        <Setter Property="Margin" Value="0 0 0 15"></Setter>
        <Setter Property="Background" Value="#fff"></Setter>
        <Setter Property="Height" Value="50"></Setter>
        <Setter Property="HorizontalAlignment" Value="Stretch"></Setter>
        <Setter Property="VerticalAlignment" Value="Stretch"></Setter>
        <Setter Property="Padding" Value="6"></Setter>
        <Setter Property="Foreground" Value="{StaticResource DarkColor}"></Setter>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:FormItem">
                    <Grid Background="{TemplateBinding Background}" Height="{TemplateBinding Height}">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="{Binding HeaderWidth,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=local:Form}}"></ColumnDefinition>
                            <ColumnDefinition Width="*"></ColumnDefinition>
                        </Grid.ColumnDefinitions>
                        <Rectangle Width="3" HorizontalAlignment="Left" Fill="{StaticResource Highlight}"></Rectangle>
                        <StackPanel VerticalAlignment="Center" HorizontalAlignment="Left" Margin="13 0 0 0" Orientation="Horizontal">
                            <Image x:Name="Icon" Source="{TemplateBinding Icon}" Width="24" Height="24" Margin="0 0 10 0" VerticalAlignment="Center" HorizontalAlignment="Left"></Image>
                            <TextBlock Text="{TemplateBinding Title}" Foreground="{TemplateBinding Foreground}" VerticalAlignment="Center" HorizontalAlignment="Left"></TextBlock>
                        </StackPanel>
                        <ContentPresenter Margin="{TemplateBinding Padding}" Grid.Column="1" HorizontalAlignment="{TemplateBinding HorizontalAlignment}" VerticalAlignment="{TemplateBinding VerticalAlignment}"></ContentPresenter>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="Icon" Value="{x:Null}">
                            <Setter Property="Visibility" Value="Collapsed" TargetName="Icon"></Setter>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>  

  这里我们重写了ListBoxItem 的ControlTemplate,我们需要注意的一个地方就是我们使用了

<ContentPresenter Margin="{TemplateBinding Padding}" Grid.Column="1" HorizontalAlignment="{TemplateBinding HorizontalAlignment}" VerticalAlignment="{TemplateBinding VerticalAlignment}"></ContentPresenter> 

来替代ListBoxItem的Content,我们需要始终记住,只有控件拥有Content属性才能使用ContentPresenter ,这个属性是用来呈现控件的Content。

另外一个需要重点介绍的就是FormItem这个类中的代码,这个控件在加载的时候所有的效果都是在后台中进行加载的,首先贴出相关的类的实现,然后再做进一步的分析。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
 
namespace X.UI
{
    public class FormItem : ListBoxItem
    {
        static FormItem()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(FormItem), new FrameworkPropertyMetadata(typeof(FormItem)));          
        }
 
        public FormItem()
        {
            System.Windows.Media.TranslateTransform transform = EnsureRenderTransform<System.Windows.Media.TranslateTransform>(this);
            transform.X = transform.Y = 100;
            Opacity = 0;
 
            IsVisibleChanged += FormItem_IsVisibleChanged;
        }
 
        void FormItem_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            if (this.Parent is Form)
            {
                if (!IsVisible)
                {
                    int index = (this.Parent as Form).Items.IndexOf(this);
                    System.Windows.Media.TranslateTransform transform = EnsureRenderTransform<System.Windows.Media.TranslateTransform>(this);
                    DoubleAnimation da = new DoubleAnimation()
                    {
                        From = 0,
                        To = 100,
                        EasingFunction = new CircleEase { EasingMode = EasingMode.EaseOut }
                    };
                    transform.BeginAnimation(System.Windows.Media.TranslateTransform.XProperty, da);
                    transform.BeginAnimation(System.Windows.Media.TranslateTransform.YProperty, da);
                    DoubleAnimation daopacity = new DoubleAnimation
                    {
                        From = 1,
                        To = 0,
                    };
                    this.BeginAnimation(UIElement.OpacityProperty, daopacity);
                }
                else
                {
                    int index = (this.Parent as Form).Items.IndexOf(this);
                    System.Windows.Media.TranslateTransform transform = EnsureRenderTransform<System.Windows.Media.TranslateTransform>(this);
                    DoubleAnimation da = new DoubleAnimation()
                    {
                        From = 100,
                        To = 0,
                        BeginTime = TimeSpan.FromMilliseconds(100 * (index + 1)),
                        Duration = TimeSpan.FromMilliseconds(666),
                        EasingFunction = new CircleEase { EasingMode = EasingMode.EaseOut }
                    };
                    transform.BeginAnimation(System.Windows.Media.TranslateTransform.XProperty, da);
                    transform.BeginAnimation(System.Windows.Media.TranslateTransform.YProperty, da);
                    DoubleAnimation daopacity = new DoubleAnimation
                    {
                        From = 0,
                        To = 1,
                        BeginTime = TimeSpan.FromMilliseconds(100 * (index + 1)),
                        Duration = TimeSpan.FromMilliseconds(666),
                        EasingFunction = new CircleEase { EasingMode = EasingMode.EaseOut }
                    };
                    this.BeginAnimation(UIElement.OpacityProperty, daopacity);
                }
            }
        }
 
        private T EnsureRenderTransform<T>(UIElement uiTarget)
            where T : Transform
        {
            if (uiTarget.RenderTransform is T)
                return uiTarget.RenderTransform as T;
            else
            {
                T instance = typeof(T).Assembly.CreateInstance(typeof(T).FullName) as T;
                uiTarget.RenderTransform = instance;
                return instance;
            }
        }
 
        public string Title
        {
            get { return (string)GetValue(TitleProperty); }
            set { SetValue(TitleProperty, value); }
        }
 
        // Using a DependencyProperty as the backing store for Title.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty TitleProperty =
            DependencyProperty.Register("Title", typeof(string), typeof(FormItem), new PropertyMetadata(""));
 
 
        public ImageSource Icon
        {
            get { return (ImageSource)GetValue(IconProperty); }
            set { SetValue(IconProperty, value); }
        }
 
        // Using a DependencyProperty as the backing store for Icon.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IconProperty =
            DependencyProperty.Register("Icon", typeof(ImageSource), typeof(FormItem), new PropertyMetadata(null));
 
    }
}  

这里在FormItem的构造函数中,添加了一个IsVisibleChanged事件,这个事件会在加载当前控件的时候发生,另外当当前控件的属性值发生变化的时候会触发该效果。其实效果就是同时在X和Y方向做一个平移的效果,这个也是一个常用的效果。

我们重点讨论的是下面的这段代码:

private T EnsureRenderTransform<T>(UIElement uiTarget)
           where T : Transform
       {
           if (uiTarget.RenderTransform is T)
               return uiTarget.RenderTransform as T;
           else
           {
               T instance = typeof(T).Assembly.CreateInstance(typeof(T).FullName) as T;
               uiTarget.RenderTransform = instance;
               return instance;
           }
       }

  这里我们创建TranslateTransform的时候是使用的System.Windows.Media.TranslateTransform transform = EnsureRenderTransform<System.Windows.Media.TranslateTransform>(this);这个方法,而不是每次都new一个对象,每次new一个对象的效率是很低的,而且会占据内存,我们如果已经创建过当前对象完全可以重复利用,这里我们使用了带泛型参数的函数来实现当前效果,typeof(T).Assembly.CreateInstance(typeof(T).FullName) as T,核心是通过程序集来创建对象,这种方式我们也是经常会使用的,比如我们可以通过获取应用程序级别的程序集来通过Activator.CreateInstance来创建窗体等一系列的对象,这种通过反射的机制来扩展的方法是我们需要特别留意的,另外写代码的时候必须注重代码的质量和效率,而不仅仅是实现了某一个功能,这个在以后的开发过程中再一点点去积累去吸收。

以上就是c# WPF中自定义加载时实现带动画效果的Form和FormItem的详细内容,更多关于c# wpf实现带动画效果的Form和FormItem的资料请关注我们其它相关文章!

(0)

相关推荐

  • C# WPF 建立无边框(标题栏)的登录窗口的示例

    前言:笔者最近用c#写WPF做了一个项目,此前未曾做过完整的WPF项目,算是一边学一边用,网上搜了不少资料,效率当然是不敢恭维的,有时会在一些很简单的问题上纠结很长时间,血与泪的教训可不少. 不过,正如电视剧某榜里的一句话:既然我活了下来,就不会白白活着!笔者怎么也算挣扎过了,有些经验与教训可以分享,趁着记忆深刻总结写下来.希望后来者少走弯路,提高工作效率.如果有写得不好的地方,希望读者能够指正,一起进步! --------------------------------- 今天先从登录窗口说起

  • C#中WPF依赖属性的正确学习方法

    前言 我在学习WPF的早期,对依赖属性理解一直都非常的不到位,其恶果就是,我每次在写依赖属性的时候,需要翻过去的代码来复制黏贴. 相信很多朋友有着和我相同的经历,所以这篇文章希望能帮助到那些刚刚开始学依赖属性的朋友. 那些[讨厌]的依赖属性的讲解文章 初学者肯定会面临一件事,就是百度,谷歌,或者MSDN来查看依赖属性的定义和使用,而这些文章虽然都写的很好,但,那是相对于已经学会使用依赖属性的朋友而言. 而对于初学者而言,说是误导都不过分. 比如,官网的这篇文章https://docs.micro

  • C# WPF 自定义按钮的方法

    本文介绍WPF一种自定义按钮的方法. 实现效果 使用图片做按钮背景: 自定义鼠标进入时效果: 自定义按压效果: 自定义禁用效果 实现效果如下图所示: 实现步骤 创建CustomButton.cs,继承自Button: 创建一个资源文件ButtonStyles.xaml: 在资源文件中设计按钮的Style: 在CustomButton.cs中添加Style中需要的依赖属性: 在程序中添加资源并引用(为了方便在不同的程序中引用自定义按钮,自定义按钮放在独立的类库中,应用程序中进行资源合并即可). 示

  • c# WPF中System.Windows.Interactivity的使用

    背景 在我们进行WPF开发应用程序的时候不可避免的要使用到事件,很多时候没有严格按照MVVM模式进行开发的时候习惯直接在xaml中定义事件,然后再在对应的.cs文件中直接写事件的处理过程,这种处理方式写起来非常简单而且不用过多地处理考虑代码之间是否符合规范,但是我们在写代码的时候如果完全按照WPF规范的MVVM模式进行开发的时候就应该将相应的事件处理写在ViewModel层,这样整个代码才更加符合规范而且层次也更加清楚,更加符合MVVM规范. 常规用法 1 引入命名空间 通过在代码中引入Syst

  • c# WPF中CheckBox样式的使用总结

    背景 很多时候我们使用WPF开发界面的时候经常会用到各种空间,很多时候我们需要去自定义控件的样式来替换默认的样式,今天通过两个方法来替换WPF中的CheckBox样式,透过这两个例子我们可以掌握基本的WPF样式的开发如何定义ControlTemplate以及使用附加属性来为我们的控件增加新的样式. 常规使用 我们在使用CheckBox的时候,原始的样式有时不能满足我们的需求,这是我们就需要更改其模板,比如我们常用的一种,在播放器中"播放"."暂停"按钮,其实这也是一

  • c# WPF实现Windows资源管理器(附源码)

      今天我来写一篇关于利用WPF来实现Windows的资源管理器功能,当然只是局部实现这个功能,因为在很多时候我们需要来实现对本机资源的管理,当然我们可以使用OpenFileDialog dialog = new OpenFileDialog()这个Microsoft.Win32命名空间下的这个类来实现一些资源查找和导入的功能,但是在很多时候我们可能需要更多的功能,并且希望能够集成到我们自己的项目中,但是我们这个时候就不得不自己来写一套来集成到我们的软件中去了,因为OpenFileDialog这

  • c# WPF如何实现滚动显示的TextBlock

    在我们使用TextBlock进行数据显示时,经常会遇到这样一种情况就是TextBlock的文字内容太多,如果全部显示的话会占据大量的界面,这是我们就会只让其显示一部分,另外的一部分就让其随着时间的推移去滚动进行显示,但是WPF默认提供的TextBlock是不具备这种功能的,那么怎么去实现呢? 其实个人认为思路还是比较清楚的,就是自己定义一个UserControl,然后将WPF简单的元素进行组合,最终实现一个自定义控件,所以我们顺着这个思路就很容易去实现了,我们知道Canvas这个控件可以通过设置

  • 浅谈c# WPF中的PreviewTextInput

    今天在使用TextBox的TextInput事件的时候,发现无论如何都不能触发该事件,然后百思不得其解,最后在MSDN上找到了答案:TextInput 事件可能已被标记为由复合控件的内部实现进行处理.例如,TextBox 就是这样一个控件:在其组合期间已将 TextInput 事件标记为已处理.之所以这么做是因为,控件需要将某些类型的输入(如箭头键)解释为对该控件具有特殊含义.如果将 PreviewTextInput 事件用于为文本输入附加处理程序,则会获得更好的效果.该技术可以应对控件组合将此

  • c# WPF中通过双击编辑DataGrid中Cell的示例(附源码)

    背景 在很多的时候我们需要编辑DataGrid中每一个Cell,编辑后保存数据,原生的WPF中的DataGrid并没有提供这样的功能,今天通过一个具体的例子来实现这一个功能,在这个例子中DataGrid中的数据类型可能是多种多样的,有枚举.浮点类型.布尔类型.DateTime类型,每一种不同的类型需要双击以后呈现不同的效果,本文通过使用Xceed.Wpf.DataGrid这个动态控件库来实现这个功能,当前使用的Dll版本是2.5.0.0,不同的版本可能实现上面有差别,这个在使用的时候需要特别注意

  • c# WPF中如何自定义MarkupExtension

    在介绍这一篇文章之前,我们首先来回顾一下WPF中的一些基础的概念,首先当然是XAML了,XAML全称是Extensible Application Markup Language (可扩展应用程序标记语言),是专门用于WPF技术中的UI设计语言,通过使用XAML语言,我们能够快速设计软件界面,同时能够通过绑定这种机制能够很好地实现界面和实现逻辑之间的解耦,这个就是MVVM模式的核心了,那么今天我们介绍的MarkupExtension和XAML之间又有哪些的关系呢? Markup Extensio

  • C# WPF Image控件的绑定方法

    在我们平时的开发中会经常用到Image控件,通过设置Image控件的Source属性,我们可以加载图片,设置Image的source属性时可以使用相对路径也可以使用绝对路径,一般情况下建议使用绝对路径,类似于下面的形式Source="/Demo;Component/Images/Test.jpg"其中Demo表示工程的名称,后面表示具体哪个文件夹下面的哪个图片资源,在程序中,我们甚至可以为Image控件设置X:Name属性,在后台代码中动态去改变Image的Source,但我个人认为这

随机推荐