Skip to main content

WPF C#/VB
Notepad App with Spellchecker

C#

// AZUL CODING ---------------------------------------
// WPF C#/VB - Notepad App with Spellchecker
// https://youtu.be/7lxpGNWRvWs


using System;
using System.Text;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Win32;
using System.Windows.Media;
using System.Text.RegularExpressions;
using System.Drawing.Text;

namespace AzulNotepad;

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    private int lastSearchIndex = 0;

    private static readonly OpenFileDialog OpenDialog = new()
    {
        Title = "Select a text file",
        Filter = "Text files (.txt)|*.txt"
    };

    private static readonly SaveFileDialog SaveDialog = new()
    {
        Title = "Save to a text file",
        Filter = "Text files (.txt)|*.txt"
    };

    public MainWindow()
    {
        InitializeComponent();

        FontComboBox.ItemsSource = new InstalledFontCollection().Families.Select(x => x.Name);
        FontComboBox.SelectedItem = MainTextBox.FontFamily.ToString();

        FontSizeComboBox.ItemsSource = Enumerable.Range(0, 9).Select(i => 8 + (i * 2)).ToList();
        FontSizeComboBox.SelectedItem = (int)MainTextBox.FontSize;
    }

    private void MainTextBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        UndoBtn.IsEnabled = MainTextBox.CanUndo;
        RedoBtn.IsEnabled = MainTextBox.CanRedo;

        UpdateStatusBar();
    }

    private void MainTextBox_SelectionChanged(object sender, RoutedEventArgs e)
    {
        UpdateStatusBar();
    }

    #region Open and save

    private void OpenBtn_Click(object sender, RoutedEventArgs e)
    {
        if (OpenDialog.ShowDialog() == true)
        {
            try
            {
                MainTextBox.Text = File.ReadAllText(OpenDialog.FileName);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }
    }

    private void SaveBtn_Click(object sender, RoutedEventArgs e)
    {
        if (SaveDialog.ShowDialog() == true)
        {
            try
            {
                File.WriteAllText(SaveDialog.FileName, MainTextBox.Text, Encoding.UTF8);
                MessageBox.Show("File saved successfully", "Success", MessageBoxButton.OK, MessageBoxImage.Information);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }
    }

    #endregion
    #region Undo/redo

    private void UndoBtn_Click(object sender, RoutedEventArgs e)
    {
        MainTextBox.Undo();

        if (!MainTextBox.CanUndo)
            UndoBtn.IsEnabled = false;
    }

    private void RedoBtn_Click(object sender, RoutedEventArgs e)
    {
        MainTextBox.Redo();

        if (!MainTextBox.CanRedo)
            RedoBtn.IsEnabled = false;
    }

    #endregion
    #region Clipboard

    private void CutBtn_Click(object sender, RoutedEventArgs e)
    {
        MainTextBox.Cut();
    }

    private void CopyBtn_Click(object sender, RoutedEventArgs e)
    {
        MainTextBox.Copy();
    }

    private void PasteBtn_Click(object sender, RoutedEventArgs e)
    {
        MainTextBox.Paste();
    }

    #endregion
    #region Find and replace

    private void FindReplaceBtn_Click(object sender, RoutedEventArgs e)
    {
        FindReplaceMenu.Visibility = FindReplaceMenu.Visibility == Visibility.Visible
            ? Visibility.Collapsed : Visibility.Visible;
    }

    private (RegexOptions regexOptions, string pattern) GetSearchOptions(string searchText)
    {
        RegexOptions regexOptions = MatchCaseCheckBox.IsChecked ?? false
            ? RegexOptions.None : RegexOptions.IgnoreCase;

        string pattern = MatchWordCheckBox.IsChecked ?? false
            ? $@"\b{Regex.Escape(searchText)}\b"
            : Regex.Escape(searchText);

        return (regexOptions, pattern);
    }

    private void FindOrReplace(bool replace = false)
    {
        string searchText = FindTextBox.Text;
        string replaceText = ReplaceTextBox.Text;
        string mainText = MainTextBox.Text;

        if (string.IsNullOrEmpty(searchText))
        {
            MessageBox.Show("Please enter text to find.", "Find and replace",
                MessageBoxButton.OK, MessageBoxImage.Information);
            return;
        }

        lastSearchIndex = Math.Max(0, Math.Min(lastSearchIndex, mainText.Length));
        string remainingText = mainText[lastSearchIndex..];

        (RegexOptions regexOptions, string pattern) = GetSearchOptions(searchText);
        Match match = Regex.Match(remainingText, pattern, regexOptions);

        if (match.Success)
        {
            int absoluteIndex = lastSearchIndex + match.Index;
            if (replace)
            {
                mainText = mainText.Remove(absoluteIndex, match.Length).Insert(absoluteIndex, replaceText);
                MainTextBox.Text = mainText;
            }

            MainTextBox.Select(absoluteIndex, replace ? replaceText.Length : match.Length);
            MainTextBox.Focus();

            lastSearchIndex = absoluteIndex + (replace ? replaceText.Length : match.Length);
        }
        else
        {
            match = Regex.Match(mainText, pattern, regexOptions);

            if (match.Success)
            {
                if (replace)
                {
                    mainText = mainText.Remove(match.Index, match.Length).Insert(match.Index, replaceText);
                    MainTextBox.Text = mainText;
                }

                MainTextBox.Select(match.Index, replace ? replaceText.Length : match.Length);
                MainTextBox.Focus();

                lastSearchIndex = match.Index + (replace ? replaceText.Length : match.Length);
            }
            else
            {
                MessageBox.Show($"Cannot find \"{searchText}\"", "Find and replace",
                    MessageBoxButton.OK, MessageBoxImage.Information);

                lastSearchIndex = 0;
            }
        }
    }

    private void FindNextBtn_Click(object sender, RoutedEventArgs e)
    {
        FindOrReplace();
    }

    private void ReplaceBtn_Click(object sender, RoutedEventArgs e)
    {
        FindOrReplace(true);
    }

    private void ReplaceAllBtn_Click(object sender, RoutedEventArgs e)
    {
        string searchText = FindTextBox.Text;
        string replaceText = ReplaceTextBox.Text;
        string mainText = MainTextBox.Text;

        if (string.IsNullOrEmpty(searchText))
        {
            MessageBox.Show("Please enter text to find.", "Find and replace",
                MessageBoxButton.OK, MessageBoxImage.Information);
            return;
        }

        (RegexOptions regexOptions, string pattern) = GetSearchOptions(searchText);

        string replacedText = Regex.Replace(mainText, pattern, replaceText, regexOptions);
        int replacementCount = Regex.Matches(mainText, pattern, regexOptions).Count;

        MainTextBox.Text = replacedText;

        MessageBox.Show($"Replaced {replacementCount} occurrence(s).", "Find and replace",
            MessageBoxButton.OK, MessageBoxImage.Information);

        lastSearchIndex = 0;
    }

    #endregion
    #region Spellchecker and other features

    private void SpellcheckerBtn_Click(object sender, RoutedEventArgs e)
    {
        MainTextBox.SpellCheck.IsEnabled = !MainTextBox.SpellCheck.IsEnabled;
    }

    private void DateTimeBtn_Click(object sender, RoutedEventArgs e)
    {
        string currentDateTime = DateTime.Now.ToString("dd/MM/yyyy HH:mm");

        MainTextBox.SelectedText = currentDateTime;
        MainTextBox.Focus();
    }

    private void FontBtn_Click(object sender, RoutedEventArgs e)
    {
        FontMenu.Visibility = FontMenu.Visibility == Visibility.Visible
            ? Visibility.Collapsed : Visibility.Visible;
    }

    private void UpdateFontBtn_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            MainTextBox.FontFamily = new FontFamily(FontComboBox.Text);

            double fontSize = Convert.ToDouble(FontSizeComboBox.Text);
            if (fontSize <= 1)
            {
                MessageBox.Show("Please enter a font size greater than 1.", "Font size",
                    MessageBoxButton.OK, MessageBoxImage.Exclamation);
                return;
            }

            MainTextBox.FontSize = fontSize;
        }
        catch
        {
            MessageBox.Show("Please enter a valid font name and size.", "Font",
                    MessageBoxButton.OK, MessageBoxImage.Exclamation);
        }
    }

    private void UpdateStatusBar()
    {
        int caretIndex = MainTextBox.CaretIndex;
        int chars = MainTextBox.Text.Length;

        string textBeforeCaret = MainTextBox.Text[..caretIndex];
        int lines = textBeforeCaret.Split('\n').Length;

        int lastNewLineIndex = textBeforeCaret.LastIndexOf('\n');
        int columns = lastNewLineIndex == -1
            ? caretIndex + 1
            : caretIndex - lastNewLineIndex;

        string trimmedText = MainTextBox.Text.Trim();
        int words = string.IsNullOrEmpty(trimmedText)
            ? 0 : Regex.Matches(trimmedText, @"\S+").Count;

        StatusText.Text = $"Ln {lines}  •  Col {columns}{chars} characters  •  {words} words";
    }

    #endregion
}

Enjoying this tutorial?


VB.NET

' AZUL CODING ---------------------------------------
' WPF C#/VB - Notepad App with Spellchecker
' https://youtu.be/7lxpGNWRvWs


Imports System
Imports System.Text
Imports System.IO
Imports Microsoft.Win32
Imports System.Text.RegularExpressions
Imports System.Drawing.Text

Class MainWindow

    Private lastSearchIndex As Integer = 0

    Private Shared ReadOnly OpenDialog As New OpenFileDialog() With {
        .Title = "Select a text file",
        .Filter = "Text files (.txt)|*.txt"
    }

    Private Shared ReadOnly SaveDialog As New SaveFileDialog() With {
        .Title = "Save to a text file",
        .Filter = "Text files (.txt)|*.txt"
    }

    Public Sub New()
        InitializeComponent()

        FontComboBox.ItemsSource = New InstalledFontCollection().Families.Select(Function(x) x.Name)
        FontComboBox.SelectedItem = MainTextBox.FontFamily.ToString()

        FontSizeComboBox.ItemsSource = Enumerable.Range(0, 9).Select(Function(i) 8 + (i * 2)).ToList()
        FontSizeComboBox.SelectedItem = CInt(MainTextBox.FontSize)
    End Sub

    Private Sub MainTextBox_TextChanged(sender As Object, e As TextChangedEventArgs)
        UndoBtn.IsEnabled = MainTextBox.CanUndo
        RedoBtn.IsEnabled = MainTextBox.CanRedo

        UpdateStatusBar()
    End Sub

    Private Sub MainTextBox_SelectionChanged(sender As Object, e As RoutedEventArgs)
        UpdateStatusBar()
    End Sub

#Region "Open and save"

    Private Sub OpenBtn_Click(sender As Object, e As RoutedEventArgs)
        If OpenDialog.ShowDialog() = True Then
            Try
                MainTextBox.Text = File.ReadAllText(OpenDialog.FileName)
            Catch ex As Exception
                MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error)
            End Try
        End If
    End Sub

    Private Sub SaveBtn_Click(sender As Object, e As RoutedEventArgs)
        If SaveDialog.ShowDialog() = True Then
            Try
                File.WriteAllText(SaveDialog.FileName, MainTextBox.Text, Encoding.UTF8)
                MessageBox.Show("File saved successfully", "Success", MessageBoxButton.OK, MessageBoxImage.Information)
            Catch ex As Exception
                MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error)
            End Try
        End If
    End Sub

#End Region
#Region "Undo/redo"

    Private Sub UndoBtn_Click(sender As Object, e As RoutedEventArgs)
        MainTextBox.Undo()

        If Not MainTextBox.CanUndo Then
            UndoBtn.IsEnabled = False
        End If
    End Sub

    Private Sub RedoBtn_Click(sender As Object, e As RoutedEventArgs)
        MainTextBox.Redo()

        If Not MainTextBox.CanRedo Then
            RedoBtn.IsEnabled = False
        End If
    End Sub

#End Region
#Region "Clipboard"

    Private Sub CutBtn_Click(sender As Object, e As RoutedEventArgs)
        MainTextBox.Cut()
    End Sub

    Private Sub CopyBtn_Click(sender As Object, e As RoutedEventArgs)
        MainTextBox.Copy()
    End Sub

    Private Sub PasteBtn_Click(sender As Object, e As RoutedEventArgs)
        MainTextBox.Paste()
    End Sub

#End Region
#Region "Find and replace"

    Private Sub FindReplaceBtn_Click(sender As Object, e As RoutedEventArgs)
        FindReplaceMenu.Visibility = If(FindReplaceMenu.Visibility = Visibility.Visible,
            Visibility.Collapsed, Visibility.Visible)
    End Sub

    Private Function GetSearchOptions(searchText As String) As Tuple(Of RegexOptions, String)
        Dim regexOptions As RegexOptions = If(MatchCaseCheckBox.IsChecked = True,
            regexOptions.None, regexOptions.IgnoreCase)

        Dim pattern As String = If(MatchWordCheckBox.IsChecked = True,
            $"\b{Regex.Escape(searchText)}\b",
            Regex.Escape(searchText))

        Return New Tuple(Of RegexOptions, String)(regexOptions, pattern)
    End Function

    Private Sub FindOrReplace(Optional replace As Boolean = False)
        Dim searchText As String = FindTextBox.Text
        Dim replaceText As String = ReplaceTextBox.Text
        Dim mainText As String = MainTextBox.Text

        If String.IsNullOrEmpty(searchText) Then
            MessageBox.Show("Please enter text to find.", "Find and replace",
                MessageBoxButton.OK, MessageBoxImage.Information)
            Return
        End If

        lastSearchIndex = Math.Max(0, Math.Min(lastSearchIndex, mainText.Length))
        Dim remainingText As String = mainText.Substring(lastSearchIndex)

        Dim searchOptionsResult = GetSearchOptions(searchText)
        Dim regexOptions As RegexOptions = searchOptionsResult.Item1
        Dim pattern As String = searchOptionsResult.Item2
        Dim match As Match = Regex.Match(remainingText, pattern, regexOptions)

        If match.Success Then
            Dim absoluteIndex As Integer = lastSearchIndex + match.Index
            If replace Then
                mainText = mainText.Remove(absoluteIndex, match.Length).Insert(absoluteIndex, replaceText)
                MainTextBox.Text = mainText
            End If

            MainTextBox.Select(absoluteIndex, If(replace, replaceText.Length, match.Length))
            MainTextBox.Focus()

            lastSearchIndex = absoluteIndex + If(replace, replaceText.Length, match.Length)
        Else
            match = Regex.Match(mainText, pattern, regexOptions)

            If match.Success Then
                If replace Then
                    mainText = mainText.Remove(match.Index, match.Length).Insert(match.Index, replaceText)
                    MainTextBox.Text = mainText
                End If

                MainTextBox.Select(match.Index, If(replace, replaceText.Length, match.Length))
                MainTextBox.Focus()

                lastSearchIndex = match.Index + If(replace, replaceText.Length, match.Length)
            Else
                MessageBox.Show($"Cannot find ""{searchText}""", "Find and replace",
                    MessageBoxButton.OK, MessageBoxImage.Information)

                lastSearchIndex = 0
            End If
        End If
    End Sub

    Private Sub FindNextBtn_Click(sender As Object, e As RoutedEventArgs)
        FindOrReplace()
    End Sub

    Private Sub ReplaceBtn_Click(sender As Object, e As RoutedEventArgs)
        FindOrReplace(True)
    End Sub

    Private Sub ReplaceAllBtn_Click(sender As Object, e As RoutedEventArgs)
        Dim searchText As String = FindTextBox.Text
        Dim replaceText As String = ReplaceTextBox.Text
        Dim mainText As String = MainTextBox.Text

        If String.IsNullOrEmpty(searchText) Then
            MessageBox.Show("Please enter text to find.", "Find and replace",
                MessageBoxButton.OK, MessageBoxImage.Information)
            Return
        End If

        Dim searchOptionsResult = GetSearchOptions(searchText)
        Dim regexOptions As RegexOptions = searchOptionsResult.Item1
        Dim pattern As String = searchOptionsResult.Item2

        Dim replacedText As String = Regex.Replace(mainText, pattern, replaceText, regexOptions)
        Dim replacementCount As Integer = Regex.Matches(mainText, pattern, regexOptions).Count

        MainTextBox.Text = replacedText

        MessageBox.Show($"Replaced {replacementCount} occurrence(s).", "Find and replace",
            MessageBoxButton.OK, MessageBoxImage.Information)

        lastSearchIndex = 0
    End Sub

#End Region
#Region "Spellchecker and other features"

    Private Sub SpellcheckerBtn_Click(sender As Object, e As RoutedEventArgs)
        MainTextBox.SpellCheck.IsEnabled = Not MainTextBox.SpellCheck.IsEnabled
    End Sub

    Private Sub DateTimeBtn_Click(sender As Object, e As RoutedEventArgs)
        Dim currentDateTime As String = Now.ToString("dd/MM/yyyy HH:mm")

        MainTextBox.SelectedText = currentDateTime
        MainTextBox.Focus()
    End Sub

    Private Sub FontBtn_Click(sender As Object, e As RoutedEventArgs)
        FontMenu.Visibility = If(FontMenu.Visibility = Visibility.Visible,
            Visibility.Collapsed, Visibility.Visible)
    End Sub

    Private Sub UpdateFontBtn_Click(sender As Object, e As RoutedEventArgs)
        Try
            MainTextBox.FontFamily = New FontFamily(FontComboBox.Text)

            Dim fontSize As Double = Convert.ToDouble(FontSizeComboBox.Text)
            If fontSize <= 1 Then
                MessageBox.Show("Please enter a font size greater than 1.", "Font size",
                    MessageBoxButton.OK, MessageBoxImage.Exclamation)
                Return
            End If

            MainTextBox.FontSize = fontSize
        Catch
            MessageBox.Show("Please enter a valid font name and size.", "Font",
                    MessageBoxButton.OK, MessageBoxImage.Exclamation)
        End Try
    End Sub

    Private Sub UpdateStatusBar()
        Dim caretIndex As Integer = MainTextBox.CaretIndex
        Dim chars As Integer = MainTextBox.Text.Length

        Dim textBeforeCaret As String = MainTextBox.Text.Substring(0, caretIndex)
        Dim lines As Integer = textBeforeCaret.Split(CChar(vbLf)).Length

        Dim lastNewLineIndex As Integer = textBeforeCaret.LastIndexOf(vbLf)
        Dim columns As Integer = If(lastNewLineIndex = -1,
            caretIndex + 1,
            caretIndex - lastNewLineIndex)

        Dim trimmedText As String = MainTextBox.Text.Trim()
        Dim words As Integer = If(String.IsNullOrEmpty(trimmedText),
            0, Regex.Matches(trimmedText, "\S+").Count)

        StatusText.Text = $"Ln {lines}  •  Col {columns}  •  {chars} characters  •  {words} words"
    End Sub

#End Region
End Class

XAML

<!-- AZUL CODING --------------------------------------- -->
<!-- WPF C#/VB - Notepad App with Spellchecker -->
<!-- https://youtu.be/7lxpGNWRvWs -->


<Window x:Class="AzulNotepad.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="Notepad - Azul Coding" Height="400" SizeToContent="Width" ResizeMode="CanMinimize">
    <DockPanel Background="White">
        <StackPanel DockPanel.Dock="Top" Margin="20,20,20,0" Orientation="Horizontal">
            <Button Name="OpenBtn" Click="OpenBtn_Click" ToolTip="Open a file" Padding="8,5" Margin="0,0,5,0" Background="#f0f0f0">
                <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
                    <Image Height="24" Width="24" Source="https://img.icons8.com/fluency/48/opened-folder.png"/>
                </StackPanel>
            </Button>
            <Button Name="SaveBtn" Click="SaveBtn_Click" ToolTip="Save a file" Padding="8,5" Margin="0,0,5,0" Background="#f0f0f0">
                <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
                    <Image Height="24" Width="24" Source="https://img.icons8.com/fluency/48/save.png"/>
                </StackPanel>
            </Button>
            <Rectangle Width="2" Margin="0,0,5,0" Fill="DodgerBlue"/>
            <Button Name="UndoBtn" Click="UndoBtn_Click" IsEnabled="False" ToolTip="Undo" Padding="8,5" Margin="0,0,5,0" Background="#f0f0f0">
                <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
                    <Image Height="24" Width="24" Source="https://img.icons8.com/fluency/48/undo.png"/>
                </StackPanel>
            </Button>
            <Button Name="RedoBtn" Click="RedoBtn_Click" IsEnabled="False" ToolTip="Redo" Padding="8,5" Margin="0,0,5,0" Background="#f0f0f0">
                <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
                    <Image Height="24" Width="24" Source="https://img.icons8.com/fluency/48/redo.png"/>
                </StackPanel>
            </Button>
            <Rectangle Width="2" Margin="0,0,5,0" Fill="DodgerBlue"/>
            <Button Name="CutBtn" Click="CutBtn_Click" ToolTip="Cut" Padding="8,5" Margin="0,0,5,0" Background="#f0f0f0">
                <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
                    <Image Height="24" Width="24" Source="https://img.icons8.com/fluency/48/cut.png"/>
                </StackPanel>
            </Button>
            <Button Name="CopyBtn" Click="CopyBtn_Click" ToolTip="Copy" Padding="8,5" Margin="0,0,5,0" Background="#f0f0f0">
                <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
                    <Image Height="24" Width="24" Source="https://img.icons8.com/fluency/48/copy.png"/>
                </StackPanel>
            </Button>
            <Button Name="PasteBtn" Click="PasteBtn_Click" ToolTip="Paste" Padding="8,5" Margin="0,0,5,0" Background="#f0f0f0">
                <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
                    <Image Height="24" Width="24" Source="https://img.icons8.com/fluency/48/paste.png"/>
                </StackPanel>
            </Button>
            <Rectangle Width="2" Margin="0,0,5,0" Fill="DodgerBlue"/>
            <ToggleButton Name="FindReplaceBtn" Click="FindReplaceBtn_Click" ToolTip="Toggle find and replace menu" Padding="8,5" Margin="0,0,5,0" Background="#f0f0f0">
                <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
                    <Image Height="24" Width="24" Source="https://img.icons8.com/fluency/48/find-and-replace.png"/>
                </StackPanel>
            </ToggleButton>
            <ToggleButton Name="SpellcheckerBtn" Click="SpellcheckerBtn_Click" ToolTip="Toggle spellchecker" Padding="8,5" Margin="0,0,5,0" Background="#f0f0f0">
                <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
                    <Image Height="24" Width="24" Source="https://img.icons8.com/fluency/48/spell-check.png"/>
                </StackPanel>
            </ToggleButton>
            <ToggleButton Name="FontBtn" Click="FontBtn_Click" ToolTip="Toggle font menu" Padding="8,5" Margin="0,0,5,0" Background="#f0f0f0">
                <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
                    <Image Height="24" Width="24" Source="https://img.icons8.com/fluency/48/choose-font.png"/>
                </StackPanel>
            </ToggleButton>
            <Button Name="DateTimeBtn" Click="DateTimeBtn_Click" ToolTip="Insert date and time" Padding="8,5" Margin="0" Background="#f0f0f0">
                <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
                    <Image Height="24" Width="24" Source="https://img.icons8.com/fluency/48/overtime--v1.png"/>
                </StackPanel>
            </Button>
        </StackPanel>
        <StackPanel x:Name="FontMenu" Visibility="Collapsed" d:Visibility="Visible" DockPanel.Dock="Top" Margin="20,20,20,-10" Orientation="Horizontal">
            <ComboBox x:Name="FontComboBox" ToolTip="Font name" Margin="0,0,5,0" Padding="5,0" Width="175" FontSize="16" IsEditable="True" VerticalContentAlignment="Center"></ComboBox>
            <ComboBox x:Name="FontSizeComboBox" ToolTip="Font size" Margin="0,0,5,0" Padding="5,0" Width="75" FontSize="16" IsEditable="True" VerticalContentAlignment="Center"></ComboBox>
            <Button Name="UpdateFontBtn" Click="UpdateFontBtn_Click" ToolTip="Update font" Padding="8,5" Margin="0" Background="#f0f0f0">
                <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
                    <Image Height="18" Width="18" Source="https://img.icons8.com/fluency/36/checkmark--v1.png"/>
                </StackPanel>
            </Button>
        </StackPanel>
        <DockPanel DockPanel.Dock="Bottom" Margin="20,0,20,20">
            <Rectangle DockPanel.Dock="Top" Height="2" Fill="DodgerBlue"/>
            <TextBlock x:Name="StatusText" Text="Ln 1  •  Col 1  •  0 characters  •  0 words" DockPanel.Dock="Bottom" Margin="0,5,0,0" FontSize="14"></TextBlock>
        </DockPanel>
        <StackPanel x:Name="FindReplaceMenu" Visibility="Collapsed" d:Visibility="Visible" DockPanel.Dock="Bottom" Margin="20,-10,20,20">
            <StackPanel Orientation="Horizontal" Margin="0,0,0,5">
                <TextBlock Text="Find:" Margin="0,0,10,0" FontSize="14" VerticalAlignment="Center" MinWidth="53"></TextBlock>
                <TextBox x:Name="FindTextBox" Margin="0,0,5,0" Padding="5,0" Width="200" FontSize="16" VerticalContentAlignment="Center"></TextBox>
                <Button x:Name="FindNextBtn" Click="FindNextBtn_Click" ToolTip="Find next" Padding="8,5" Margin="0,0,10,0" Background="#f0f0f0">
                    <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
                        <Image Height="18" Width="18" Source="https://img.icons8.com/fluency/36/search.png"/>
                    </StackPanel>
                </Button>
                <CheckBox x:Name="MatchCaseCheckBox" Content="Match case" VerticalAlignment="Center" FontSize="14" Margin="5,0,15,0" VerticalContentAlignment="Center"/>
                <CheckBox x:Name="MatchWordCheckBox" Content="Whole word" VerticalAlignment="Center" FontSize="14" VerticalContentAlignment="Center"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="Replace:" Margin="0,0,10,0" FontSize="14" VerticalAlignment="Center" MinWidth="53"></TextBlock>
                <TextBox x:Name="ReplaceTextBox" Margin="0,0,5,0" Padding="5,0" Width="200" FontSize="16" VerticalContentAlignment="Center"></TextBox>
                <Button x:Name="ReplaceBtn" Click="ReplaceBtn_Click" Padding="10,5" Margin="0,0,5,0" Background="#f0f0f0" FontSize="14" Content="Replace next occurrence"/>
                <Button x:Name="ReplaceAllBtn" Click="ReplaceAllBtn_Click" Padding="10,5" Margin="0" Background="#f0f0f0" FontSize="14" Content="Replace all"/>
            </StackPanel>
        </StackPanel>
        <TextBox x:Name="MainTextBox" TextChanged="MainTextBox_TextChanged" SelectionChanged="MainTextBox_SelectionChanged" AcceptsReturn="True" AcceptsTab="True" Margin="20" FontFamily="Segoe UI" FontSize="16" Padding="5"></TextBox>
    </DockPanel>
</Window>