A WPF Searchable TextBlock Control with Highlighting
So I needed a TextBlock that was searchable and from searching online, it seems a lot of people need one too. So I decided to inherit TextBlock and write a SearchableTextBox. It is really easy to use.
WPF SearchTextBlock Example Project
Here is an example application you can download or clone: WpfSharp.Controls
SearchableTextBlock Explained
Here are the steps for creating this SearchableTextBlock
- Hide the Text dependency property by making it private. I did this because TextBlock doesn’t have a TextChanged event.
- Create a public HighlightableText dependency property that wraps the Text property. You can now bind to HighlightableText.
- Add a dependency property each for HighlightForeground and HighlightBackground.
- Added a list of searchable words as a dependency property and some code to turn the word list into a regular expression.
- Add a new set to the Text property so that it enters the string value as Run objects and adds the highlighting.
Here is the object for you to browse.
using System; using System.Text.RegularExpressions; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Media; using System.Collections.Generic; namespace HighlightText { public class SearchableTextBlock : TextBlock { #region Constructors // Summary: // Initializes a new instance of the System.Windows.Controls.TextBlock class. public SearchableTextBlock() { //Binding binding = new Binding("HighlightableText"); //binding.Source = this; //binding.Mode = BindingMode.TwoWay; //SetBinding(TextProperty, binding); } public SearchableTextBlock(Inline inline) : base(inline) { } #endregion #region Properties new private string Text { set { if (string.IsNullOrWhiteSpace(RegularExpression) || !IsValidRegex(RegularExpression)) { base.Text = value; return; } Inlines.Clear(); string[] split = Regex.Split(value, RegularExpression, RegexOptions.IgnoreCase); foreach (var str in split) { Run run = new Run(str); if (Regex.IsMatch(str, RegularExpression, RegexOptions.IgnoreCase)) { run.Background = HighlightBackground; run.Foreground = HighlightForeground; } Inlines.Add(run); } } } public string RegularExpression { get { return _RegularExpression; } set { _RegularExpression = value; Text = base.Text; } } private string _RegularExpression; #endregion #region Dependency Properties #region Search Words public List SearchWords { get { if (null == (List)GetValue(SearchWordsProperty)) SetValue(SearchWordsProperty, new List()); return (List)GetValue(SearchWordsProperty); } set { SetValue(SearchWordsProperty, value); UpdateRegex(); } } // Using a DependencyProperty as the backing store for SearchStringList. This enables animation, styling, binding, etc... public static readonly DependencyProperty SearchWordsProperty = DependencyProperty.Register("SearchWords", typeof(List), typeof(SearchableTextBlock), new PropertyMetadata(new PropertyChangedCallback(SearchWordsPropertyChanged))); public static void SearchWordsPropertyChanged(DependencyObject inDO, DependencyPropertyChangedEventArgs inArgs) { SearchableTextBlock stb = inDO as SearchableTextBlock; if (stb == null) return; stb.UpdateRegex(); } #endregion #region HighlightableText public event EventHandler OnHighlightableTextChanged; public string HighlightableText { get { return (string)GetValue(HighlightableTextProperty); } set { SetValue(HighlightableTextProperty, value); } } // Using a DependencyProperty as the backing store for HighlightableText. This enables animation, styling, binding, etc... public static readonly DependencyProperty HighlightableTextProperty = DependencyProperty.Register("HighlightableText", typeof(string), typeof(SearchableTextBlock), new PropertyMetadata(new PropertyChangedCallback(HighlightableTextChanged))); public static void HighlightableTextChanged(DependencyObject inDO, DependencyPropertyChangedEventArgs inArgs) { SearchableTextBlock stb = inDO as SearchableTextBlock; stb.Text = stb.HighlightableText; // Raise the event by using the () operator. if (stb.OnHighlightableTextChanged != null) stb.OnHighlightableTextChanged(stb, null); } #endregion #region HighlightForeground public event EventHandler OnHighlightForegroundChanged; public Brush HighlightForeground { get { if ((Brush)GetValue(HighlightForegroundProperty) == null) SetValue(HighlightForegroundProperty, Brushes.Black); return (Brush)GetValue(HighlightForegroundProperty); } set { SetValue(HighlightForegroundProperty, value); } } // Using a DependencyProperty as the backing store for HighlightForeground. This enables animation, styling, binding, etc... public static readonly DependencyProperty HighlightForegroundProperty = DependencyProperty.Register("HighlightForeground", typeof(Brush), typeof(SearchableTextBlock), new PropertyMetadata(new PropertyChangedCallback(HighlightableForegroundChanged))); public static void HighlightableForegroundChanged(DependencyObject inDO, DependencyPropertyChangedEventArgs inArgs) { SearchableTextBlock stb = inDO as SearchableTextBlock; // Raise the event by using the () operator. if (stb.OnHighlightForegroundChanged != null) stb.OnHighlightForegroundChanged(stb, null); } #endregion #region HighlightBackground public event EventHandler OnHighlightBackgroundChanged; public Brush HighlightBackground { get { if ((Brush)GetValue(HighlightBackgroundProperty) == null) SetValue(HighlightBackgroundProperty, Brushes.Yellow); return (Brush)GetValue(HighlightBackgroundProperty); } set { SetValue(HighlightBackgroundProperty, value); } } // Using a DependencyProperty as the backing store for HighlightBackground. This enables animation, styling, binding, etc... public static readonly DependencyProperty HighlightBackgroundProperty = DependencyProperty.Register("HighlightBackground", typeof(Brush), typeof(SearchableTextBlock), new PropertyMetadata(new PropertyChangedCallback(HighlightableBackgroundChanged))); public static void HighlightableBackgroundChanged(DependencyObject inDO, DependencyPropertyChangedEventArgs inArgs) { SearchableTextBlock stb = inDO as SearchableTextBlock; // Raise the event by using the () operator. if (stb.OnHighlightBackgroundChanged != null) stb.OnHighlightBackgroundChanged(stb, null); } #endregion #endregion #region Methods public void AddSearchString(String inString) { SearchWords.Add(inString); Update(); } public void Update() { UpdateRegex(); } public void RefreshHighlightedText() { Text = base.Text; } private void UpdateRegex() { string newRegularExpression = string.Empty; foreach (string s in SearchWords) { if (newRegularExpression.Length > 0) newRegularExpression += "|"; newRegularExpression += RegexWrap(s); } if (RegularExpression != newRegularExpression) RegularExpression = newRegularExpression; } public bool IsValidRegex(string inRegex) { if (string.IsNullOrEmpty(inRegex)) return false; try { Regex.Match("", inRegex); } catch (ArgumentException) { return false; } return true; } private string RegexWrap(string inString) { // Use positive look ahead and positive look behind tags // so the break is before and after each word, so the // actual word is not removed by Regex.Split() return String.Format("(?={0})|(?<={0})", inString); } #endregion } }
[…] 也可以参考SearchableTextBlock写一个高亮的文本框,一了百了,但我希望通过这个有趣的功能多介绍几种知识。 […]
Moving Cost
A WPF Searchable TextBlock Ccontrol with Highlighting | WPF
Hallo developer,
thanks for this usefull Control implementation. It works very fine. I have used it in a Datatemplate of a Datagrid.
Now i want to use a ‘case sensitive’ search.
For that i have added a new Dependency Property bool “IsMatchCase” in that Control.
I put this in:
But it matched always insensitive.
What works wrong?
Greetings
Jürgen
You wouldn’t do it in the IsValidRegex method. The regex is going to be correct or not regardless of the options.
You would create Bool dependency property for whether the SearchTextBlock is IgnoreCase or not, which you have done, I see, but you should really name it IgnoreCase not IsMatchCase, but that is semantics.
You then need to make the change in the “Text” properties set. Lines 42 and 46.
You could make the DependencyProperty of type RegexOptions instead of bool, and then you support all RegexOptions. 🙂
Even better, I updated the solution for you. I’ve been meaning to start a WpfSharp.Controls project on GitHub. Now is as good of a time as any.
https://github.com/rhyous/WpfSharp.Controls
[…] A WPF Searchable TextBlock Control with Highlighting Category: WPF | Comment (RSS) | Trackback […]