[WPF自定义控件库]使用TextBlockHighlightSource强化高亮的功能,以及使用TypeConverter简化调用
1. 强化高亮的功能
介绍了使用附加属性实现textblock的高亮功能,但也留下了问题:不能定义高亮(或者低亮)的颜色。为了解决这个问题,我创建了textblockhighlightsource
这个类,比单纯的字符串存储更多的信息,这个类的定义如下:
相应地,附加属性的类型也改变为这个类,并且属性值改变事件改成这样:
private static void onhighlighttextchanged(dependencyobject obj, dependencypropertychangedeventargs args) { var oldvalue = (textblockhighlightsource)args.oldvalue; var newvalue = (textblockhighlightsource)args.newvalue; if (oldvalue == newvalue) return; void onpropertychanged(object sender,eventargs e) { if (obj is textblock target) { markhighlight(target, newvalue); } }; if(oldvalue!=null) newvalue.propertychanged -= onpropertychanged; if (newvalue != null) newvalue.propertychanged += onpropertychanged; onpropertychanged(null, null); }
markhighlight
的关键代码修改为这样:
if (highlightsource.lowlightforeground != null) run.foreground = highlightsource.lowlightforeground; if (highlightsource.highlightforeground != null) run.foreground = highlightsource.highlightforeground; if (highlightsource.highlightbackground != null) run.background = highlightsource.highlightbackground;
使用起来就是这样:
<textblock text="git hub" textwrapping="wrap"> <kino:textblockservice.highlighttext> <kino:textblockhighlightsource text="hub" lowlightforeground="black" highlightbackground="#fff37d33" /> </kino:textblockservice.highlighttext> </textblock>
2. 使用typeconverter简化调用
textblockhighlightsource
提供了很多功能,但和直接使用字符串比起来,创建一个textblockhighlightsource
要复杂多。为了可以简化调用可以使用自定义的typeconverter
。
首先来了解一下typeconverter
的概念。xaml本质上是xml,其中的属性内容全部都是字符串。如果对应属性的类型是xaml内置类型(即boolea,char,string,decimal,single,double,int16,int32,int64,timespan,uri,byte,array等类型),xaml解析器直接将字符串转换成对应值赋给属性;对于其它类型,xaml解析器需做更多工作。
<grid.rowdefinitions> <rowdefinition height="auto"/> <rowdefinition height="*"/> </grid.rowdefinitions>
如上面这段xaml中的"auto"和"*",xaml解析器将其分别解析成gridlength.auto和new gridlength(1, gridunittype.star)再赋值给height,它相当于这段代码:
grid.rowdefinitions.add(new rowdefinition { height = gridlength.auto }); grid.rowdefinitions.add(new rowdefinition { height = new gridlength(1, gridunittype.star) });
为了完成这个工作,xaml解析器需要typeconverter的协助。xaml解析器通过两个步骤查找typeconverter:
1. 检查属性声明上的typeconverterattribute。
2. 如果属性声明中没有typeconverterattribute,检查类型声明中的typeconverterattribute。
属性声明上typeconverterattribute的优先级高于类型声明。如果以上两步都找不到类型对应的typeconverterattribute,xaml解析器将会报错:属性"*"的值无效。找到typeconverterattribute指定的typeconverter后,xaml解析器调用它的object convertfromstring(string text)
函数将字符串转换成属性的值。
wpf内置的typeconverter十分十分多,但有时还是需要自定义typeconverter,自定义typeconverter的基本步骤如下:
- 创建一个继承自typeconverter的类;
- 重写
virtual bool canconvertfrom(itypedescriptorcontext context, type sourcetype);
- 重写
virtual bool canconvertto(itypedescriptorcontext context, type destinationtype);
- 重写
virtual object convertfrom(itypedescriptorcontext context, cultureinfo culture, object value);
- 重写
virtual object convertto(itypedescriptorcontext context, cultureinfo culture, object value, type destinationtype);
- 使用typeconverterattribute 指示xaml解析器可用的typeconverter;
到这里我想typeconverter
的概念已经介绍得够详细了。回到本来话题,要简化textblockhighlightsource
的调用我创建了textblockhighlightsourceconverter
这个类,它继承自typeconverter
,里面的关键代码如下:
public override bool canconvertfrom(itypedescriptorcontext context, type sourcetype) { if (sourcetype == typeof(string)) { return true; } return base.canconvertfrom(context, sourcetype); } public override object convertfrom(itypedescriptorcontext context, cultureinfo culture, object value) { switch (value) { case null: throw getconvertfromexception(null); case string source: return new textblockhighlightsource { text = value.tostring() }; } return base.convertfrom(context, culture, value); }
然后在textblockhighlightsource上使用typeconverterattribute
:
[typeconverter(typeof(textblockhighlightsourceconverter))] public class textblockhighlightsource : frameworkelement
这样在xaml中textblockhighlightsource
的调用方式就可以和使用字符串一样简单了。
<textblock text="github" kino:textblockservice.highlighttext="hub" />
3. 使用style
有没有发现textblockhighlightsource
继承自frameworkelement
?这种奇特的写法是为了让textblockhighlightsource
可以使用全局的style。毕竟要在应用程序里统一highlight的颜色还是全局样式最好使,但作为附加属性,textblockhighlightsource
并不是visualtree的一部分,它拿不到visualtree上的resources。最简单的解决方案是让textblockhighlightsource
继承自frameworkelement
,把它放到visualtree里,用法如下:
<stackpanel> <frameworkelement.resources> <style targettype="kino:textblockhighlightsource"> <setter property="lowlightforeground" value="blue"/> </style> </frameworkelement.resources> <textbox x:name="filterelement3"/> <kino:textblockhighlightsource text="{binding elementname=filterelement3,path=text}" highlightforeground="darkblue" highlightbackground="yellow" x:name="textblockhighlightsource2"/> <textblock text="a very powerful projector with special features for internet usability, usb" kino:textblockservice.highlighttext="{binding elementname=textblockhighlightsource2}" textwrapping="wrap"/> </stackpanel>
也许你会觉得这种写法有些奇怪,毕竟我也觉得在view上放一个隐藏的元素真的很怪。其实在一万二千年前微软就已经有这种写法,在domaindatasource的文档里就有用到:
<grid x:name="layoutroot" background="white"> <grid.rowdefinitions> <rowdefinition height="25" /> <rowdefinition height="auto" /> </grid.rowdefinitions> <riacontrols:domaindatasource x:name="source" queryname="getproducts" autoload="true"> <riacontrols:domaindatasource.domaincontext> <domain:productdomaincontext /> </riacontrols:domaindatasource.domaincontext> <riacontrols:domaindatasource.filterdescriptors> <riadata:filterdescriptorcollection logicaloperator="and"> <riadata:filterdescriptor propertypath="color" operator="isequalto" value="blue" /> <riadata:filterdescriptor propertypath="listprice" operator="islessthanorequalto"> <riacontrols:controlparameter controlname="maxprice" propertyname="selecteditem.content" refresheventname="selectionchanged" /> </riadata:filterdescriptor> </riadata:filterdescriptorcollection> </riacontrols:domaindatasource.filterdescriptors> </riacontrols:domaindatasource> <combobox x:name="maxprice" grid.row="0" width="60" selectedindex="0"> <comboboxitem content="100" /> <comboboxitem content="500" /> <comboboxitem content="1000" /> </combobox> <data:datagrid grid.row="1" itemssource="{binding data, elementname=source}" /> </grid>
把datasource放到view上这种做法可能是winform的祖传家训,结构可耻但有用。
4. 结语
写这篇博客的时候我才发觉这个附加属性还叫highlighttext好像不太好,但也懒得改了。
这篇文章介绍了使用typeconverter简化调用,以及继承自frameworkelement
以便使用style。
5. 参考
typeconverter 类
typeconverters 和 xaml
type converters for xaml overview
typeconverterattribute class