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. js---11闭包

    //匿名立即调用函数 (function(){//把a,b,f全部隐藏在函数中,外部访问不到, var a = 5; var b = 6; function f(){ alert(a); } wind ...

  2. docker -mysql服务设置远程连接 解决1251 client does not support ..问题

    前提: 安装MYSQL实例 docker pull mysql 启动mysql(做了端口映射) [root@localhost ~]# docker run -p 3306:3306 --name m ...

  3. Linux下ping命令、traceroute命令、tracert命令的使用

    Linux系统的ping命令是常用的网络命令,它通常用来测试与目标主机的连通性,我们经常会说“ping一下某机器,看是不是开着”.不能打开网页时会说“你先ping网关地址192.168.1.1试试”. ...

  4. 刘汝佳 算法竞赛-入门经典 第二部分 算法篇 第六章 2(Binary Trees)

    112 - Tree Summing 题目大意:给出一个数,再给一颗树,每个头节点的子树被包含在头节点之后的括号里,寻找是否有从头节点到叶子的和与给出的数相等,如果有则输出yes,没有输出no! 解题 ...

  5. JS --- 延迟加载的几种方式

    标题:JS延迟加载,也就是等页面加载完成之后再加载 JavaScript 文件.  JS延迟加载有助于提高页面加载速度. 1. defer 属性 HTML 4.01 为 <script>标 ...

  6. HTTP基础知识整理

    http请求由三部分组成,分别是:请求行.消息报头.请求正文 HTTP(超文本传输协议)是一个基于请求与响应模式的.无状态的.应用层的协议,常基于TCP的连接方式,HTTP1.1版本中给出一种持续连接 ...

  7. POJ 1991 DP

    题意: 思路: 考虑DP 先把事件按照地点顺序排个序 f[i][j][0]表示从i到j还没有去过 现在在i f[i][j][1]表示从i到j还没有去过 现在在j 那么方程就呼之欲出了 f[i][j][ ...

  8. POJ 3051 DFS

    题意:判断连通块大小 水题 //By SiriusRen #include <cstdio> #include <cstring> #include <algorithm ...

  9. FFT之大数乘法

    #include <iostream> #include <stdio.h> #include <cmath> #include <algorithm> ...

  10. Codefroces Educational Round 26 837 B. Flag of Berland

    B. Flag of Berland time limit per test 1 second memory limit per test 256 megabytes input standard i ...