My tip is based on the excellent article A Universal WPF Find / Replace Dialog by Thomas Willwacher. For anyone interested, I've distilled this project down to a bare-bones Find and Replace tool for the AvalonEdit editor only.
Here's the XAML for your "FindReplaceDialog.xaml" file...
<Window x:Class="FindReplace.FindReplaceDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Find and Replace" WindowStartupLocation="CenterOwner"
Width="300" SizeToContent="Height" ResizeMode="NoResize"
WindowStyle="ToolWindow" ShowInTaskbar="False" Closed="Window_Closed">
<Grid Margin="0,4">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TabControl Name="tabMain" Height="Auto" Grid.ColumnSpan="2">
<TabItem Header="Find">
<StackPanel>
<TextBlock Margin="3">Text to Find:</TextBlock>
<TextBox Margin="3" Name="txtFind" />
<Button Margin="5" HorizontalAlignment="Right"
Width="80" Content="Find Next" Click="FindNextClick" />
</StackPanel>
</TabItem>
<TabItem Header="Replace">
<StackPanel>
<TextBlock Margin="3">Text to Find:</TextBlock>
<TextBox Margin="3" Name="txtFind2" />
<TextBlock Margin="3" Text="Replace with:" />
<TextBox Margin="3" Name="txtReplace" />
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button Margin="5" HorizontalAlignment="Right"
Width="80" Content="Find Next" Click="FindNext2Click" />
<Button Margin="5" HorizontalAlignment="Right"
Width="80" Content="Replace" Click="ReplaceClick" />
<Button Margin="5" HorizontalAlignment="Right"
Width="80" Content="Replace All" Click="ReplaceAllClick" />
</StackPanel>
</StackPanel>
</TabItem>
</TabControl>
<CheckBox Grid.Row="1" Grid.Column="0" Margin="10,2"
Name="cbCaseSensitive" Content="Match case" IsChecked="true" />
<CheckBox Grid.Row="2" Grid.Column="0" Margin="10,2"
Name="cbWholeWord" Content="Match whole word" IsChecked="true" />
<CheckBox Grid.Row="1" Grid.Column="1" Margin="10,2"
Name="cbRegex" Content="Regular Expression" />
<CheckBox Grid.Row="2" Grid.Column="1" Margin="10,2"
Name="cbWildcards" Content="Wildcards" />
<CheckBox Grid.Row="3" Grid.Column="1" Margin="10,2"
Name="cbSearchUp" Content="Search up" />
</Grid>
</Window>
And here's the code-behind for your "FindReplaceDialog.xaml.cs" file...
using System.Text.RegularExpressions;
using System.Windows;
using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Document;
namespace FindReplace
{
public partial class FindReplaceDialog : Window
{
private static string textToFind = "";
private static bool caseSensitive = true;
private static bool wholeWord = true;
private static bool useRegex = false;
private static bool useWildcards = false;
private static bool searchUp = false;
private TextEditor editor;
public FindReplaceDialog(TextEditor editor)
{
InitializeComponent();
this.editor = editor;
txtFind.Text = txtFind2.Text = textToFind;
cbCaseSensitive.IsChecked = caseSensitive;
cbWholeWord.IsChecked = wholeWord;
cbRegex.IsChecked = useRegex;
cbWildcards.IsChecked = useWildcards;
cbSearchUp.IsChecked = searchUp;
}
private void Window_Closed(object sender, System.EventArgs e)
{
textToFind = txtFind2.Text;
caseSensitive = (cbCaseSensitive.IsChecked == true);
wholeWord = (cbWholeWord.IsChecked == true);
useRegex = (cbRegex.IsChecked == true);
useWildcards = (cbWildcards.IsChecked == true);
searchUp = (cbSearchUp.IsChecked == true);
theDialog = null;
}
private void FindNextClick(object sender, RoutedEventArgs e)
{
if (!FindNext(txtFind.Text))
SystemSounds.Beep.Play();
}
private void FindNext2Click(object sender, RoutedEventArgs e)
{
if (!FindNext(txtFind2.Text))
SystemSounds.Beep.Play();
}
private void ReplaceClick(object sender, RoutedEventArgs e)
{
Regex regex = GetRegEx(txtFind2.Text);
string input = editor.Text.Substring(editor.SelectionStart, editor.SelectionLength);
Match match = regex.Match(input);
bool replaced = false;
if (match.Success && match.Index == 0 && match.Length == input.Length)
{
editor.Document.Replace(editor.SelectionStart, editor.SelectionLength, txtReplace.Text);
replaced = true;
}
if (!FindNext(txtFind2.Text) && !replaced)
SystemSounds.Beep.Play();
}
private void ReplaceAllClick(object sender, RoutedEventArgs e)
{
if (MessageBox.Show("Are you sure you want to Replace All occurences of \"" +
txtFind2.Text + "\" with \"" + txtReplace.Text + "\"?",
"Replace All", MessageBoxButton.OKCancel, MessageBoxImage.Question) == MessageBoxResult.OK)
{
Regex regex = GetRegEx(txtFind2.Text, true);
int offset = 0;
editor.BeginChange();
foreach (Match match in regex.Matches(editor.Text))
{
editor.Document.Replace(offset + match.Index, match.Length, txtReplace.Text);
offset += txtReplace.Text.Length - match.Length;
}
editor.EndChange();
}
}
private bool FindNext(string textToFind)
{
Regex regex = GetRegEx(textToFind);
int start = regex.Options.HasFlag(RegexOptions.RightToLeft) ?
editor.SelectionStart : editor.SelectionStart + editor.SelectionLength;
Match match = regex.Match(editor.Text, start);
if (!match.Success) {
if (regex.Options.HasFlag(RegexOptions.RightToLeft))
match = regex.Match(editor.Text, editor.Text.Length);
else
match = regex.Match(editor.Text, 0);
}
if (match.Success)
{
editor.Select(match.Index, match.Length);
TextLocation loc = editor.Document.GetLocation(match.Index);
editor.ScrollTo(loc.Line, loc.Column);
}
return match.Success;
}
private Regex GetRegEx(string textToFind, bool leftToRight = false)
{
RegexOptions options = RegexOptions.None;
if (cbSearchUp.IsChecked == true && !leftToRight)
options |= RegexOptions.RightToLeft;
if (cbCaseSensitive.IsChecked == false)
options |= RegexOptions.IgnoreCase;
if (cbRegex.IsChecked == true)
{
return new Regex(textToFind, options);
}
else
{
string pattern = Regex.Escape(textToFind);
if (cbWildcards.IsChecked == true)
pattern = pattern.Replace("\\*", ".*").Replace("\\?", ".");
if (cbWholeWord.IsChecked == true)
pattern = "\\b" + pattern + "\\b";
return new Regex(pattern, options);
}
}
private static FindReplaceDialog theDialog = null;
public static void ShowForReplace(TextEditor editor)
{
if (theDialog == null)
{
theDialog = new FindReplaceDialog(editor);
theDialog.tabMain.SelectedIndex = 1;
theDialog.Show();
theDialog.Activate();
}
else
{
theDialog.tabMain.SelectedIndex = 1;
theDialog.Activate();
}
if (!editor.TextArea.Selection.IsMultiline)
{
theDialog.txtFind.Text = theDialog.txtFind2.Text = editor.TextArea.Selection.GetText();
theDialog.txtFind.SelectAll();
theDialog.txtFind2.SelectAll();
theDialog.txtFind2.Focus();
}
}
}
}
You can open this Find and Replace tool in your ApplicationCommands.Replace
handler by simply calling the static
method...
FindReplaceDialog.ShowForReplace(myAvalonEditor);
I hope that you find this code useful!
-Bruce