zoukankan      html  css  js  c++  java
  • [WPF 自定义控件]使用TextBlockHighlightSource强化高亮的功能,以及使用TypeConverter简化调用

    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

  • 相关阅读:
    重写Nacos服务发现逻辑动态修改远程服务IP地址
    手撸一个SpringBoot配置中心实现配置动态刷新
    使用CompletableFuture实现多个异步任务并行完成后合并结果
    SpringBoot实现Flyway的Callback回调钩子
    Java实现相似结构表算法
    使用Druid解析SQL实现血缘关系计算
    记一次解决RestTemplate和HttpClient请求结果乱码的问题
    double转json格式化添加自定义注解
    了解23种设计模式
    BigDecimal四舍五入
  • 原文地址:https://www.cnblogs.com/dino623/p/TextBlockHighlightSource.html
Copyright © 2011-2022 走看看