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. php数组合并有哪三种方法

    php数组合并有哪三种方法 一.总结 一句话总结:array_merge():array_merge_recursive():‘+'号 $a = array('color'=>'red',5,6 ...

  2. Kinect 开发 —— 全息图

    Kinect的另一个有趣的应用是伪全息图(pseudo-hologram).3D图像可以根据人物在Kinect前面的各种位置进行倾斜和移动.如果方法够好,可以营造出3D控件中3D图像的效果,这样可以用 ...

  3. OpenCV —— 矩阵操作

    多通道的矩阵 —— 通道是连续的!! 要将指向该数据类型的指针移动到下一通道,我们只需要将其增加1.如果想访问下一个“像素”或者元素集,则需要一定的偏移量 矩阵的step元素是矩阵中行的长度,单位为字 ...

  4. 【bzoj4864】神秘物质

    Description 给出一个长度为n的序列,第i个数为ai,进行以下四种操作: merge x e:将当前第x个数和第x+1个数合并,得到一个新的数e: insert x e:在当前第x个数和第x ...

  5. 关于css的入门知识

    css:叠层样式表,给html添加样式的 接下来说一说,在网页中如何嵌套style样式 1.行间样式:把style(*权重1000)作为属性卸载标签里 eg:<p style="col ...

  6. Python3.7&Django1.11.15 兼容性问题

    环境: 1. Windows10 2. python3.7 3. Django1.11.15 启动Django时抛出以下异常: Unhandled exception in thread starte ...

  7. 【hdu 4696】Professor Tian

    [Link]:http://acm.hdu.edu.cn/showproblem.php?pid=4649 [Description] 给你一个由位运算"与""或&quo ...

  8. apache 使用 mod_fcgid.so模块时 配置指令

    FcgidBusyScanInterval指令 说明:扫描繁忙超时进程的间隔 语法: FcgidBusyScanInterval seconds 默认:FcgidBusyScanInterval 12 ...

  9. Intent调用系统拍照程序,返回图片太小的问题

    之前採用的方式(返回的照片会被压缩,不能达到预期效果): Intent intent = new Intent(); Intent intent_camera = getPackageManager( ...

  10. struct数组初始化

    const int MAXN=100; struct A { int a,b; }; struct A arr[100];//此时编译通过 struct A arr[MAXN];//此时编译不通过,原 ...