ПИШЕМ БЛОКНОТ НА C# - Часть 3 (Работа с текстом + Последние штрихи)

Пришло время научить наш блокнот работать с текстом, а именно: вставлять, удалять, вырезать, копировать, искать и заменять текст. Помимо этого мы научим строку состояния выводить нам актуальную информацию о количестве символов в тексте (с пробелами и без), а также о количестве строк в документе.




Для начала напишем код для элементов меню "Правка".

Отменить


notebox.Undo();

Вырезать


if (notebox.SelectionLength > 0)
{
    notebox.Cut();
}

Копировать


if (notebox.SelectionLength > 0)
{
    notebox.Copy();
}

Вставить


notebox.Paste();

Удалить


if (notebox.SelectionLength > 0)
{
    notebox.SelectedText = "";
}

Выделить всё


notebox.SelectAll();

Время и дата


notebox.AppendText(Environment.NewLine + Convert.ToString(System.DateTime.Now));

Найти и заменить


SearchForm findText = new SearchForm();
findText.Owner = this;
findText.Show();

Перейти...


GoToForm gotoform = new GoToForm();
gotoform.Owner = this;
gotoform.tbLineNum.Minimum = 0;
gotoform.tbLineNum.Maximum = notebox.Lines.Count();
gotoform.ShowDialog();

TextWork.cs

Теперь создадим класс TextWork.cs. В этот класс мы поместим методы поиска и замены для нашего текстового редактора, а также методы подсчета символов, строк и слов для строки состояния, метод для активации некоторых пунктов меню "Правка" только при наличии текста.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace NewNoteBlock
{
    public static class TextWork
    {
        // Метод поиска текста в TextBox
        // Для использования создаем в форме поиска глобальную переменную 
        // типа int = 0 для стартовой позиции поиска,
        // передаем в метод ссылки на TextBox'ы с исходным и искомым текстами,
        // а также необходимо указать, учитывать ли регистр букв при поиске (True - учитывать, False - не учитывать)
        public static int FindTextBox(ref TextBox textBox, string findText, ref int findCutLength, bool register)
        {
            // Поиск с учетом регистра
            if (register == true)
            {
                if (textBox.Text.Contains(findText))
                {
                    // Заносим текст в переменную string, удаляем из него уже пройденный 
                    // текст (findCutLength) в переменной nextText
                    string text = textBox.Text;
                    string nextText = text.Remove(0, findCutLength);
                    // Ищем в nextText
                    int resultPosition = nextText.IndexOf(findText);
                    // Если искомое выражение найдено - выделяем его, добавляем его позицию и длину 
                    // к значению пройденного текста (findCutLenght)
                    if (resultPosition != -1)
                    {
                        textBox.Select(resultPosition + findCutLength, findText.Length);
                        textBox.ScrollToCaret();
                        textBox.Focus();
                        findCutLength += findText.Length + resultPosition;
                    }
                    // Если попытка поиска не первая, и больше совпадений в тексте нет - обнуляем
                    // значение пройденного текста и начинаем поиск сначала
                    else if (resultPosition == -1 && findCutLength != 0)
                    {
                        findCutLength = 0;
                        return FindTextBox(ref textBox, findText, ref findCutLength, register);
                    }
                }
                else
                {
                    findCutLength = 0;
                    MessageBox.Show("По вашему запросу ничего не нашлось.", "Совпадений не найдено", MessageBoxButtons.OK, MessageBoxIcon.Information);
                }
            }
            // Поиск без учета регистра
            else if (register == false)
            {
                if (textBox.Text.ToLower().Contains(findText.ToLower()))
                {
                    string text = textBox.Text.ToLower();
                    string nextText = text.Remove(0, findCutLength);
                    int resultPosition = nextText.IndexOf(findText.ToLower());

                    if (resultPosition != -1)
                    {
                        textBox.Select(resultPosition + findCutLength, findText.Length);
                        textBox.ScrollToCaret();
                        textBox.Focus();
                        findCutLength += findText.Length + resultPosition;
                    }
                    else if (resultPosition == -1 && findCutLength != 0)
                    {
                        findCutLength = 0;
                        return FindTextBox(ref textBox, findText, ref findCutLength, register);
                    }
                }
                // Если текст изначально не содержит результатов поиска - обнуляем findCutLength, выводим сообщение
                else
                {
                    findCutLength = 0;
                    MessageBox.Show("По вашему запросу ничего не нашлось.", "Совпадений не найдено", MessageBoxButtons.OK, MessageBoxIcon.Information);
                }
            }
            
            return 0;
        }

        // Метод "Заменить"
        public static int ReplaceTextBox(ref TextBox textBox, string findText, string replaceText, ref int findCutLength, bool register)
        {
            if (register == true)
            {
                if (textBox.Text.Contains(findText))
                {
                    if (textBox.SelectedText == "" || textBox.SelectedText != findText)
                    {
                        string text = textBox.Text;
                        string nextText = text.Remove(0, findCutLength);
                        int resultPosition = nextText.IndexOf(findText);
                        if (resultPosition != -1)
                        {
                            textBox.Select(resultPosition + findCutLength, findText.Length);
                            textBox.ScrollToCaret();
                            textBox.Focus();
                            findCutLength += findText.Length + resultPosition;
                        }
                        else if (resultPosition == -1 && findCutLength != 0)
                        {
                            findCutLength = 0;
                            return ReplaceTextBox(ref textBox, findText, replaceText, ref findCutLength, register);
                        }
                    }
                    else if (textBox.SelectedText == findText)
                    {
                        textBox.SelectedText = replaceText;
                    }
                }
                else
                {
                    findCutLength = 0;
                    MessageBox.Show("По вашему запросу ничего не нашлось.", "Совпадений не найдено", MessageBoxButtons.OK, MessageBoxIcon.Information);
                }
            }
            else if (register == false)
            {
                if (textBox.Text.ToLower().Contains(findText.ToLower()))
                {
                    if (textBox.SelectedText == "" || textBox.SelectedText.ToLower() != findText.ToLower())
                    {
                        string text = textBox.Text.ToLower();
                        string nextText = text.Remove(0, findCutLength);
                        int resultPosition = nextText.IndexOf(findText.ToLower());
                        if (resultPosition != -1)
                        {
                            textBox.Select(resultPosition + findCutLength, findText.Length);
                            textBox.ScrollToCaret();
                            textBox.Focus();
                            findCutLength += findText.Length + resultPosition;
                        }
                        else if (resultPosition == -1 && findCutLength != 0)
                        {
                            findCutLength = 0;
                            return ReplaceTextBox(ref textBox, findText, replaceText, ref findCutLength, register);
                        }
                    }
                    else if (textBox.SelectedText.ToLower() == findText.ToLower())
                    {
                        textBox.SelectedText = replaceText;
                    }
                }
                else
                {
                    findCutLength = 0;
                    MessageBox.Show("По вашему запросу ничего не нашлось.", "Совпадений не найдено", MessageBoxButtons.OK, MessageBoxIcon.Information);
                }
            }
            return 0;
        }

        // Метод "Заменить всё"
        public static int ReplaceAllTextBox(ref TextBox textBox, string findText, string replaceText, bool register)
        {
            if (register == true)
            {
                string text = textBox.Text;
                string words = findText;
                if (textBox.Text.Contains(words))
                {
                    int startPosition = text.IndexOf(words);
                    textBox.Select(startPosition, words.Length);
                    textBox.SelectedText = replaceText;
                    return ReplaceAllTextBox(ref textBox, findText, replaceText, register);
                }
                else
                {
                    MessageBox.Show("Замены произведены успешно.",  "Заменить всё", MessageBoxButtons.OK, MessageBoxIcon.Information);
                }
            }
            else if (register == false)
            {
                string text = textBox.Text.ToLower();
                string words = findText.ToLower();
                if (text.Contains(words))
                {
                    int startPosition = text.IndexOf(words);
                    textBox.Select(startPosition, findText.Length);
                    textBox.SelectedText = replaceText;
                    return ReplaceAllTextBox(ref textBox, findText, replaceText, register);
                }
                else
                {
                    MessageBox.Show("Замены произведены успешно.", "Заменить всё", MessageBoxButtons.OK, MessageBoxIcon.Information);
                }
            }
            return 0;
        }

        public static void mEditEnableds(ref TextBox notebox, ref ToolStripMenuItem mEditCopy, ref ToolStripMenuItem mEditCut, ref ToolStripMenuItem mEditDel)
        {
            if (notebox.Text.Length < 1)
            {
                mEditCopy.Enabled = false;
                mEditCut.Enabled = false;
                mEditDel.Enabled = false;
                mEditFind.Enabled = false;
                mEditGo.Enabled = false;
            }
            else
            {
                mEditCopy.Enabled = true;
                mEditCut.Enabled = true;
                mEditDel.Enabled = true;
                mEditFind.Enabled = true;
                mEditGo.Enabled = true;
            }
        }

        public static void StatusAnalize(ref TextBox notebox, ref ToolStripStatusLabel statusLinesCount, ref ToolStripStatusLabel statusWordsCount, ref ToolStripStatusLabel statusCharSpaceCount, ref ToolStripStatusLabel statusCharCount)
        {
            string text = notebox.Text;
            // Количество строк в тексте
            statusLinesCount.Text = notebox.Lines.Count().ToString();
            // Количество слов в тексте
            statusWordsCount.Text = text.Split(new Char[] { ' ', '\t', '\n', '\r', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '-',
                '_', '+', '=', '[', '{', ']', '}', '/', '\\', '|', '"', ':', ';', '.', ',', '>', '<' }, StringSplitOptions.RemoveEmptyEntries).Length.ToString();
            // Количество символов без пробелов
            statusCharCount.Text = text.Replace(" ", "").Replace("\t", "").Replace("\n", "").Replace("\r", "").ToCharArray().Length.ToString();
            // Количество символов с пробелами
            statusCharSpaceCount.Text = text.ToCharArray().Length.ToString();
        }
    }
}

SearchForm.cs

Переходим к коду формы SearchForm.cs. В нём мы определим ту самую переменную из класса TextWork.cs, по которой метод поиска определяет с какой позиции начать выполнять операцию, а также напишем код для использования этого класса.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace NewNoteBlock
{
    public partial class SearchForm : Form
    {
        public SearchForm()
        {
            InitializeComponent();
        }

        private void SearchForm_Shown(object sender, EventArgs e) // Событие при открытии формы поиска и замены
        {
            tbFind.Focus();
        }

        int findCutLength = 0; // На сколько символов обрезаем текст для поиска

        private void tbFind_TextChanged(object sender, EventArgs e) // Cобытие при изменении текста в tbFind
        {
            findCutLength = 0;
        }

        private void tbReplace_TextChanged(object sender, EventArgs e) // Событие при изменении текста в tbReplace
        {
            findCutLength = 0;
        }

        private void cbReg_CheckStateChanged(object sender, EventArgs e) // Событие при изменении статуса cbReg
        {
            findCutLength = 0;
        }

        private void SearchForm_FormClosing(object sender, FormClosingEventArgs e) // Событие при закрытии формы (до закрытия)
        {
            findCutLength = 0;
        }

        private void butFind_Click(object sender, EventArgs e) // Кнопка "Найти"
        {
            MainForm main = this.Owner as MainForm;
            if (main != null)
            {
                if (cbReg.CheckState == CheckState.Checked)
                {
                    TextWork.FindTextBox(ref main.notebox, tbFind.Text, ref findCutLength, true);
                }
                else
                {
                    TextWork.FindTextBox(ref main.notebox, tbFind.Text, ref findCutLength, false);
                }
            }
        }

        private void butReplace_Click(object sender, EventArgs e) // Кнопка "Заменить"
        {
            MainForm main = this.Owner as MainForm;
            if (main != null)
            {
                if (cbReg.CheckState == CheckState.Checked)
                {
                    TextWork.ReplaceTextBox(ref main.notebox, tbFind.Text, tbReplace.Text, ref findCutLength, true);
                }
                else
                {
                    TextWork.ReplaceTextBox(ref main.notebox, tbFind.Text, tbReplace.Text, ref findCutLength, false);
                }
            }
        }

        private void butReplaceAll_Click(object sender, EventArgs e) // Кнопка "Заменить всё"
        {
            MainForm main = this.Owner as MainForm;
            if (main != null)
            {
                if (cbReg.CheckState == CheckState.Checked)
                {
                    TextWork.ReplaceAllTextBox(ref main.notebox, tbFind.Text, tbReplace.Text, true);
                }
                else
                {
                    TextWork.ReplaceAllTextBox(ref main.notebox, tbFind.Text, tbReplace.Text, false);
                }
            }
        }

        private void butCancel_Click(object sender, EventArgs e) // Кнопка "Отмена"
        {
            this.Close();
        }
    }
}

GoToForm.cs


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace NewNoteBlock
{
    public partial class GoToForm : Form
    {
        public GoToForm()
        {
            InitializeComponent();
        }

        private void butGo_Click(object sender, EventArgs e) // Кнопка "Перейти к строке"
        {
            MainForm main = this.Owner as MainForm;
            if (main != null)
            {
                int lineNumber = Convert.ToInt32(tbLineNum.Text);
                if (lineNumber > 0 && lineNumber <= main.notebox.Lines.Count())
                {
                    main.notebox.SelectionStart = main.notebox.GetFirstCharIndexFromLine(Convert.ToInt32(tbLineNum.Text) - 1);
                    main.notebox.ScrollToCaret();
                    this.Close();
                }
            }
        }

        private void butCancel_Click(object sender, EventArgs e) // Кнопка "Отменить"
        {
            this.Close();
        }
    }
}

Подсчет количества строк, слов, символов с пробелами и без


Метод для подсчета мы написали. Вызывать его будем через событие "notebox_TextChanged" (Событие при изменении текста в notebox) в форме MainForm.cs. Там уже есть код для изменения значения tbChange. Добавим в это событие строку:

TextWork.StatusAnalize(ref notebox, ref statusLinesCount , ref statusWordsCount, ref statusCharSpaceCount, ref statusCharCount);

Также добавим строку для активации пунктов меню "Правка" только при наличии текста:

TextWork.mEditEnableds(ref notebox, ref mEditCopy, ref mEditCut, ref mEditDel, ref mEditFind, ref mEditGo);

Меню - "Формат"

Добавим на форму MainForm.cs элемент FontDialog1 (FontDialog). Для клика по элементу меню Формат - "Шрифт" напишем код:

fontDialog.Font = notebox.Font;
DialogResult = fontDialog.ShowDialog();
if (DialogResult == DialogResult.OK)
{
    notebox.Font = fontDialog.Font;
}

Для события "CheckStateChanged" элемента меню Формат - "Перенос по словам" напишем код:

if (mFormatTransfer.CheckState == CheckState.Checked)
{
    notebox.WordWrap = true;
    notebox.ScrollBars = ScrollBars.Vertical;
    mEditGo.Enabled = false;
    statusLab1.Visible = false;
    statusLinesCount.Visible = false;
}
else
{
    notebox.WordWrap = false;
    notebox.ScrollBars = ScrollBars.Both;
    mEditGo.Enabled = true;
    statusLab1.Visible = true;
    statusLinesCount.Visible = true;
}

Видимость строки состояния

Для события "CheckStateChanged" элемента меню Вид - "Строка состояния" напишем код:

if (mViewStatusStrip.CheckState == CheckState.Checked)
{
    statusStrip.Visible = true;
}
else
{
    statusStrip.Visible = false;
}

О программе

Для клика по элементу меню Справка - "О программе" пишем:

AboutForm about = new AboutForm();
about.Show();

На этом создание текстового редактора завершено. У нас получился полноценный текстовый редактор, не просто не уступающий функционалу Блокнота Windows, а в чем-то даже превосходящий. По желанию, вы можете добавить свои иконки и изображения для придания дополнительной красоты. Полностью готовый проект текстового редактора вы можете скачать на странице проектов и статей.Спасибо за внимание!