С# Windows Forms: помещаем однодокументное приложение в MDI-контейнер
В блоге уже есть материалы о разработке MDI-приложений на C# Windows Forms: многодокументный текстовый редактор, взаимодействие форм и MDI для C#.
Представим себе, что у нас имеется приложение Windows Forms .Net Framework и мы хотим интегрировать его в многодокументный интерфейс, добавив возможности по обмену данными между окнами и управлению ими.
В качестве примера возьмём простейший калькулятор на 4 арифметических действия (решение с одним проектом внутри, все имена по умолчанию, проект прикреплён далее).
Создадим новое приложение по тому же алгоритму, что показывает ссылка выше, пустое главное окно переименуем в MainForm из Form1 (правая кнопка мыши на имени Form1 в Обозревателе решений, ввести новое имя, после Enter подтвердить переименование всех ссылок).
С помощью окна Свойства настроим форму так, как нам нравится, но обязательно укажем IsMdiContainer = true.
Я также перетащил на форму из Панели элементов стандартное меню MenuStrip и стандартную панель инструментов ToolStrip. На последнюю пока добавим только текстовую метку Label, а командами меню, легко создаваемыми "на ходу", будем вызывать нужные кусочки кода.
Из папки простого калькулятора средствами Windows скопируем файлы с именем Form1 (их там три - Form1.cs, Form1.Designer.cs, Form1.resx) в папку нашего проекта и добавим их через Обозреватель решения в проект (правая кнопка мыши на выделенном жирным имени проекта, Добавить, Существующий элемент, выберем файл Form1.cs и нажмём кнопку Добавить).
В верхней части кода файла MainForm.cs пропишем ссылку на пространство имён нового класса:
using WindowsFormsApp1; //или другой namespace, указанный в открывающей части файла Form1.cs
Теперь мы можем программно создавать любое количество окон дочернего класса Form1, управлять ими с помощью встроенных свойств и методов многодокументного приложения, а также, добавляя в класс Form1 новые публичные методы, вызывать их для текущего дочернего окна и заставлять передавать информацию обратно от потомка к родителю.
Также никто не мешает добавлять в класс MainForm новые свойства или методы, например:
private static int winCounter; //Номер окна калькулятора private string Buffer; //Буферная строка для обмена данными между калькуляторами
Покажем основные возможности на примерах.
По выбору пункта меню Файл - Создать создадим и покажем очередное дочернее окно, не забыв указать ему, кто папа. :)
Form1 newForm = new Form1 (); newForm.MdiParent = this; //Указали новой форме, кто её родитель newForm.Text = "Калькулятор " + ++winCounter; //Номер нового окна - в заголовок //Также при необходимости ставим любые другие свойства новой формы newForm.Show ();
Кстати, я бы в свойствах дочернего окна Form1 поставил свойство StartPosition в значение WindowsDefaultLocation, чтобы система сама решала, куда пристроить очередное окно.
По выбору пункта Файл - Выбрать выведем в метку на панели инструментов сообщение о том, какое из дочерних окон является активным:
Form activeChild = this.ActiveMdiChild;
if (activeChild != null) {
toolStripLabel1.Text = "Выбрано: " + activeChild.Text;
}
else {
toolStripLabel1.Text = "Ничего не выбрано";
}
Чтобы всё было корректно при "ручном" выборе активного дочернего окна, имеет смысл обработать этим же кодом событие MdiChildActivate окна главной формы (выберите для него в окне свойств ту же функцию выбратьToolStripMenuItem_Click в качестве обработчика указанного события).
По выбору пункта Файл - Выход покажем, как обойти и закрыть дочерние окна, их сворачивание перед этим делается просто для примера:
private void closeChildren() {
while (this.MdiChildren.Length > 0) { //Пока остались потомки
Form child = this.MdiChildren[this.MdiChildren.Length - 1]; //Взять последнего
child.WindowState = FormWindowState.Minimized; //Свернуть
Application.DoEvents(); //Обработать накопившиеся события
child.Close(); //Закрыть
}
}
private void выходToolStripMenuItem_Click(object sender, EventArgs e) {
closeChildren();
this.Close(); //Последним закрыть родителя
}
Отдельный метод closeChildren нужен потому, что подобный же код с закрытием дочерних окон (без завершающего оператора this.Close ();, а то вылетит на повторном закрытии) следует выполнять по событию FormClosing главной формы (т.к. закрытие главного окна "крестиком" не гарантирует корректного закрытия всех окон потомков):
private void MainForm_FormClosing(object sender, FormClosingEventArgs e) {
closeChildren();
}
Пункт меню Окно - Каскад займётся укладкой дочерних окон:
LayoutMdi(MdiLayout.Cascade);
И Окно - Мозаика тоже:
LayoutMdi(MdiLayout.TileHorizontal);
Чтобы заработал обмен данными между текстовыми полями активных калькуляторов, мы будем получать ActiveMdiChild как объект класса именно Form1, а не Form и добавим в класс Form1 соответствующие публичные методы, например:
public string Cut() {
string str = textBox1.Text.Trim();
textBox1.Clear();
return str;
}
public void Paste (string str) {
textBox1.Text = str;
}
Теперь можно, например, через пункт родительского меню Правка - Вырезать сохранять нужную информацию потомка (в нашем случае - содержимое текстового поля) в свойствах родителя:
Form1 activeChild = ActiveMdiChild as Form1;
if (activeChild != null) {
Buffer = activeChild.Cut();
}
а через пункт Правка - Вставить возвращать строку в текущее окно:
Form1 activeChild = ActiveMdiChild as Form1;
if (activeChild != null && Buffer.Length > 0) {
activeChild.Paste(Buffer);
}
Это примерно всё, другие идеи легко реализовать по аналогии.
Скачать этот проект Windows Forms C# в архиве .zip, развернуть в новую папку. (17 Кб)
25.02.2026, 15:07 [193 просмотра]

