public partial class PopWindow : Window, INotifyPropertyChanged
{
public event EventHandler<string> SearchTermChanged;
public event EventHandler NavigatePrevious;
public event EventHandler NavigateNext;
public PopWindow()
{
InitializeComponent();
DataContext = this;
}
private string _searchTerm;
public string SearchTerm
{
get { return _searchTerm; }
set
{
if (_searchTerm != value)
{
_searchTerm = value;
OnPropertyChanged(nameof(SearchTerm));
// OnSearchTermChanged();
}
}
}
private void PrevButton_Click(object sender, RoutedEventArgs e)
{
NavigatePrevious?.Invoke(this, EventArgs.Empty);
}
private void NextButton_Click(object sender, RoutedEventArgs e)
{
NavigateNext?.Invoke(this, EventArgs.Empty);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void SearchBox_TextChanged(object sender, TextChangedEventArgs e)
{
SearchTermChanged?.Invoke(this, SearchTerm);
}
}
<Window x:Class="WpfApp2.MainWindow"
...
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel Name="MainGrid">
<RichTextBox x:Name="RichTextBox" VerticalAlignment="Top" Height="150">
<FlowDocument>
<Paragraph>
This is some example text in the RichTextBox. It can be used for testing the search functionality.
</Paragraph>
<Paragraph>
Another paragraph of example text to ensure the search and highlight feature works correctly.
</Paragraph>
</FlowDocument>
</RichTextBox>
<DataGrid x:Name="DataGrid" AutoGenerateColumns="False" VerticalAlignment="Top" Height="250">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding ID}" />
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
<DataGridTextColumn Header="Description" Binding="{Binding Description}" />
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</Window>
MainWindow
public partial class MainWindow : Window
{
private PopWindow _searchPopup;
private List<MatchLocation> _matches;
private int _currentMatchIndex;
public MainWindow()
{
InitializeComponent();
DataGrid.ItemsSource = new List<Item>
{
new Item { ID = 1, Name = "Item 1", Description = "This is the first item." },
new Item { ID = 2, Name = "Item 2", Description = "This is the second item." },
new Item { ID = 3, Name = "Item 3", Description = "This is the third item." }
};
this.PreviewKeyDown += new KeyEventHandler(MainWindow_PreviewKeyDown);
}
private void MainWindow_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.F && (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
{
_searchPopup = new PopWindow();
_searchPopup.SearchTermChanged += OnSearchTermChanged;
_searchPopup.NavigatePrevious += OnNavigatePrevious;
_searchPopup.NavigateNext += OnNavigateNext;
_searchPopup.Owner = this;
_searchPopup.Show();
_searchPopup.SearchBox.Focus();
}
}
private void OnSearchTermChanged(object sender, string searchTerm)
{
_matches = FindMatches(searchTerm);
_currentMatchIndex = -1;
if (_matches.Any())
{
for (int i = 0; i < _matches.Count - 1; i++)
{
NavigateToMatch(_matches[i], Brushes.Yellow);
}
NavigateToNextMatch();
NavigateToMatch(_matches[_currentMatchIndex], Brushes.Orange);
}
}
private void OnNavigatePrevious(object sender, EventArgs e)
{
if (_matches.Any() && _currentMatchIndex > 0)
{
NavigateToMatch(_matches[_currentMatchIndex], Brushes.Yellow);
_currentMatchIndex--;
NavigateToMatch(_matches[_currentMatchIndex], Brushes.Orange);
}
}
private void OnNavigateNext(object sender, EventArgs e)
{
if (_matches.Any() && _currentMatchIndex < _matches.Count - 1)
{
NavigateToMatch(_matches[_currentMatchIndex], Brushes.Yellow);
_currentMatchIndex++;
NavigateToMatch(_matches[_currentMatchIndex], Brushes.Orange);
}
}
private List<MatchLocation> FindMatches(string searchTerm)
{
var matches = new List<MatchLocation>();
foreach (var control in FindVisualChildren<TextBlock>(this))
{
matches.AddRange(FindMatchesInTextBlock(control, searchTerm));
}
foreach (var control in FindVisualChildren<TextBox>(this))
{
matches.AddRange(FindMatchesInTextBox(control, searchTerm));
}
return matches;
}
private List<MatchLocation> FindMatchesInTextBlock(TextBlock textBlock, string searchTerm)
{
var matches = new List<MatchLocation>();
var regex = new Regex(Regex.Escape(searchTerm), RegexOptions.IgnoreCase);
foreach (Match match in regex.Matches(textBlock.Text))
{
matches.Add(new MatchLocation
{
Control = textBlock,
Match = match
});
}
return matches;
}
private List<MatchLocation> FindMatchesInTextBox(TextBox textBox, string searchTerm)
{
var matches = new List<MatchLocation>();
var regex = new Regex(Regex.Escape(searchTerm), RegexOptions.IgnoreCase);
foreach (Match match in regex.Matches(textBox.Text))
{
matches.Add(new MatchLocation
{
Control = textBox,
Match = match
});
}
return matches;
}
private void NavigateToMatch(MatchLocation matchLocation, Brush highlightColor)
{
if (matchLocation.Control is TextBlock textBlock)
{
HighlightTextBlockMatch(textBlock, matchLocation.Match, highlightColor);
textBlock.BringIntoView();
}
else if (matchLocation.Control is TextBox textBox)
{
HighlightTextBoxMatch(textBox, matchLocation.Match, highlightColor);
textBox.Focus();
textBox.CaretIndex = matchLocation.Match.Index;
textBox.ScrollToLine(textBox.GetLineIndexFromCharacterIndex(matchLocation.Match.Index));
}
}
private void HighlightTextBlockMatch(TextBlock textBlock, Match match, Brush highlightColor)
{
var text = textBlock.Text;
textBlock.Inlines.Clear();
var startIndex = 0;
foreach (Match m in Regex.Matches(text, Regex.Escape(match.Value), RegexOptions.IgnoreCase))
{
if (m.Index > startIndex)
{
textBlock.Inlines.Add(new Run(text.Substring(startIndex, m.Index - startIndex)));
}
var run = new Run(m.Value)
{
Background = highlightColor
};
textBlock.Inlines.Add(run);
startIndex = m.Index + m.Length;
}
if (startIndex < text.Length)
{
textBlock.Inlines.Add(new Run(text.Substring(startIndex)));
}
}
private void HighlightTextBoxMatch(TextBox textBox, Match match, Brush highlightColor)
{
var startIndex = textBox.Text.IndexOf(match.Value, StringComparison.CurrentCultureIgnoreCase);
if (startIndex >= 0)
{
textBox.Select(startIndex, match.Length);
textBox.SelectionBrush = highlightColor;
}
}
private void NavigateToNextMatch()
{
if (_matches.Any() && _currentMatchIndex < _matches.Count - 1)
{
_currentMatchIndex++;
NavigateToMatch(_matches[_currentMatchIndex], Brushes.Yellow);
}
}
public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
yield return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}
private class MatchLocation
{
public UIElement Control { get; set; }
public Match Match { get; set; }
}
public class Item
{
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
}