БлогNot. Visual C++: редактор текста с отдельными окнами для файлов

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 [14129 просмотров]


теги: программирование textprocessing c++ c++/cli

К этой статье пока нет комментариев, Ваш будет первым