Click here to Skip to main content
15,880,543 members
Articles / Desktop Programming / WPF
Tip/Trick

A Find and Replace Tool for AvalonEdit

Rate me:
Please Sign up or sign in to vote.
4.94/5 (16 votes)
3 May 2014CPOL 32.7K   13   11
A bare-bones Find and Replace tool for AvalonEdit

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.

Image 1

Here's the XAML for your "FindReplaceDialog.xaml" file...

XML
<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...

C#
using System.Text.RegularExpressions;
using System.Windows;
using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Document;

namespace FindReplace
{
    /// <summary>
    /// Interaction logic for FindReplaceDialog.xaml
    /// </summary>
    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)  // start again from beginning or end
            {
                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...

C#
FindReplaceDialog.ShowForReplace(myAvalonEditor);

I hope that you find this code useful!

-Bruce

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Greene & Morehead Engineering, Inc.
United States United States
Motion Commander Foundation (MCF) is a set of .NET libraries, documentation and examples that enable the rapid creation of sophisticated and professional C# or Visual Basic machine control applications.

MCF provides the infrastructure (data management, plotting, alarms, message logging, user login, internationalization, Modbus, MTConnect, etc) - so that you can focus on the business logic and user interface for your machine!

MCF is designed around Microsoft .NET best practices to be intuitive for experienced developers, and examples are provided that will enable even novice .NET developers to get started easily.

Comments and Discussions

 
GeneralMy vote of 5 Pin
peterboulton25-Oct-22 5:26
professionalpeterboulton25-Oct-22 5:26 
GeneralMy vote of 5 Pin
xx xx 202223-Mar-22 20:13
xx xx 202223-Mar-22 20:13 
QuestionApplicationCommands Pin
Knight school25-Sep-20 21:38
Knight school25-Sep-20 21:38 
PraiseNice Pin
Member 1378558331-May-19 1:12
Member 1378558331-May-19 1:12 
QuestionThanks Pin
VEMS13-Sep-18 14:18
VEMS13-Sep-18 14:18 
QuestionHow to assure that the dialog doesn't overlay the found text? Pin
Valya-S10-Aug-17 10:36
Valya-S10-Aug-17 10:36 
QuestionThanks for the code... Pin
Your Display Name Here4-Aug-16 6:18
Your Display Name Here4-Aug-16 6:18 
AnswerRe: Thanks for the code... Pin
Bruce Greene4-Aug-16 7:06
Bruce Greene4-Aug-16 7:06 
QuestionHow can one use this Find Replace in MVVM design pattern Pin
Member 1073467919-May-15 17:14
Member 1073467919-May-15 17:14 
I do not use myAvalonEditor in my code behind, I use it in my view. So how do I make this MVVM compatible? Please help me get this working.
QuestionFind-and-Replace-Tool-for-AvalonEdit Pin
Member 984436618-Mar-15 1:07
Member 984436618-Mar-15 1:07 
AnswerRe: Find-and-Replace-Tool-for-AvalonEdit Pin
Bruce Greene18-Mar-15 6:57
Bruce Greene18-Mar-15 6:57 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.