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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...