当前位置: 移动技术网 > IT编程>开发语言>.net > [WPF自定义控件库]使用TextBlockHighlightSource强化高亮的功能,以及使用TypeConverter简化调用

[WPF自定义控件库]使用TextBlockHighlightSource强化高亮的功能,以及使用TypeConverter简化调用

2019年06月26日  | 移动技术网IT编程  | 我要评论

组织部面试问题,裸婚时代的女主角,广东广机国际招标股份有限公司

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

6. 源码

textblock at master · dinochan_kino.toolkit.wpf

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网