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、文字、小数点、シングル、ダブル、Int16型、のInt32、Int64の、のTimeSpan、ウリ、バイト、アレイタイプ、等)、XAMLパーサーは直接対応に文字列を変換しますプロパティ値に割り当てられ、他のタイプのため、XAMLパーサーは多くの作業を行う必要があります。
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
上記段落XAML「自動」と「*」、それぞれGridLength.Auto及び新しいGridLength(1、GridUnitType.Star)を解析するXAMLパーサーのように再度コードに対応する高さ、に割り当てられました:
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
如何:实现类型转换器