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的资料请关注其它相关文章!
上一篇: 技术面试要做哪些准备?