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/> <RowDefinition/> </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