WPF自定义控件与样式(3)-TextBox & RichTextBox & PasswordBox样式、水印、Label标签、功能扩展
一.前言.预览
申明:wpf自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接。
本文主要是对文本输入控件进行样式开发,及相关扩展功能开发,主要内容包括:
- 基本文本框textbox控件样式及扩展功能,实现了样式、水印、label标签、功能扩展;
- 富文本框richtextbox控件样式;
- 密码输入框passwordbox控件样式及扩展功能;
效果图:
二.基本文本框textbox控件样式及扩展功能
2.1 textbox基本样式
样式代码如下:
<!--textbox默认样式--> <style targettype="{x:type textbox}" x:key="defaulttextbox"> <setter property="contextmenu" value="{dynamicresource textboxcontextmenu}" /> <setter property="selectionbrush" value="{staticresource textselectionbrush}" /> <setter property="fontfamily" value="{staticresource fontfamily}" /> <setter property="fontsize" value="{staticresource fontsize}" /> <setter property="borderthickness" value="1" /> <setter property="minheight" value="26" /> <setter property="width" value="100" /> <setter property="background" value="{staticresource textbackground}" /> <setter property="foreground" value="{staticresource textforeground}" /> <setter property="padding" value="0" /> <setter property="borderbrush" value="{staticresource controlborderbrush}" /> <setter property="local:controlattachproperty.focusborderbrush" value="{staticresource focusborderbrush}" /> <setter property="local:controlattachproperty.mouseoverborderbrush" value="{staticresource mouseoverborderbrush}" /> <setter property="verticalcontentalignment" value="center" /> <!-- change snapstodevicepixels to true to view a better border and validation error --> <setter property="snapstodevicepixels" value="true" /> <!--英 ['kærət] 美 ['kærət] 插入符号--> <setter property="caretbrush" value="{staticresource textforeground}" /> <setter property="template"> <setter.value> <controltemplate targettype="{x:type textbox}"> <grid x:name="part_root"> <border x:name="bg" snapstodevicepixels="{templatebinding snapstodevicepixels}" cornerradius="{templatebinding local:controlattachproperty.cornerradius}" borderbrush="{templatebinding borderbrush}" borderthickness="{templatebinding borderthickness}" background="{templatebinding background}" /> <grid x:name="part_innergrid"> <grid.columndefinitions> <columndefinition width="auto" /> <columndefinition width="*" /> <columndefinition width="auto" /> </grid.columndefinitions> <!--label区域--> <contentcontrol x:name="label" margin="1" template="{templatebinding local:controlattachproperty.labeltemplate}" content="{templatebinding local:controlattachproperty.label}"/> <!--内容区域--> <scrollviewer x:name="part_contenthost" borderthickness="0" grid.column="1" istabstop="false" margin="2" verticalalignment="stretch" background="{x:null}" /> <!--水印--> <textblock x:name="message" padding="{templatebinding padding}" visibility="collapsed" text="{templatebinding local:controlattachproperty.watermark}" grid.column="1" foreground="{templatebinding foreground}" ishittestvisible="false" opacity="{staticresource watermarkopacity}" horizontalalignment="{templatebinding horizontalcontentalignment}" verticalalignment="{templatebinding verticalcontentalignment}" margin="5,2,5,2" /> <!--附加内容区域--> <border x:name="part_attachcontent" grid.column="2" margin="2" verticalalignment="center" horizontalalignment="center" > <contentcontrol verticalalignment="center" verticalcontentalignment="center" template="{templatebinding local:controlattachproperty.attachcontent}" /> </border> </grid> </grid> <controltemplate.triggers> <!--显示水印--> <datatrigger binding="{binding relativesource={relativesource self}, path=text}" value=""> <setter targetname="message" property="visibility" value="visible" /> </datatrigger> <trigger property="ismouseover" value="true"> <setter property="borderbrush" value="{binding path=(local:controlattachproperty.mouseoverborderbrush),relativesource={relativesource self}}"/> </trigger> <trigger property="isfocused" value="true"> <setter property="borderbrush" value="{binding path=(local:controlattachproperty.focusborderbrush),relativesource={relativesource self}}"/> </trigger> <!--不可用--> <trigger property="isenabled" value="false"> <setter targetname="part_root" property="opacity" value="{staticresource disableopacity}" /> </trigger> <!--只读时,禁用part_attachcontent--> <trigger property="isreadonly" value="true"> <setter targetname="part_attachcontent" property="isenabled" value="false" /> <setter targetname="bg" property="opacity" value="{staticresource readonlyopacity}" /> <setter targetname="part_contenthost" property="opacity" value="{staticresource readonlyopacity}" /> <setter targetname="label" property="opacity" value="{staticresource readonlyopacity}" /> </trigger> </controltemplate.triggers> </controltemplate> </setter.value> </setter> </style>
模板内容主要包含四部分:
- 用于实现label标签的预留区域;
- textbox本身的文本输入显示部分;
- 水印显示部分;
- 功能扩展的预留区域;
其中label标签、功能扩展,还有输入框的不同状态显示效果都是通过附加属性来实现的,其实从本质上附加属性和控件上定义的依赖属性是同一个概念,有些时候附加属性会更加方便,对于一些可共用的属性,就比较方便,这一点怎本文是有体现的。上面代码使用到的附加属性代码:
2.2 水印效果实现
通过2.1的代码示例,可以看出,水印是内置了一个textblock,用附加属性controlattachproperty.watermark设置水印内容,在触发器中检测,当textbox中有输入值,则隐藏水印的textblock,使用示例:
<stackpanel> <textbox width="140" height="40" margin="3" textwrapping="wrap" verticalscrollbarvisibility="visible">333333333333333</textbox> <textbox width="150" height="30" margin="3" core:controlattachproperty.watermark="我是水印" core:controlattachproperty.cornerradius="2"></textbox> <textbox width="150" height="30" margin="3" isreadonly="true" core:controlattachproperty.cornerradius="15" snapstodevicepixels="true" >我是只读的</textbox> <textbox width="150" height="30" margin="3" isenabled="false">isenabled="false"</textbox> <textbox width="150" height="30" core:controlattachproperty.watermark="我是水印"></textbox> </stackpanel>
效果:
2.3 label标签实现
参考2.1的代码,预留了label的区域,通过设置附加属性local:controlattachproperty.label设置标签文本,local:controlattachproperty.labeltemplate设置label标签的模板样式,即可自定义实现label标签,自定义样式:
<!--textbox包含附加属性label的样式--> <style targettype="{x:type textbox}" x:key="labeltextbox" basedon="{staticresource defaulttextbox}"> <setter property="local:controlattachproperty.labeltemplate" > <setter.value> <controltemplate targettype="contentcontrol"> <border width="60" background="{staticresource textlabelbackground}"> <textblock verticalalignment="center" horizontalalignment="right" margin="3" text="{templatebinding content}"></textblock> </border> </controltemplate> </setter.value> </setter> </style>
使用示例及效果:
<textbox width="200" height="30" margin="3" core:controlattachproperty.watermark="请输入姓名" style="{staticresource labeltextbox}" core:controlattachproperty.label="姓名:"></textbox>
2.4 扩展功能及自定义扩展
思路和2.3的label标签实现相似,清除文本框内的内容是一个常用需求,我们就线扩展一个这么一个功能的textbox,通过附加属性controlattachproperty.attachcontent定义扩展功能的模板,模板内定义的是一个按钮fbutton(可参考上一篇,本文末尾附录中有链接)
<!--textbox包含清除text按钮的样式--> <style targettype="{x:type textbox}" x:key="clearbuttontextbox" basedon="{staticresource defaulttextbox}"> <setter property="local:controlattachproperty.attachcontent"> <setter.value> <controltemplate> <local:fbutton ficon="" style="{staticresource fbutton_transparency}" istabstop="false" ficonmargin="0" local:controlattachproperty.iscleartextbuttonbehaviorenabled="true" command="local:controlattachproperty.cleartextcommand" commandparameter="{binding relativesource={relativesource findancestor,ancestortype={x:type textbox}}}" margin="1,3,1,4" ficonsize="14" foreground="{staticresource textforeground}" cursor="arrow"/> </controltemplate> </setter.value> </setter> </style>
这里定义的是显示效果,清除textbox内容的逻辑代码如何实现的呢?还是附加属性:
- controlattachproperty.iscleartextbuttonbehaviorenabled="true" :注入事件到当前button
- command="local:controlattachproperty.cleartextcommand":定义fbutton的命令对象实例command
- commandparameter="{binding relativesource={relativesource findancestor,ancestortype={x:type textbox}}}":把textbox作为参数传入
逻辑代码如下,从代码不难看出,它是支持多种输入控件的内容清除的,也就是说该扩展功能可以轻松支持其他输入控件,第四节密码数据的清除也是这样使用的。
效果:
当然我们也可以自定义扩展其他功能,如:
<textbox width="200" height="30" margin="3" core:controlattachproperty.watermark="查询关键词" isenabled="true"> <core:controlattachproperty.attachcontent> <controltemplate> <stackpanel orientation="horizontal"> <core:fbutton ficon="" style="{staticresource fbutton_transparency}" istabstop="false" ficonmargin="0" ficonsize="18" margin="1,1,2,3" foreground="{staticresource textforeground}" cursor="arrow"/> <core:fbutton ficon="" style="{staticresource fbutton_transparency}" istabstop="false" ficonmargin="0" ficonsize="22" foreground="{staticresource textforeground}" cursor="arrow"/> </stackpanel> </controltemplate> </core:controlattachproperty.attachcontent> </textbox>
效果:
由上不难同时实现label标签和清除文本内容的样式:
<!--textbox包含附加属性label,以及cleartext按钮的样式--> <style targettype="{x:type textbox}" x:key="labelclearbuttontextbox" basedon="{staticresource defaulttextbox}"> <setter property="local:controlattachproperty.labeltemplate" > <setter.value> <controltemplate targettype="contentcontrol"> <border width="60" background="{staticresource textlabelbackground}"> <textblock verticalalignment="center" horizontalalignment="right" margin="3" text="{templatebinding content}"></textblock> </border> </controltemplate> </setter.value> </setter> <setter property="local:controlattachproperty.attachcontent"> <setter.value> <controltemplate> <local:fbutton ficon="" style="{staticresource fbutton_transparency}" istabstop="false" ficonmargin="0" local:controlattachproperty.iscleartextbuttonbehaviorenabled="true" command="local:controlattachproperty.cleartextcommand" commandparameter="{binding relativesource={relativesource findancestor,ancestortype={x:type textbox}}}" margin="0,3,1,4" ficonsize="14" foreground="{staticresource textforeground}" cursor="arrow"/> </controltemplate> </setter.value> </setter> </style>
2.6 文件选择输入相关扩展
先看看效果,就明白了。
具体实现原理和上面2.4差不多 ,实现了三个文件、文件夹选择相关的功能扩展,样式代码:
<!--labelopenfiletextbox--> <style targettype="{x:type textbox}" x:key="labelopenfiletextbox" basedon="{staticresource labelclearbuttontextbox}"> <setter property="local:controlattachproperty.label" value="文件路径"/> <setter property="local:controlattachproperty.watermark" value="选择文件路径"/> <setter property="local:controlattachproperty.attachcontent"> <setter.value> <controltemplate> <local:fbutton ficon="" style="{staticresource fbutton_transparency}" istabstop="false" ficonmargin="0" local:controlattachproperty.isopenfilebuttonbehaviorenabled="true" command="local:controlattachproperty.openfilecommand" commandparameter="{binding relativesource={relativesource findancestor,ancestortype={x:type textbox}}}" margin="0,1,0,1" ficonsize="22" foreground="{staticresource textforeground}" cursor="arrow"/> </controltemplate> </setter.value> </setter> </style> <!--labelopenfoldertextbox--> <style targettype="{x:type textbox}" x:key="labelopenfoldertextbox" basedon="{staticresource labelclearbuttontextbox}"> <setter property="local:controlattachproperty.label" value="设置路径"/> <setter property="local:controlattachproperty.watermark" value="选择文件夹路径"/> <setter property="local:controlattachproperty.attachcontent"> <setter.value> <controltemplate> <local:fbutton ficon="" style="{staticresource fbutton_transparency}" istabstop="false" ficonmargin="0" local:controlattachproperty.isopenfolderbuttonbehaviorenabled="true" command="local:controlattachproperty.openfoldercommand" commandparameter="{binding relativesource={relativesource findancestor,ancestortype={x:type textbox}}}" margin="0,1,0,1" ficonsize="22" foreground="{staticresource textforeground}" cursor="arrow"/> </controltemplate> </setter.value> </setter> </style> <!--labelsavefiletextbox--> <style targettype="{x:type textbox}" x:key="labelsavefiletextbox" basedon="{staticresource labelclearbuttontextbox}"> <setter property="local:controlattachproperty.label" value="保存路径"/> <setter property="local:controlattachproperty.watermark" value="选择文件保存路径"/> <setter property="local:controlattachproperty.attachcontent"> <setter.value> <controltemplate> <local:fbutton ficon="" style="{staticresource fbutton_transparency}" istabstop="false" ficonmargin="0" local:controlattachproperty.issavefilebuttonbehaviorenabled="true" command="local:controlattachproperty.savefilecommand" commandparameter="{binding relativesource={relativesource findancestor,ancestortype={x:type textbox}}}" margin="0,1,0,1" ficonsize="20" foreground="{staticresource textforeground}" cursor="arrow"/> </controltemplate> </setter.value> </setter> </style>
当然实现原理和2.4一样,都是依赖属性来实现事件的注入和绑定的,所以就不多废话了:
三.富文本框richtextbox控件样式
richtextbox的样式比较简单:
<!--***************************defaultrichtextbox***************************--> <style x:key="defaultrichtextbox" targettype="{x:type richtextbox}"> <setter property="contextmenu" value="{dynamicresource textboxcontextmenu}" /> <setter property="selectionbrush" value="{staticresource textselectionbrush}" /> <setter property="fontfamily" value="{staticresource fontfamily}" /> <setter property="fontsize" value="{staticresource fontsize}" /> <setter property="borderthickness" value="1" /> <setter property="borderbrush" value="{staticresource controlborderbrush}" /> <setter property="minheight" value="26" /> <setter property="minwidth" value="10" /> <setter property="background" value="{staticresource textbackground}" /> <setter property="foreground" value="{staticresource textforeground}" /> <setter property="caretbrush" value="{staticresource textforeground}" /> <setter property="local:controlattachproperty.focusborderbrush" value="{staticresource focusborderbrush}" /> <setter property="local:controlattachproperty.mouseoverborderbrush" value="{staticresource mouseoverborderbrush}" /> <setter property="padding" value="1" /> <setter property="allowdrop" value="true" /> <setter property="verticalscrollbarvisibility" value="auto" /> <setter property="focusvisualstyle" value="{x:null}" /> <setter property="scrollviewer.panningmode" value="verticalfirst" /> <!--该值指示是否启用了笔势--> <setter property="stylus.isflicksenabled" value="false" /> <!--snapstodevicepixels:该值来确定呈现此元素是否应使用特定于设备的像素设置--> <setter property="snapstodevicepixels" value="true" /> <setter property="template"> <setter.value> <controltemplate targettype="{x:type textboxbase}"> <grid> <border x:name="bd" borderbrush="{templatebinding borderbrush}" borderthickness="{templatebinding borderthickness}" background="{templatebinding background}" snapstodevicepixels="{templatebinding snapstodevicepixels}"> <scrollviewer x:name="part_contenthost" snapstodevicepixels="{templatebinding snapstodevicepixels}" /> </border> </grid> <controltemplate.triggers> <trigger property="ismouseover" value="true"> <setter property="borderbrush" value="{binding path=(local:controlattachproperty.mouseoverborderbrush),relativesource={relativesource self}}"/> </trigger> <trigger property="isfocused" value="true"> <setter property="borderbrush" value="{binding path=(local:controlattachproperty.focusborderbrush),relativesource={relativesource self}}"/> </trigger> <trigger property="isenabled" value="false"> <setter targetname="bd" property="opacity" value="0.5" /> </trigger> <trigger property="isreadonly" value="true"> <setter targetname="bd" property="opacity" value="0.85" /> </trigger> </controltemplate.triggers> </controltemplate> </setter.value> </setter> </style>
使用实力及效果:
四.密码输入框passwordbox控件样式及扩展功能
密码输入控件的样式和第二节文本框textbox基本一致,就不做详细的说明了,直接上样式的代码,相关逻辑(c#) 代码和上面是一样的(复用)。
<!--textbox默认样式--> <style targettype="{x:type passwordbox}" x:key="defaultpasswordbox"> <setter property="contextmenu" value="{dynamicresource textboxcontextmenu}" /> <setter property="selectionbrush" value="{staticresource textselectionbrush}" /> <setter property="fontfamily" value="{staticresource fontfamily}" /> <setter property="fontsize" value="{staticresource fontsize}" /> <setter property="borderthickness" value="1" /> <setter property="passwordchar" value="●"/> <setter property="height" value="30" /> <setter property="width" value="200" /> <setter property="background" value="{staticresource textbackground}" /> <setter property="foreground" value="{staticresource textforeground}" /> <setter property="padding" value="0" /> <setter property="borderbrush" value="{staticresource controlborderbrush}" /> <setter property="local:controlattachproperty.focusborderbrush" value="{staticresource focusborderbrush}" /> <setter property="local:controlattachproperty.mouseoverborderbrush" value="{staticresource mouseoverborderbrush}" /> <setter property="verticalcontentalignment" value="center" /> <!-- change snapstodevicepixels to true to view a better border and validation error --> <setter property="snapstodevicepixels" value="true" /> <!--英 ['kærət] 美 ['kærət] 插入符号--> <setter property="caretbrush" value="{staticresource textforeground}" /> <setter property="template"> <setter.value> <controltemplate targettype="{x:type passwordbox}"> <grid x:name="part_root"> <border x:name="bg" snapstodevicepixels="{templatebinding snapstodevicepixels}" cornerradius="{templatebinding local:controlattachproperty.cornerradius}" borderbrush="{templatebinding borderbrush}" borderthickness="{templatebinding borderthickness}" background="{templatebinding background}" /> <grid x:name="part_innergrid"> <grid.columndefinitions> <columndefinition width="auto" /> <columndefinition width="*" /> <columndefinition width="auto" /> </grid.columndefinitions> <!--label区域--> <contentcontrol x:name="label" margin="1" template="{templatebinding local:controlattachproperty.labeltemplate}" content="{templatebinding local:controlattachproperty.label}"/> <!--内容区域--> <scrollviewer x:name="part_contenthost" borderthickness="0" grid.column="1" istabstop="false" margin="2" verticalalignment="stretch" background="{x:null}" /> <!--附加内容区域--> <border x:name="part_attachcontent" grid.column="2" margin="2" verticalalignment="center" horizontalalignment="center" > <contentcontrol verticalalignment="center" verticalcontentalignment="center" template="{templatebinding local:controlattachproperty.attachcontent}" /> </border> </grid> </grid> <controltemplate.triggers> <trigger property="ismouseover" value="true"> <setter property="borderbrush" value="{binding path=(local:controlattachproperty.mouseoverborderbrush),relativesource={relativesource self}}"/> </trigger> <trigger property="isfocused" value="true"> <setter property="borderbrush" value="{binding path=(local:controlattachproperty.focusborderbrush),relativesource={relativesource self}}"/> </trigger> <!--不可用--> <trigger property="isenabled" value="false"> <setter targetname="part_root" property="opacity" value="{staticresource disableopacity}"></setter> </trigger> </controltemplate.triggers> </controltemplate> </setter.value> </setter> </style> <!--textbox包含清除text按钮的样式--> <style targettype="{x:type passwordbox}" x:key="clearbuttonpasswordbox" basedon="{staticresource defaultpasswordbox}"> <setter property="local:controlattachproperty.attachcontent"> <setter.value> <controltemplate> <local:fbutton ficon="" style="{staticresource fbutton_transparency}" istabstop="false" ficonmargin="0" local:controlattachproperty.iscleartextbuttonbehaviorenabled="true" command="local:controlattachproperty.cleartextcommand" commandparameter="{binding relativesource={relativesource findancestor,ancestortype={x:type passwordbox}}}" margin="1,3,1,4" ficonsize="14" foreground="{staticresource textforeground}" cursor="arrow"/> </controltemplate> </setter.value> </setter> </style> <!--textbox包含附加属性label的样式--> <style targettype="{x:type passwordbox}" x:key="labelpasswordbox" basedon="{staticresource defaultpasswordbox}"> <setter property="local:controlattachproperty.labeltemplate" > <setter.value> <controltemplate targettype="contentcontrol"> <border width="60" background="{staticresource textlabelbackground}"> <textblock verticalalignment="center" horizontalalignment="right" margin="3" text="{templatebinding content}"></textblock> </border> </controltemplate> </setter.value> </setter> </style> <!--textbox包含附加属性label,以及cleartext按钮的样式--> <style targettype="{x:type passwordbox}" x:key="labelclearbuttonpasswordbox" basedon="{staticresource defaultpasswordbox}"> <setter property="local:controlattachproperty.labeltemplate" > <setter.value> <controltemplate targettype="contentcontrol"> <border width="60" background="{staticresource textlabelbackground}"> <textblock verticalalignment="center" horizontalalignment="right" margin="3" text="{templatebinding content}"></textblock> </border> </controltemplate> </setter.value> </setter> <setter property="local:controlattachproperty.attachcontent"> <setter.value> <controltemplate> <local:fbutton ficon="" style="{staticresource fbutton_transparency}" istabstop="false" ficonmargin="0" local:controlattachproperty.iscleartextbuttonbehaviorenabled="true" command="local:controlattachproperty.cleartextcommand" commandparameter="{binding relativesource={relativesource findancestor,ancestortype={x:type passwordbox}}}" margin="0,3,1,4" ficonsize="14" foreground="{staticresource textforeground}" cursor="arrow"/> </controltemplate> </setter.value> </setter> </style>
使用示例及效果: