在應用開發過程中,經常遇到這樣的需求:通過關鍵字查詢資料,把帶有關鍵字的資料顯示出來,同時在結果中高亮顯示關鍵字。在web開發中,只需在關鍵字上加一層標籤,然後設定標籤樣式就可以輕鬆實現。
在WPF中顯示文字內容通常採用TextBlock
控制元件,也可以採用類似的方式,通過內聯流內容元素Run
達到同樣的效果:
<TextBlock FontSize="20">
<Run Text="Hel" /><Run Foreground="Red" Text="lo " /><Run Text="Word" />
</TextBlock>
需要注意的是每個
Run
之間不要換行,如果換行的話,每個Run
之間會有間隙,看起來像增加了空格。
通過這種方式實現查詢結果中高亮關鍵字,需要把查詢結果拆分成三部分,然後繫結到Run
元素的Text
屬性,或者在後臺程式碼中使用TextBlock
的Inlines
屬性新增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
中指定內容高亮。
通過TextEffect
的PositionStart
、PositionCount
以及Foreground
屬性設定字串中需要高亮內容的起始位置、長度以及高亮顏色。定義附加屬性允許TextBlock
設定需要高亮的內容位置以及顏色。
ColoredLettering
(並不要求繼承DependencyObject
)。ColoredLettering
中註冊自定義的附加屬性,註冊附加屬性方式與註冊依賴屬性類似,不過附加屬性是用DependencyProperty.RegisterAttached
來註冊。TextEffect
的PositionStart
、PositionCount
以及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 ,通過這個控制元件也可以實現查詢結果中高亮關鍵字,甚至支援指定內容觸發事件做一些邏輯操作。