Visual C++: редактор текста с отдельными окнами для файлов
Создадим приложение с двумя ссылающимися друг на друга формами, главной Form1
и дочерней Form2
, обе они снабжены файлами .cpp
. Добавить в проект форму можно через меню Проект - Добавить новый элемент - UI - Форма Windows Forms, а файл - через меню Проект - Добавить новый элемент - Код - Файл C++.
При этом формы будут ссылаться друг на друга, так как главная форма будет вызывать конструктор дочерней, и находиться в одном пространстве имён MDI_Lab2
. Число дочерних форм будет не ограничено, они сохранят в своём классе ссылку на главную, а вот ей помнить всех детишек необязательно :)
Вот как будет выглядеть изменённая часть файла Form1.h
:
namespace MDI_Lab2 { //... ref class Form1; ref class Form2; public ref class Form1 : public System::Windows::Forms::Form { public: Form1(void) { InitializeComponent(); Created = 1; } //... private: int Created; //Счётчик дочерних форм private: Form2 ^ childForm; //Ссылка на последнюю "дочку" //... #pragma endregion private: System::Void createFile (String ^name,String ^message); //... }; }
Кроме того, на форму 1 добавлено меню MenuStrip1
с четырьмя показанными на картинке пунктами:
меню главной формы
Обратите внимание, что третий пункт имеет тип toolStripTextBox
и будет служить для ввода размеров дочернего окна. На форме есть также стандартный openFileDialog1
со свойством Filter = Текстовые файлы|*.txt
У Form2.h
по отношению к автоматически сгенерированному файлу изменится вот что:
namespace MDI_Lab2 { //... ref class Form2; ref class Form1; public ref class Form2 : public System::Windows::Forms::Form { public: Form2 (Form1^ parent, String ^name) { InitializeComponent(); parentForm = parent; fileName = name; } private: Form1 ^ parentForm; //Ссылка на родителя public: String ^ fileName; //Полное имя редактируемого файла private: String ^ originalText; //Оригимнальный текст файла //... #pragma endregion public: System::Void setText( System::String ^ message); private: int Write (System::Void); //... }; }
Кроме того, на форму добавлен многострочный TextBox
с именем textBox1
, растянутый на всё окно и приспособленный для роли текстового редактора (свойства
Dock = Fill
, Multiline=True
, ScrollBars=Vertical
).
Файл Form1.cpp
будет содержать функции-кандидаты, не обрабатывающие события от Form1
. Анализ этого файла даст вам также ответы на вопросы:
- Как получить из строки
String ^
список целых положительных чисел, разделённых одним из допустимых разделителей? - Как создать дочернюю форму, "помнящую" о родительской через встроенное свойство
parentForm
? - Как передать строку (текст из файла) дочерней форме?
- Как управлять размером окна дочерней формы?
#include "StdAfx.h" #include "Form1.h" #include "Form2.h" //Здесь лежат функции-кандидаты, не обрабатывающие события от Form1 namespace MDI_Lab2 { System::Void Form1::createFile (String ^name, String ^message) { childForm = gcnew Form2 (this,name); int size[2]= {400,300}; //Размер окна по умолчанию String ^ split = " ,.:\txX"; //Допустимые разделители array <Char> ^ spliters = split->ToCharArray(); //Делаем из них массив String ^ line = toolStripTextBox1->Text; //Получаем строку для анализа array <String^> ^ words = line->Split (spliters); //Анализируем её bool ok=true; //Верный ли формат в поле ввода if (words->Length>1) for (int i=0; i<2; i++) { //Берём не более 2 слов try { int n=System::Convert::ToInt32(words[i]); //Избавляемся от нулей и отрицательных чисел: if (n==0) n=size[i]; else if (n<0) n=-n; //Избавляемся от размеров окна, которые не влезут в главный экран: System::Drawing::Rectangle scr = //Размеры главного экрана System::Windows::Forms::Screen::PrimaryScreen->WorkingArea; int screen_size[2] = { scr.Width, scr.Height }; //получим как целые числа if (n>screen_size[i]) n = screen_size[i]; size[i] = n; } catch (...) { ok=false; } } else ok=false; //ok - верный ли формат строки в поле textBox1 //В size в любом случае допустимые размеры toolStripTextBox1->Text = size[0]+"x"+size[1]; //Ставим их назад в поле ввода childForm->Size = System::Drawing::Size (size[0], size[1]); //и размер окна childForm->setText(message); //Копируем текст файла childForm->Show(); //Показываем дочернюю форму } //При желании можно было сюда поместить и обработчики событий, //только не забыть в Form1.h прописать прототипы функций, //а здесь указать перед именем функции класс Form1:: }
Остальные обработчики событий главной формы можно разместить и в Form1.h
, показано только содержимое функций:
//Пункт Создать главного меню createFile("noname"+(Created++)+".txt",""); //Пункт Открыть главного меню openFileDialog1->ShowDialog(); if (openFileDialog1->FileName == nullptr) return; try { // Создание экземпляра StreamReader для чтения из файла System::Text::Encoding^ Coding = System::Text::Encoding::GetEncoding(1251); //Кодировка русской Windows auto Reader = gcnew IO::StreamReader(openFileDialog1->FileName,Coding); String ^message = Reader->ReadToEnd(); Reader->Close(); createFile (openFileDialog1->FileName,message); } catch (IO::FileNotFoundException^ ) { return; //Чтоб не ругался на нажатие "Отмена" из диалога } catch (Exception^ e) { // Отчет о других ошибках MessageBox::Show(e->Message, "Ошибка",MessageBoxButtons::OK,MessageBoxIcon::Exclamation); } //Пункт Выход главного меню Application::Exit();
Файл Form2.cpp
будет содержать методы для установки содержимого файла в textBox1
и для записи содержимого textBox1
в файл, имя которого мы запомнили при открытии в свойстве fileName
:
#include "StdAfx.h" #include "Form2.h" //Здесь лежат функции-кандидаты, не обрабатывающие события от Form2 namespace MDI_Lab2 { System::Void Form2::setText( System::String ^ message) { textBox1->Text = message; this->originalText = textBox1->Text; textBox1->Modified = false; //После чтения текст не изменён this->Text = System::IO::Path::GetFileName(fileName); } int Form2::Write (System::Void) { try { // Создание экземпляра StreamWriter для записи в файл: System::Text::Encoding^ Coding = System::Text::Encoding::GetEncoding(1251); auto Writer = gcnew IO::StreamWriter(fileName, false, Coding); Writer->Write(textBox1->Text); Writer->Close(); textBox1->Modified = false; } catch (Exception^ e) { MessageBox::Show(e->Message, "Ошибка",MessageBoxButtons::OK,MessageBoxIcon::Exclamation); return 1; } return 0; } }
В Form2.h
также добавим не самое экономичное по ресурсам, но зато простое слежение за изменением содержимого файла (событие TextChanged
от textBox1
) и автоматическое сохранение файла дочерней формы при закрытии окна (событие FormClosing
от формы Form2
), приводится полный код функций:
private: System::Void Form2_FormClosing(System::Object^ sender, System::Windows::Forms::FormClosingEventArgs^ e) { if (textBox1->Modified == false) return; auto MBox = MessageBox::Show("Текст был изменен. \nСохранить изменения?","Простой редактор", MessageBoxButtons::YesNoCancel,MessageBoxIcon::Exclamation); if (MBox == System::Windows::Forms::DialogResult::No) return; if (MBox == System::Windows::Forms::DialogResult::Cancel) { e->Cancel = true; return; } Write(); } private: System::Void textBox1_TextChanged(System::Object^ sender, System::EventArgs^ e) { if (originalText != textBox1->Text) textBox1->Modified = true; else textBox1->Modified = false; }
Теперь проект готов к работе и его можно собирать.
Скачать архив .zip с проектом Visual C++ MDI_Lab2 (52 Кб)
Проект нужно развернуть "на месте", не создавая новых папок.
А что дальше?
В качестве примера того, как это развить (например, заставить родителя помнить всех потомков и сохранять их файлы при закрытии), допишем код таким образом:
1. Описание childForm
в файле Form1.h
заменим на:
private: static const int maxSize=100; private: static array <Form2 ^> ^childForm;
Номером окна-потомка по-прежнему будет управлять переменная Created
, только теперь в конструкторе главной формы сделаем её равной 0
(файл Form1.h
):
public: Form1(void) { InitializeComponent(); Created = 0; childForm = gcnew array <Form2 ^> (maxSize); }
Здесь же выделили память под указатели на формы потомков, которых можно будет создать не более maxSize=100
за один сеанс работы программы.
2. По пункту "Создать" увеличивать счётчик окон не будем:
createFile("noname"+(Created)+".txt","");
а сделаем это в методе createFile
из файла Form1.cpp
:
System::Void Form1::createFile (String ^name, String ^message) { if (Created<maxSize) { childForm[Created] = gcnew Form2 (this,name); //дальше как было //... childForm[Created]->Size = System::Drawing::Size (size[0], size[1]); //и размер окна childForm[Created]->setText(message); //Копируем текст файла childForm[Created]->Show(); //Показываем дочернюю форму Created++; } else MessageBox::Show("Слишком много окон", "Ошибка",MessageBoxButtons::OK,MessageBoxIcon::Exclamation); }
3. Именно в файл Form1.cpp
поместим обработчик события FormClosing
главной формы. Это нужно потому, что он будет ссылаться на методы класса Form2
. То есть, создадим обработчик как обычно, двойным щелчком по свободному месту на форме, он добавится в Form1.h
, потом оставим там только строку
private: System::Void Form1_FormClosing(System::Object^ sender, System::Windows::Forms::FormClosingEventArgs^ e);
а тело метода перенесём в Form1.cpp
:
System::Void Form1::Form1_FormClosing(System::Object^ sender, System::Windows::Forms::FormClosingEventArgs^ e) { for (int i=0; i<Created; i++) { if (childForm[i]->textBox1->Modified == false) continue; childForm[i]->Write(); } }
Для простоты при закрытии главной формы не спрашиваем пользователя, хочет ли он сохранять файлы. Наконец, метод Write
в файле Form2.h
придётся прописать как публичный:
public: int Write (System::Void);
Теперь при закрытии окна-родителя будут без лишних вопросов сохраняться все потомки.
Не хочу помнить кучу потомков, пусть потомки помнят родителя :)
В этом случае над исходным проектом можно поиздеваться ещё и так.
В Form1.h
добавлен счётчик дочерних окон:
private: int FormCount; //Счётчик форм
Его можно инициализировать по событию Load
главной формы Form1
:
private: System::Void Form1_Load(System::Object^ sender, System::EventArgs^ e) { FormCount=0; }
Создавая или открывая новое окно методом Form1::createFile
, увеличивать счетчик окон (см. Form1.cpp
):
FormCount++; childForm->Show(); //Показываем дочернюю форму
В файле Form1.h
добавить публичный метод, уменьшающий счётчик окон:
public: System::Void Form1::Dec (System::Void) { FormCount--; }
В Form2.h
добавить обработчик события "форма закрыта" (Form2_FormClosed
):
private: System::Void Form2_FormClosed(System::Object^ sender, System::Windows::Forms::FormClosedEventArgs^ e) { parentForm->Dec(); }
Чтобы это работало, не забудьте указать в начале Form2.h
#include "Form1.h"
Для Form1
по событию FormClosing
предусмотреть проверку того, все ли дочерние окна закрыты:
private: System::Void Form1_FormClosing(System::Object^ sender, System::Windows::Forms::FormClosingEventArgs^ e) { if (FormCount>0) { auto MBox = MessageBox::Show("Сначала закройте дочерние окна","Простой редактор", MessageBoxButtons::OK,MessageBoxIcon::Exclamation); e->Cancel = true; return; } }
В этой заметке есть решение, которое выглядит получше (простое MDI-приложение).
25.02.2015, 14:56 [14317 просмотров]