WPF使用TextBlock實現查找結果高亮顯示

来源:https://www.cnblogs.com/czwy/archive/2023/08/28/17661379.html
-Advertisement-
Play Games

本文將分享使用 GitHub Actions 完成對一個.Net Core+Vue 的前後端分離項目 zhontai 的構建,並使用 docker 部署到雲伺服器(阿裡雲),及對docker部署.Net Core+Vue的一些經驗分享。 ...


在應用開發過程中,經常遇到這樣的需求:通過關鍵字查找數據,把帶有關鍵字的數據顯示出來,同時在結果中高亮顯示關鍵字。在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指定需要高亮內容的開始位置,內容長度以及高亮顏色即可。

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

總結

本文介紹的方法只是高亮第一個匹配到的關鍵字,如果需要高亮匹配到的所有內容,只需要對附加屬性進行改造,以支持傳入一組位置和顏色信息。
最後分享一個可以解析一組有限的HTML標記並顯示它們的WPF控制項HtmlTextBlock ,通過這個控制項也可以實現查找結果中高亮關鍵字,甚至支持指定內容觸發事件做一些邏輯操作。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 經過一段時間的準備,【ASP.NET Core MVC開發實戰之商城系統】已經完成,目前代碼已開發完成,先將全部內容整理分享,如有不足之處,還請指正。 ...
  • # 認識.NET 日誌系統 ## 基本概念 1. 日誌級別:Trace{ logBuilder.AddConsole() //可多個Provider }) ``` 3. 需要記錄日誌的代碼,註入ILogger即可,T一般就用當前類,這個類的名字會輸出到日誌,方便定位錯誤,然後調用LogInforma ...
  • MySqlConnector有個MySqlBulkCopy批量新增數據方法,不過只能用DataTable,需要把list轉成DataTable代碼如下: MySqlBulkCopy mySqlBulkCopy = new MySqlBulkCopy(conn) { DestinationTableN ...
  • 有個項目需要獲取項目內所有Action,併在凌晨定時任務跑完所有介面檢查是否有介面報錯,寫瞭如下方法: /// <summary> /// 獲取Action註釋 /// </summary> /// <param name="root"></param> /// <param name="metho ...
  • dapper是C#程式員比較喜歡用的輕量級ORM,簡單易學,只是沒有批量新增以及修改(收費版有),寫瞭如下擴展 /// <summary> /// dapper MySQL批量新增修改擴展 /// </summary> public static class DapperExtensions { / ...
  • 在開發某一個需求的時候,領導要求使用RocketMQ(阿裡雲版) 作為消息隊列。生產者主要有WebAPI/MVC/JOB(控制台應用程式),然後消費者採用的是Windows服務。那[西瓜程式猿]來記錄一下如何使用RocketMQ(阿裡雲版),給各位小伙伴作為參考防止踩坑。 ...
  • ##### 常用基本配置項 ```xml net35; net40; net45; net451; net452; net46; net461; net462; net47; net471; net472; net48; netstandard2.0; netstandard2.1; netcore ...
  • ### 前言 在非同步編程中,處理非同步操作之間的數據流轉是一個比較常用的操作。`C#`非同步編程提供了一個強大的工具來解決這個問題,那就是`AsyncLocal`。它是一個線程本地存儲的機制,可以在非同步操作之間傳遞數據。它為我們提供了一種簡單而可靠的方式來共用數據,而不必擔心線程切換或非同步上下文的變化。 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...