• WPF使用TextBlock实现查找结果高亮显示


    在应用开发过程中,经常遇到这样的需求:通过关键字查找数据,把带有关键字的数据显示出来,同时在结果中高亮显示关键字。在web开发中,只需在关键字上加一层标签,然后设置标签样式就可以轻松实现。

    在WPF中显示文本内容通常采用TextBlock控件,也可以采用类似的方式,通过内联流内容元素Run达到同样的效果:

    <TextBlock FontSize="20">
        <Run Text="Hel" /><Run Foreground="Red" Text="lo " /><Run Text="Word" />
    TextBlock>
    

    需要注意的是每个Run之间不要换行,如果换行的话,每个Run之间会有间隙,看起来像增加了空格。

    通过这种方式实现查找结果中高亮关键字,需要把查找结果拆分成三部分,然后绑定到Run元素的Text属性,或者在后台代码中使用TextBlockInlines属性添加Run元素

    textBlock1.Inlines.Add(new Run("hel"));
    textBlock1.Inlines.Add(new Run("lo ") { Foreground=new SolidColorBrush(Colors.Red)});
    textBlock1.Inlines.Add(new Run("world"));
    

    这种方法虽然可以达到效果,但显然与MVVM的思想不符。接下来本文介绍一种通过附加属性实现TextBlock中指定内容高亮。
    image

    技术要点与实现#

    通过TextEffectPositionStartPositionCount以及Foreground属性设置字符串中需要高亮内容的起始位置、长度以及高亮颜色。定义附加属性允许TextBlock设置需要高亮的内容位置以及颜色。

    • 首先定义类ColoredLettering(并不要求继承DependencyObject)。
    • ColoredLettering中注册自定义的附加属性,注册附加属性方式与注册依赖属性类似,不过附加属性是用DependencyProperty.RegisterAttached来注册。
    • 给附加属性注册属性值变化事件,事件处理逻辑中设置TextEffectPositionStartPositionCount以及Foreground实现内容高亮。
    public class ColoredLettering
    {
        public static void SetColorStart(TextBlock textElement, int value)
        {
            textElement.SetValue(ColorStartProperty, value);
        }
    
        public static int GetColorStart(TextBlock textElement)
        {
            return (int)textElement.GetValue(ColorStartProperty);
        }
    
        // Using a DependencyProperty as the backing store for ColorStart.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ColorStartProperty =
            DependencyProperty.RegisterAttached("ColorStart", typeof(int), typeof(ColoredLettering), new FrameworkPropertyMetadata(0, OnColorStartChanged));
    
        private static void OnColorStartChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            TextBlock textBlock = d as TextBlock;
            if (textBlock != null)
            {
                if (e.NewValue == e.OldValue) return;
                    if (e.NewValue is int)
                    {
                        int count = GetColorLength(textBlock);
                        Brush brush = GetForeColor(textBlock);
                        if ((int)e.NewValue <= 0 || count <= 0 || brush == TextBlock.ForegroundProperty.DefaultMetadata.DefaultValue) return;
                        if (textBlock.TextEffects.Count != 0)
                        {
                            textBlock.TextEffects.Clear();
                        }
                        TextEffect textEffect = new TextEffect()
                        {
                            Foreground = brush,
                            PositionStart = (int)e.NewValue,
                            PositionCount = count
                        };
                        textBlock.TextEffects.Add(textEffect);
                    }
            }
        }
    
        public static void SetColorLength(TextBlock textElement, int value)
        {
            textElement.SetValue(ColorLengthProperty, value);
        }
    
        public static int GetColorLength(TextBlock textElement)
        {
            return (int)textElement.GetValue(ColorLengthProperty);
        }
    
        // Using a DependencyProperty as the backing store for ColorStart.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ColorLengthProperty =
            DependencyProperty.RegisterAttached("ColorLength", typeof(int), typeof(ColoredLettering), new FrameworkPropertyMetadata(0, OnColorLengthChanged));
    
        private static void OnColorLengthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            TextBlock textBlock = d as TextBlock;
                if (textBlock != null)
                {
                    if (e.NewValue == e.OldValue) return;
                    if (e.NewValue is int)
                    {
                        int start = GetColorStart(textBlock);
                        Brush brush = GetForeColor(textBlock);
                        if ((int)e.NewValue <= 0 || start <= 0 || brush == TextBlock.ForegroundProperty.DefaultMetadata.DefaultValue) return;
                        if (textBlock.TextEffects.Count != 0)
                        {
                            textBlock.TextEffects.Clear();
                        }
                        TextEffect textEffect = new TextEffect()
                        {
                            Foreground = brush,
                            PositionStart = start,
                            PositionCount = (int)e.NewValue
                        };
                        textBlock.TextEffects.Add(textEffect);
                    }
                }
        }
    
        public static void SetForeColor(TextBlock textElement, Brush value)
        {
            textElement.SetValue(ColorStartProperty, value);
        }
    
        public static Brush GetForeColor(TextBlock textElement)
        {
            return (Brush)textElement.GetValue(ForeColorProperty);
        }
    
        // Using a DependencyProperty as the backing store for ForeColor.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ForeColorProperty =
            DependencyProperty.RegisterAttached("ForeColor", typeof(Brush), typeof(ColoredLettering), new PropertyMetadata(TextBlock.ForegroundProperty.DefaultMetadata.DefaultValue, OnForeColorChanged));
    
        private static void OnForeColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            TextBlock textBlock = d as TextBlock;
            if (textBlock != null)
            {
                if (e.NewValue == e.OldValue) return;
                if (e.NewValue is Brush)
                {
                    int start = GetColorStart(textBlock);
                    int count = GetColorLength(textBlock);
                    if (start <= 0 || count <= 0) return;
                    if (textBlock.TextEffects.Count != 0)
                    {
                        textBlock.TextEffects.Clear();
                    }
                    TextEffect textEffect = new TextEffect()
                    {
                        Foreground = (Brush)e.NewValue,
                        PositionStart = start,
                        PositionCount = count
                    };
                    textBlock.TextEffects.Add(textEffect);
                }
            }
        }
    }
    

    调用时只需在TextBlock指定需要高亮内容的开始位置,内容长度以及高亮颜色即可。

    local:ColoredLettering.ColorLength="{Binding Count}"
               local:ColoredLettering.ColorStart="{Binding Start}"
               local:ColoredLettering.ForeColor="{Binding ForeColor}"
               FontSize="20"
               Text="Hello World" />
    

    总结#

    本文介绍的方法只是高亮第一个匹配到的关键字,如果需要高亮匹配到的所有内容,只需要对附加属性进行改造,以支持传入一组位置和颜色信息。
    最后分享一个可以解析一组有限的HTML标记并显示它们的WPF控件HtmlTextBlock ,通过这个控件也可以实现查找结果中高亮关键字,甚至支持指定内容触发事件做一些逻辑操作。

  • 相关阅读:
    manjaro系统无法安装
    MyBatis-Plus(二)
    【C++】类和对象 — 日期类的实现 运算符重载 初始化列表 友元(下篇)
    GPT实战系列-LangChain如何构建基通义千问的多工具链
    Java基础 - 练习(五)根据今天日期获取一周内的日期(基姆拉尔森公式)
    09 更真实的云原生:Kubeadm实际搭建多节点的Kubernetes集群
    Web 安全之 Permissions Policy(权限策略)详解
    【JavaScript】 一万字 JavaScript 笔记(详细讲解 + 代码演示 + 图解)
    HashMap很美好,但线程不安全怎么办?ConcurrentHashMap告诉你答案!
    Java面试题总结(三)
  • 原文地址:https://www.cnblogs.com/czwy/p/17661379.html