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

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

  1. WPF 如何创建自己的WPF自定义控件库

    在我们平时的项目中,我们经常需要一套自己的自定义控件库,这个特别是在Prism这种框架下面进行开发的时候,每个人都使用一套统一的控件,这样才不会每个人由于界面不统一而造成的整个软件系统千差万别,所以我 ...

  2. [WPF自定义控件库] 关于ScrollViewer和滚动轮劫持(scroll-wheel-hijack)

    原文:[WPF自定义控件库] 关于ScrollViewer和滚动轮劫持(scroll-wheel-hijack) 1. 什么是滚动轮劫持# 这篇文章介绍一个很简单的继承自ScrollViewer的控件 ...

  3. [WPF自定义控件库]使用WindowChrome自定义RibbonWindow

    原文:[WPF自定义控件库]使用WindowChrome自定义RibbonWindow 1. 为什么要自定义RibbonWindow 自定义Window有可能是设计或功能上的要求,可以是非必要的,而自 ...

  4. [WPF自定义控件库] 让Form在加载后自动获得焦点

    原文:[WPF自定义控件库] 让Form在加载后自动获得焦点 1. 需求 加载后让第一个输入框或者焦点是个很基本的功能,典型的如"登录"对话框.一般来说"登录" ...

  5. [WPF自定义控件库]排序、筛选以及高亮

    1. 如何让列表的内容更容易查找 假设有这么一个列表(数据源在本地),由于内容太多,要查找到其中某个想要的数据会比较困难.要优化这个列表,无非就是排序.筛选和高亮. 改造过的结果如上. 2. 排序 在 ...

  6. [WPF自定义控件库]以Button为例谈谈如何模仿Aero2主题

    1. 为什么选择Aero2 除了以外观为卖点的控件库,WPF的控件库都默认使用"素颜"的外观,然后再提供一些主题包.这样做的最大好处是可以和原生控件或其它控件库兼容,而且对于大部分 ...

  7. [WPF自定义控件库]简单的表单布局控件

    1. WPF布局一个表单 <Grid Width="400" HorizontalAlignment="Center" VerticalAlignment ...

  8. [WPF自定义控件库]使用WindowChrome的问题

    1. 前言 上一篇文章介绍了使用WindowChrome自定义Window,实际使用下来总有各种各样的问题,这些问题大部分都不影响使用,可能正是因为不影响使用所以一直没得到修复(也有可能别人根本不觉得 ...

  9. [WPF自定义控件库] 自定义控件的代码如何与ControlTemplate交互

    1. 前言 WPF有一个灵活的UI框架,用户可以轻松地使用代码控制控件的外观.例设我需要一个控件在鼠标进入的时候背景变成蓝色,我可以用下面这段代码实现: protected override void ...

随机推荐

  1. vim 基础学习之普通模式

    .操作 = 操作符 + 动作 aaa bbb例如,d是删除命令,b是移动到距离光标最近的字符串开头当我们执行db的时候,就会删除光标(不包括光标位置)到最近字串开头之间的字符dj则会删除光标所在行以及 ...

  2. css3的新特性选择器-------属性选择器

    自己学css的时候比较乱,这次趁着复习把css3的新特性选择器和css2以前不怎么用的选择器做一个总结 <div id="parent"> <p>I'm a ...

  3. 洛谷P2251 质量检测

    题目背景 无 题目描述 为了检测生产流水线上总共N件产品的质量,我们首先给每一件产品打一个分数A表示其品质,然后统计前M件产品中质量最差的产品的分值Q[m] = min{A1, A2, ... Am} ...

  4. 在物理 Data Guard 中对异构主备系统的支持 (文档 ID 1602437.1)

    Data Guard中主数据库与物理备用数据库(Redo Apply)之间可以有什么差别?本说明针对重做应用和 Oracle Data Guard 12 发行版 1 进行了更新.它适用于 Oracle ...

  5. PHP 获取完整URL地址

    /** * 获取当前完整URL * @return string */ function get_url() { $sys_protocal = isset($_SERVER['SERVER_PORT ...

  6. hdu 1003 Max Sum 最大字段和 dp

    今天看了一上午dp.看不太懂啊.dp确实不简单.今天開始学习dp,搜了杭电的dp46道,慢慢来吧.白书上的写的 又不太具体,先写几道题目再说. .. 题目连接:id=516&page=1&qu ...

  7. android sdk 镜象网站

    因为一些原因.Google相关非常多服务都无法訪问,所以在非常多时候我们SDK也无法升级,当然通过技术手段肯定能够解决,可是比較麻烦,并且下载速度也不怎么样. 这里笔者介绍一个国内的Android镜像 ...

  8. redmine-bug 问题修改流程

    1.当我们接受到测试发过来的bug以后,首先就是针对描述,弄清出问题,如果不能重现或者是对问题不清楚,那么可以去找提出者确认 注意:有时候一个问题可能是经过几次重开或者追加提出的,这时候你一定要从头到 ...

  9. HTML5手机应用的最大优势就是可以在网页上直接调试和修改

    HTML5手机应用的最大优势就是可以在网页上直接调试和修改

  10. JAVA 多线程和进程概念的引入

    1.并发和并行 并行:指两个或多个时间在同一时刻发生(同时发生): 并发:指两个或多个事件在一个时间段内发生. 在操作系统中,安装了多个程序,并发指的是在一段时间内宏观上有多个程序同时运行,这在单 C ...