Mdi многодокументный интерфейс


Mdi многодокументный интерфейс

(перевод одноимённой статьи с delphi.about.com)

Что такое MDI?
MDI расшифровывается как multiple document interface (многодокументный интерфейс). В приложениях с MDI, в основном (родительском) окне можно окрыть более одного дочернего окна. Данная возможность обычно используется в электронных таблицах или текстовых редакторах.

Каждое MDI приложение имеет три основные составляющие:
Одну (и только одну) родительскую форму MDI, Одну и более (обычно больше) дочерних форм MDI, и основное меню MDI.

MDI «мать»
Как уже упоминалось, в проекте MDI приложения может присутствовать только один MDI контейнер (родительская форма) и он должен быть стартовой формой.
Для создания основного окна MDI приложения проделайте следующие шаги:

  1. Запустите Delphi и выберите File | New Application. Delphi создаст новый проект с одной формой под названием form1 (по умолчанию).
  2. В свойстве Name присвойте форме имя frMain.
  3. Установите свойство FormStyle в fsMDIform.
  4. Сохраните этот проект (имя проекта на Ваше усмотрение, например prMDIExample), вместе с uMain.pas в только что созданной директории.

Как Вы успели заметить, для создания основной формы MDI, мы установили свойство FormStyle в fsMDIform. В каждом приложении только одна форма может иметь свойство fsMDIform.

MDI «дети»
Каждое родительское окно MDI нуждается по крайней мере в одной дочерней форме. Дочерние формы MDI — это простые формы, за исключением того, что их видимая часть ограничена размерами родительского окна. Так же при минимизации такого окна, оно помещается не в панель задач, а остаётся внутри родительского окна ( на панель задач попадёт только родительское окно).

Теперь давайте создадим дополнительные формы, а точнее дочерние. Просто выберите File | New Form. Будет создан новый объект формы с именем form1 (по умолчанию). При помощи Object Inspector измените свойство Name в форме form1 на frChild, а свойство FormStyle на fsMDIChild. Сохраните эту форму с соответствующим ей файлом как uchild.pas. Обратите внимание, что при помощи данного свойства мы можем превратить любую существующую форму в дочернюю форму MDI.
Ваше приложение может включать множество дочерних MDI форм такого же или другого типа.

Так же хочется обратить Ваше внимание, что MDI приложение может включать в себя и самые обычные формы, но в отличие от дочерних, они будут отображаться как обычные модальные диалоговые окна (такие как about box, или файловый диалог).

Естевственно, что как на родительском так и на дочернем окнах можно располагать любые элементы управления, однако уже давно сложилась традиция, что на родительской форме располагается панель статуса (status bar) и панель инструментов (toolbar), в то время как на дочерних формах располагаются все остальные контролы, такие как гриды, картинки, поля вводи и т. д.

Автосодание -> Доступные
Теперь давайте произведём некоторые настройки нашего проекта. Выберите Project | Options, откроется диалог опций проекта (Project Options). В левой панели выберите frChild (Авто-создание форм («Auto-create forms»)), и переместите её в правую панель (Доступные формы (Available forms)). Список правой панели содержит те формы, которые используются Вашим приложением, но которые не созданы автоматически. В MDI приложении, по умолчанию, все дочерние формы создаются автоматически и отображаются в родительской форме.

Создание и отображение.
Как упомянуто выше, настройка не позволяет автоматически создавать дочерние окна, поэтому нам необходимо добавить некоторый код, который будет производить создание объекта формы frChild. Следующую функцию CreateChildForm необходимо поместить внутри основной формы (MDI родитель) (наряду с заголовком в interface’s private):

Данный код создаёт одну дочернюю форму с заголовком childName.
Не забудьте, что этот код находится разделе «uses uchild».

На закрытие не минимизировать!
Закрытие дочернего окна в MDI приложении всего навсего минимизирует его в клиентской области родительского окна. Поэтому мы должны обеспечить процедуру OnClose, и установить параметр Action в caFree:

Обратите внимание, что если форма является дочерней формой MDI, и её свойство BorderIcons установлено в biMinimize (по умолчанию), то опять же по умолчанию параметр Action установлен в caMinimize. Если же в дочерней форме MDI нет этих установок, то по умолчанию Action установлен как caNone, означающий, что при закрытии формы ничего не случится.

MDI родительское меню
Каждое MDI приложение должно иметь основное меню с (если больше ничего нет), опцией выравнивания окон. Поскольку мы предварительно переместили дочернюю форму из Авто-создаваемых (Auto-create) в Доступные (Available) формы, то нам нужен будет код, который (пункт меню) будет создавать дочерние формы.

Итак, переместите компонент TMainMenu на frMain (MDI родитель) и приведите его к следующему виду:

Для создания дочерних окон в нашем приложении будет использоваться пункт меню «New child». Второе меню (Window) будет использоваться для выравнивания дочерних окошек внутри родительского окна-формы.

. Создать и отобразить
В заключении нам необходимо сделать обработчик для пункта меню «New child». При нажатии на пунк меню File | New Child нашего приложения, будет вызываться процедура NewChild1Click которая в свою очередь будет вызывать процедуру CreateChildForm (приведённую выше), для создания (следующего) экземпляра формы frChild.

Только что созданная дочерняя форма будет иметь заголовок в виде «Child x», где x представляет количество дочерних форм внутри MDI формы, как описано ниже.

Закрыть всё
При работе с приложением, имеющим многодокументный интерфейс, всегда необходимо иметь процедуру, закрывающую все дочерние окна.

Вам прийдётся выполнять проверку на предмет наличия несохранённой информации в каждом дочернем окне. Для решения данной задачи лучше всего использовать обработчик события OnCloseQuery.

Свойства MdiChildCount и MDIChildren
MdiChildCount свойство read only, содержащее в себе количество созданных дочерних окошек. Если не создано ни одно дочернее окно, то это свойство установлено в 0. Нам прийдётся частенько использовать MdiChildCount наряду с массивом MDIChildren. Массив MDIChildren содержит ссылки на объекты TForm всех дочерних окошек.

Обратите внимание, что MDIChildCount первого созданного дочернего окна равен 1.

Меню Window
Delphi обеспечивает большинство команд, которые можно поместить внутри пункта меню Window. Далее приведён пример вызова трёх основных методов для команд, которые мы поместили в наше приложение:

Mdi многодокументный интерфейс

Многодокументный интерфейс (MDI) позволяет создавать приложение, которое поддерживает несколько форм в пределах одной контейнерной формы. Приложения типа Microsoft Excel и Microsoft Word для Windows имеют многодокументные среды.

Приложение MDI позволяет пользователю отображать несколько документов в одно и то же время — каждый документ отображается в своем собственном окне. Документы или дочерние окна содержатся в родительском окне, которое обеспечивает рабочее пространство для всех дочерних окон в приложении. Например, Microsoft Excel позволяет Вам создавать и отображать окна множественных документов различных типов. Каждое индивидуальное окно ограничено областью окна родителя Excel. Когда вы минимизируете Excel, все окна документов также свернуты, а в панели задач появляется значок родительского окна.

Дочерняя форма — обычная форма, которая устанавливает свое свойство MDIChild в True. Ваше приложение может включать много дочерних форм MDI одинаковых или различных типов.

Во время выполнения дочерние формы отображаются в пределах рабочего пространства формы родителя MDI (область в границах формы ниже заголовка и строк меню). Когда дочерняя форма свернута, ее значок появляется в пределах рабочего пространства формы MDI вместо панели задач, как показано на рис. 6.4.

Рис. 6.4 Дочерние формы, отображенные в пределах формы MDI

Примечание Ваше приложение может также включать стандартные не-MDI формы, которые не содержатся в форме MDI. Типичное использование стандартной формы в приложении MDI — отображение модального окна диалога.

Форма MDI подобна обычной форме с одним ограничением. Вы не можете размещать элемент управления непосредственно в форму MDI, если этот элемент управления не имеет свойства Align (типа элемента управления picture box) или не имеет никакого видимого интерфейса (типа элемента управления timer).

Создание приложения MDI

Чтобы создать форму MDI и ее дочерние формы, используйте следующую процедуру.

Чтобы создать приложение MDI

    Создайте форму MDI.

В меню Project выберите Add MDI Form.

Примечание Приложение может иметь только одну форму MDI. Если проект уже имеет форму MDI, команда Add MDI Form в меню Project недоступна

Создайте дочерние формы приложения.

Чтобы создать дочернюю форму MDI, создайте новую форму (или откройте существующую) и установите ее свойство MDIChild в True.

Чтобы узнать больше о приложениях MDI, см. следующие темы:

  • Работа с дочерними формами MDI во время разработки О поведении дочерних форм во время разработки.
  • Особенности форм MDI во время выполнения О поведении дочерних форм во время выполнения.
  • Приложение MDI NotePad Подробности приложения-примера MDI Notepad.
  • Работа с формами MDI и дочерними формами О взаимодействии родительских и дочерних форм MDI.

6.6. Многодокументный интерфейс.

Приложения, которые могут работать с несколькими документами, открываемыми в отдельных окнах и расположенных внутри главного окна, называют MDI-приложениями (MDI — от англ. Multiple Document Interface). В Qt подобный интерфейс создается с помощью класса QWorkspace, назначаемого центральным виджетом. Каждое окно с открытым документом становится подчиненным, по отношению к QWorkspace.

В этом разделе мы создадим приложение Editor (текстовый редактор), изображенное на рисунке 6.14, чтобы продемонстрировать принципы создания MDI-приложений и оконных меню.

Рисунок 6.14. Внешний вид приложения Editor.

Рисунок 6.15. Меню приложения Editor.


Начнем с класса MainWindow. В конструкторе создается экземпляр класса QWorkspace и назначается центральным виджетом. Затем мы соединяем сигнал windowActivated(), класса QWorkspace, с двумя приватными слотами. Эти слоты гарантируют, что меню и строка состояния всегда будут соответствовать текущему активному окну. Слот newFile() соответствует пункту меню File|New. Он создает новое окно (класса Editor) с документом, вызывая приватную функцию createEditor(). Функция createEditor() создает виджет класса Editor и устанавливает два соединения типа сигнал-слот. Первое соответствует пунктам меню Edit|Cut и Edit|Copy. Доступность этих пунктов меню разрешается или запрещается, в зависимости от наличия выделенного текста. Второе соединение отвечает за обновление индикатора MOD (признак наличия в документе несохраненных изменений), который находится в строке состояния.

Поскольку мы имеем дело с многодокументным интерфейсом, то вполне возможно, что одновременно могут оказаться открытыми несколько окон с документами. Вас может обеспокоить этот факт, поскольку интерес для нас представляют сигналы copyAvailable(bool) и modificationChanged(), исходящие только от активного окна. На самом деле это не может служить причиной для беспокойства, поскольку сигналы могут подавать только активные окна.

Функция open() соответствует пункту меню File|Open. Она создает новое окно Editor и вызывает метод Editor::open(). Если функция Editor::open() завершается с ошибкой, то окно редактора просто закрывается, поскольку пользователь уже был извещен о возникших проблемах. Слот save() вызывает функцию save() активного окна. Опять таки, весь код, который фактически сохраняет файл, находится в классе Editor. Приватная функция activeEditor() возвращает указатель на активное окно редактора. Слот cut() вызывает функцию cut() активного окна. Слоты copy(), paste() и del() реализованы аналогичным образом. Слот updateMenus() вызывается всякий раз, когда активизируется другое окно (или когда закрывается последнее окно с документом), с целью обновления системы меню. Большинство из пунктов меню имеют смысл только при наличии активного дочернего окна, поэтому мы запрещаем некоторые пункты меню, если нет ни одного окна с открытым документом. Затем очищается меню Windows и вызывается функция createWindowsMenu(), которая обновляет список открытых дочерних окон. Приватная функция createWindowsMenu() заполняет меню Windows действиями (action) и дополняет списком открытых окон. Перечень пунктов типичен для меню подобного рода и соответствующие им действия легко реализуются с помощью слотов QWorkspace — closeActiveWindow(), closeAllWindows(), tile() и cascade().

Активное окно, в списке, отмечается маркером, напротив имени документа. Когда пользователь выбирает пункт меню, соответствующий открытому документу, вызывается слот activateWindow(), которому в качестве аргумента передается индекс в массиве windows. Это очень похоже на то, что мы делали в Главе 3, когда создавали список недавно открывавшихся документов.

Для первых девяти пунктов меню мы добавили символ амперсанда, перед порядковым номером пункта меню, чтобы можно было быстро перемещаться между открытыми документами, с помощью горячих клавиш.

Функция activateWindow() вызывается, когда пользователь выбирает какое либо окно с документом, из меню Windows. Параметр param — это индекс выбранного окна, в массиве windows. Слот copyAvailable() вызывается, когда выделяется какой либо текст (или наоборот, когда выделение снимается) в окне редактора. Он так же вызывается из updateMenus(). И разрешает или запрещает пункты меню Cut и Copy. Функция updateModIndicator() обновляет индикатор MOD в строке состояния. Вызывается при любом изменении текста в окне редактора, а так же при активации другого окна. Функция closeEvent() закрывает все дочерние окна. Если какое либо из окон «проигнорирует» событие «close» (например в том случае, когда пользователь отменил закрытие окна, имевшее несохраненные данные), то это событие так же игнорируется и главным окном приложения MainWindow. В противном случае событие «принимается» и Qt закрывает окно. Если не перекрыть этот обработчик, то у пользователя не будет возможности записать на диск несохраненные данные.

На этом мы завершаем обзор класса MainWindow и переходим к реализации класса Editor. Этот класс представляет собой одно дочернее окно. Он порожден от класса QTextEdit, который реализует всю необходимую функциональность по редактированию текста. Так же, как и любой другой виджет Qt, QTextEdit может использоваться как дочернее окно в рабочей области MDI.

Ниже приводится определение класса:

Четыре приватных функции, которые обсуждались нами при создании приложения Spreadsheet, аналогичным образом реализованы и в классе Editor. Это функции maybeSave(), saveFile(), setCurrentFile() и strippedName(). В конструкторе, с помощью функции setWFlags(), взводится флаг WDestructiveClose. Если конструктор класса не принимает флаги в качестве аргументов, как это имеет место быть в случае с QTextEdit, то мы можем установить флаги вызовом setWFlags().

Так как мы позволяем пользователям одновременно открывать несколько документов, необходимо предусмотреть какие либо характеристики окон, чтобы потом пользователи могли как-то их отличать между собой, до того, как вновь создаваемые документы будут сохранены. Самый распространенный способ — присваивать документам имена по-умолчанию, которые включают в себя порядковый номер (например, document1.txt). Для этой цели мы используем переменную isUntitled, которая отличает имена документов, уже существующих, и имена документов, которым имя еще не было присвоено пользователем.

После вызова конструктора должна вызываться одна из двух функций — либо newFile(), либо open().

Функция newFile() генерирует новое имя документа, например document2.txt. Этот код помещен в newFile(), а не в конструктор, потому что нет необходимости вести счетчик создаваемых документов для тех из них, которые после конструирования объекта будут открываться функцией open(). Поскольку переменная documentNumber объявлена как статическая, то она существует в единственном экземпляре, для всех объектов класса Editor. Функция open() пытается открыть существующий файл, с помощью вызова openFile(). Функция save() использует переменную isUntitled, чтобы определить — какую функцию вызывать: saveFile() или saveAs(). За счет перекрытия родительского метода closeEvent() мы даем пользователю возможность сохранить имеющиеся изменения. Логика сохранения реализована в функции maybeSave(), которая выводит запрос перед пользователем: «Желаете ли вы сохранить имеющиеся изменения?». Если она возвращает true, то событие «close» принимается, в противном случае оно игнорируется и окно останется открытым. Функция setCurrentFile() вызывается из openFile() и saveFile(), чтобы изменить содержимое переменных curFile и isUntitled, обновить заголовок окна и сбросить признак «modified». Класс Editor наследует методы setModified() и isModified() от своего предка — QTextEdit, поэтому у нас нет необходимости «тащить» свой признак модификации документа. Когда пользователь вносит какие либо изменения в документ, QTextEdit выдает сигнал modificationChanged() и устанавливает признак модификации. Функция sizeHint() возвращает «идеальные» размеры виджета, основываясь на размере символа ‘x’. Класс QWorkspace использует эти размеры, чтобы назначить начальные размеры для окна с документом.

И в заключение приведем исходный текст файла main.cpp:

Если пользователь задаст имена документов в командной строке, то приложение попытается загрузить их. В противном случае приложение создает пустой документ. Специфические ключи командной строки, такие как -style и -font, будут автоматически исключены из списка аргументов, конструктором QApplication. Так что, если мы дадим такую команду: То приложение на запуске откроет один единственный документ readme.txt.

Многодокументный интерфейс — один из способов одновременной работы с несколькими документами. Другой способ состоит в том, чтобы использовать несколько окон верхнего уровня. Он был описан в разделе Работа с несколькими документами одновременно Главы 3.

Mdi многодокументный интерфейс

На этом шаге мы рассмотрим создание MDI -приложений.

Термин MDI (Multiple Document Interface) дословно означает многодокументный интерфейс и описывает приложения, способные загрузить и использовать одновременно несколько документов или объектов. Примером такого приложения может служить диспетчер файлов (File Manager).

Обычно MDI -приложения состоят минимум из двух форм — родительской и дочерней . Свойство родительской формы FormStyle установлено равным fsMDIForm . Стиль дочерней формы — fsMDIChild .

Родительская форма служит контейнером, содержащим дочерние формы, которые заключены в клиентскую область и могут перемещаться, изменять размеры, минимизироваться или максимизироваться. В приложении могут быть дочерние формы разных типов, например одна — для обработки изображений, а другая — для работы с текстом.

В MDI-приложении, как правило, требуется выводить несколько экземпляров классов формы. Поскольку каждая форма представляет собой объект, она должна быть создана перед использованием и освобождена, когда в ней больше не нуждаются. Delphi может делать это автоматически, а может предоставить эту работу программисту.

Автоматическое создание форм

По умолчанию при запуске приложения Delphi автоматически создает по одному экземпляру каждого класса форм в проекте и освобождает их при завершении программы. Автоматическое создание обрабатывается генерируемым Delphi кодом в трех местах.

Первое — раздел интерфейса в файле модуля формы.

Вторым является место, в котором описывается переменная класса:

Здесь описана переменная Form1 , указывающая на экземпляр класса TForm1 и доступная из любого модуля. Обычно она используется во время работы программы для управления формой.

Третье место находится в исходном тексте проекта, доступ к которому можно получить с помощью меню View | Project Source . Этот код выглядит как:

Процесс удаления форм обрабатывается с помощью концепции владельцев объектов: когда объект уничтожается, автоматически уничтожаются все объекты, которыми он владеет. Созданная описанным образом форма принадлежит объекту Application и уничтожается при закрытии приложения.

Динамическое создание форм

Хотя автоматическое создание форм полезно при разработке однодокументных приложений, при создании MDI -приложении оно, как правило, неприемлемо.

Для создания нового экземпляра формы используйте конструктор Create класса формы. Приведенный ниже код создает новый экземпляр TForm1 во время работы программы и устанавливает его свойство Caption равным New Form :

Конструктор Create получает в качестве параметра-потомка объект типа TComponent , который будет владельцем формы. Обычно в качестве владельца выступает Application . Это делается для того, чтобы все формы были автоматически закрыты по окончанию работы приложения. Можно также в качестве параметра использовать значение Nil , создав, тем самым, форму без владельца. Однако в этом случае закрывать и уничтожать ее должен программист. В случае возникновения необрабатываемой ошибки такая форма останется в памяти.

Примечание . При разработке MDI -приложения метод Show не нужен, так как Delphi автоматически показывает все вновь созданные дочерние MDI -формы. В случае однодокументного приложения использование метода Show обязательно.

Даже при динамическом создании форм Delphi попытается «помочь» в создании экземпляра каждой формы. Чтобы отказаться от них, нужно в диалоговом окне Project Options (вызывается при выполнении пункта меню Project | Options ) удалить классы форм из списка Auto-create forms :

Илон Маск рекомендует:  Три дня до конца мини-конкурса

Рис.1. Окно Project | Options

Если требуется получить доступ к отдельному дочернему экземпляру класса, используйте свойство MDIChildren , описываемое в следующем разделе.

MDI-свойства TForm

Объект TForm имеет несколько свойств, специфичных для MDI -приложений. Перечислим их.


    property ActiveMDIChild: TForm; . Это свойство возвращает дочерний объект TForm , имеющий в текущее время фокус ввода. Оно полезно, когда родительская форма содержит панель инструментов или меню, команды которых распространяются на открытую дочернюю форму. Например, представим, что проект использует дочернюю форму, содержащую элемент ТМеmо , названный memDailyNotes . Имя класса этой дочерней формы — TfrmMDIChild . Родительская форма содержит кнопку Clear в панели инструментов, которая удаляет содержимое memDailyNotes в активной дочерней форме. Вот как это реализуется:

В первой строке проверяется, равен ли ActiveMDIChild значению Nil , так как в этом случае обращение к объекту вызовет исключительную ситуацию.

Примечание . Свойство ActiveMDIChild равно Nil в том случае, если нет открытых дочерних форм или свойство FormStyle не равно fsMDIForm .

Поскольку ActiveMDIChild возвращает объект TForm , компилятор не имеет доступа к memDailyNotes — объекту TfrmMDIChild . Вторая строка проверят соответствие типов, то есть действительно ли свойство ActiveMDIChild указывает на объект TfrmMDIChild . Третья строка выполняет преобразование типа и вызывает метод Clear компонента memDailyNotes .

property MDIChildren[I: Integer]: TForm; property MDIChildCount: Integer; . Свойство MDIChildren является массивом объектов TForm , предоставляющих доступ к созданным дочерним формам. MDIChildCount возвращает количество элементов в массиве MDIChildren .

Обычно это свойство используется при выполнении какого-либо действия над всеми открытыми дочерними формами. Вот код сворачивания всех дочерних форм при щелчке на кнопке с именем Button1 :

Если сворачивать окна в порядке возрастания элементов массива, цикл будет работать некорректно, так как после сворачивания каждого окна массив MDIChildren обновляется и пересортировывается, и в этом случае можно пропустить некоторые элементы.

  • property TileMode: TTileMode; TTileMode = (tbHorizontal, tbVertical); . Это свойство перечислимого типа, определяющее, как родительская форма размещает дочерние при вызове метода Tile . Используются значения tbHorizontal (по умолчанию) и tbVertical для размещения форм по горизонтали и вертикали.
  • property WindowMenu: TMenuItem; . Профессиональные MDI -приложения позволяют активизировать необходимое дочернее окно, выбрав его из списка в меню. Свойство WindowMenu определяет объект TMenuItem , который Delphi будет использовать для вывода списка доступных дочерних форм.

    Для вывода списка TMenuItem должно быть меню верхнего уровня. Это меню имеет свойство Caption , равное sWindow .


    MDI-события и MDI-методы TForm

    В MDI -приложении событие OnActivate запускается только при переключении между дочерними формами. Если фокус ввода передается из не MDI -формы в MDI -форму, генерируется событие OnActivate родительской формы, хотя ее свойство Active никогда и не устанавливается равным True . Эта странность на самом деле строго логична: ведь, если бы OnActivate генерировался только для дочерних форм, не было бы никакой возможности узнать о переходе фокуса ввода от другого приложения.

    Специфичные для MDI -форм методы перечислены ниже:

    • procedure ArrangeIcons; — выстраивает пиктограммы минимизированных дочерних форм в нижней части родительской формы;
    • procedure Cascade — располагает дочерние формы каскадом, так что видны все их заголовки;
    • procedure Next; procedure Previous; — переходит от одной дочерней формы к другой, как-будто нажаты клавиши Ctrl+Tab или Ctrl+Shift+Tab ;
    • procedure Tile; — выстраивает дочерние формы так, что они не перекрываются.

    Пример MDI-приложения

    Создадим приложение, позволяющее просматривать графические изображения на отдельной форме. Для этого в новом проекте у формы зададим следующие свойства:

    • Caption: Image Viewer;
    • FormStyle: fsMDIForm;
    • Name: frmMDIParent;
    • ShowHint: True.

    Поместим компонент TPanel в форму. Установим у него следующие свойства:

    • Align: alTop;
    • Caption: (пусто).

    Поместим компонент TSpeedButton в TPanel и зададим у него следующие свойства:

    • Name: spbtnLoad;
    • Hint: Load;
    • Left: 8;
    • Top: 8.

    Подберем рисунок для созданной кнопки.

    Добавим в форму компонент TOpenDialog и установим следующие его свойства:

    • Filter: Bitmaps (*.bmp)|*.bmp;
    • Name: opndlgLoad;
    • Options: [ofPathMustExist,ofFileMustExist].

    Теперь создадим дочернюю форму. Выполним пункт меню File | New | Form и появится пустая форма. Установим следующие ее свойства:

    • FormStyle: fsMDIChild;
    • Name: frmMDIChild;
    • Position: poDefaultPosOnly.

    Поместим компонент TImage во вновь созданную форму и установим его следующие свойства:

    • Align: alClient;
    • Name: imgMain.

    Удалим дочернюю форму из списка автоматически создаваемых форм следующим образом:

    • выполним пункт меню Project | Options , и появится диалоговое окно Project Options , показанное на рисунке 1;
    • выберем форму frmMDIChild в списке Auto-create forms ;
    • щелкнем на кнопке «>» . Форма frmMDIChild при этом будет перенесена в список Available forms .

    Создав интерфейс, перейдем к написанию исходного текста приложения.

    Сначала загрузим изображение. Следующий код будет размещаться в обработчике события OnClick компонента spbtnLoad :

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

    Поскольку модуль ссылается на тип TfrmMDIChild , находящийся в модуле MDIChild , то после строки implementation следует добавить еще одну строку:

    Теперь можно приступить к компиляции и запуску приложения. Однако по щелчку на кнопке Close дочерняя форма не закрывается, а сворачивается в пиктограмму. Чтобы заставить ее закрыться, следует в код обработчика OnClose класса TfrmMDIChild внести изменение свойства Action :

    Внешний вид работающего приложения изображен на рисунке 2.

    Рис.2. Внешний вид приложения

    Текст этого примера можно взять здесь.

    Со следующего шага мы начнем знакомиться с графической системой Delphi .

    Работа с многодокументными (MDI) формами

    Другая разновидность форм — многодокументные, или MDI-формы. Интерфейс MDI позволяет открыть сразу несколько окон внутри родительского окна-контейнера. Скажем, в главном окне типичного текстового редактора (классический пример — Word for Windows) можно открыть несколько документов (рис. 2.7). Главное окно выполняет функции контейнера, а дочерние формы находятся внутри него. MDI-приложения появились в те времена, когда на рынке преобладали ранние версии Windows. Многодокументные приложения позволяли одновременно открывать несколько файлов, не запуская нескольких копий одной программы. Это экономило не только время, но и память.

    Рис. 2.7. Несколько документов в родительском окне-контейнере

    Интерфейс MDI обычно используется для документно-ориентированных приложений (например, текстовых или графических редакторов). Программа называется документно-ориентированной, если она предназначена для работы с документами. Если пользователи приложения должны одновременно работать с несколькими формами, выбирайте модель MDI. Visual Basic позволяет очень просто создавать MDI-приложения.

    Создание MDI-приложения

    Для создания MDI-приложения необходимо иметь как минимум две формы — родительскую (контейнер) и дочернюю (внутреннюю). Чтобы работать с дочерними формами различных типов, необходимо включить в проект дополнительные формы. Тем не менее для простейшего MDI-проекта одной формы вполне достаточно. Проект создается так:

    1. Создайте новый проект командой File >• New Project. Если у вас включен Project Wizard, выберите тип проекта Standard EXE.

    2. В созданном проекте изначально присутствует одна форма. Задайте ее свойству Name значение frmChild, а свойству Caption — Дочерняя форма MDI.

    3. Чтобы создать родительскую форму MDI, щелкните правой кнопкой мыши на папке Forms в окне проекта и выполните команду Add > MDI Form. Когда на экране появится окно Form Wizard, выберите в нем MDI Form.

    4. Задайте свойству Name значение frmMDI, а свойству Caption — значение Родительская форма MDI

    5. Щелкните правой кнопкой мыши на строке Project1 в окне проекта и выберите из контекстного меню команду Project1 Properties. Выберите из списка Startup Object строку frmMDI. Если не сделать этого, в начале работы вашего приложения будет отображаться дочерняя форма.

    6. Выберите в окне проекта строку frmChild. Задайте свойству MDI Child значение True — теперь дочерняя форма будет находиться внутри родительской.

    7. Выберите в окне проекта строку frmMDI.

    8. Запустите редактор меню командой Tools > Menu Editor. Появляется окно, изображенное на рис. 2.8.

    Рис. 2.8. Редактор меню

    В своем примере мы ограничимся очень простым меню. Не беспокойтесь о технических подробностях работы с редактором меню — они будут рассмотрены в уроке 5, «Создание и использование меню и панелей инструментов».

    1. Введите &File в поле Caption.

    2. Введите mnuFile в поле Name.

    3. Нажмите кнопку Next.

    4. Нажмите кнопку со стрелкой вправо — у команды меню появляется отступ.

    5. Введите &New Form в поле Name.

    6. Введите mnuFileNew в поле Name.

    7. Закройте редактор меню кнопкой ОК.


    8. В форме frmMDI должно появиться меню File. Выполните команду File > New, и на экране появится окно программы.

    9. Вставьте в процедуру события mnuFileNew_Click() следующий фрагмент:

    Dim frm As New frmChild

    10. Сохраните и запустите проект. На рисунке показан примерный вид окна MDI-ириложения.

    Добавленный нами фрагмент создает новую копию формы frmChild и отображает ее. Это происходит при каждом выполнении команды File > New. Попробуйте открыть и закрыть несколько дочерних окон; как видите, мы построили вполне рабочее MDI-приложение.

    Новая версия MDI-приложения

    Чтобы наша программа действительно была похожа на коммерческие MDI-приложения Windows, необходимо добавить несколько дополнительных штрихов. Например, все дочерние формы имеют одинаковые названия, так что их невоз-

    можно отличить друг от друга; этот недостаток нужно исправить. Также хотелось бы иметь возможность упорядочить дочерние окна и выстроить их «мозаикой», или «каскадом». Наконец, в меню должен присутствовать список окон, при помощи которого можно легко перейти к любому дочернему окну, даже если оно спрятано под другими окнами.

    1. Запустите редактор меню и добавьте в строку меню родительского окна, frmMDI, новое меню &Window. Установите флажок WindowList в редакторе меню.

    2 Добавьте в новое меню команды Tile и Cascade. Назовите их mnuWindowTile и mnuWindowCascade соответственно.

    3. Закройте редактор меню кнопкой ОК.

    4. Добавьте в процедуру события Click объекта mnuWindowTile следующую строку:

    5. Добавьте в процедуру события Click объекта mnuWindowCascade следующую строку:

    vbCascade и vbTileHorizontal — встроенные константы Visual Basic. Их значение описано в справочной системе.

    6. Измените код процедуры для команды mnuFileNew:

    Private Sub mnuFileNew_Click()

    Static Counter As Integer

    Dim frm As New frmChild

    Counter = Counter + 1

    frm.Caption = «Дочерняя форма MDI» & Counter

    7. Сохраните и запустите приложение. Обратите внимание на отличия рис. 2.9 от предыдущего рисунка.

    Не нашли то, что искали? Воспользуйтесь поиском:

    Лучшие изречения: Для студентов недели бывают четные, нечетные и зачетные. 9438 — | 7438 — или читать все.

    188.64.174.135 © studopedia.ru Не является автором материалов, которые размещены. Но предоставляет возможность бесплатного использования. Есть нарушение авторского права? Напишите нам | Обратная связь.

    Отключите adBlock!
    и обновите страницу (F5)

    очень нужно

    Урок 5

    Многооконная структура программы
    Перетаскивание файлов в приложение

    Часть урока для новичков

    Приложения MDI и приложения SDI.

    Прежде чем устанавливать вид и свойства вашего приложения надо подумать о том, какой вид приложения вы желаете создать. Это во многом зависит от его функционального назначения. Можно выбирать между так называемыми MDI- или SDI-приложениями.

    MDI — сокращенно от Multiple Document Interface (интерфейс для одновременной работы со многими документами), а SDI — от Single Document Interface (интерфейс для работы с одним документом). В MDI приложениях два или более окон могут быть активны одновременно. В SDI-приложениях это невозможно. Здесь в каждый момент времени может быть активным только одно окно.

    MDI -приложения являются удобным средством для одновременного выведения на экран текста или данных, которые хранятся в различных файлах. Такую структуру построения окон можно использовать для редактирования текстов, открывая и выводя на экран одновременно несколько различных документов. С помощь этих приложений можно также производить табличные вычисления, обрабатывая несколько таблиц одновременно, перенося или сравнивая данные из одной в другую. Пример такой работы над файлами — программа MS Word. Здесь файлы текстового и графического формата открываются в отдельных окнах, находящихся внутри главного окна программы.

    Различные открываемые документы в окнах имеют общее рабочее пространство, в пределах которого они отображаются, называемое родительским окном. Всегда в MDI-приложении родительское окно является главной формой приложения. Все внутренние формы, называемые дочерними окнами отображаются в пределах рабочего пространства родительского окна и не могут быть помещены за ее пределы. Даже при свертывании. При максимизации таких окон, они занимают все рабочее пространство родительского окна, оставаясь внутри его.

    Родительское окно может быть в MDI-приложениях только одно, а дочерних окон может быть теоретически бесконечно.

    Большинство MDI-приложений имеют меню » Window » («Окно») со следующими пунктами: Cascade, Arrange Icons, Tile Horizontal, Tile Vertical . С их помощью пользователь может управлять дочерними окнами приложения. Обычно нижняя часть меню отделена и представляет список всех открытых окон. Это свойство весьма удобно для быстрого перехода к тому или иному редактируемому файлу.

    Все SDI-окна можно разделить по свойствам доступа друг к другу как модальные и немодальные. Они определяют, может пользователь или нет переключаться на другие окна. Когда модальное окно открыто, все другие открытые окна становятся недоступными. Пример можно увидеть практически из любой программы, вызвав пункт меню «About» («О программе»). Отображаемое окно в приложении, как правило, не дает переключится на другое, пока не будет закрыто. Такое свойство иногда бывает очень полезным. Например, когда нужно чтобы пользователь ввел пароль, а затем получил доступ к определенному окну с данными, или окно фильтра данных, указав условия отбора, получает доступ к результату.

    Итак, модальными или немодальными могут быть только SDI-окна.

    С сегодняшнего урока мы с вами начинаем разрабатывать довольно сложный пример простого многооконного текстового редактора.

    Пример MDI-приложения.


      Запускаем Delphi. В меню «File» выбираем пункт «New Application».

  • Переименовываем свойство имени формы Name из Form1 в MainForm.
  • Устанавливаем свойство в инспекторе объектов FormStyle в fsMDIForm. Его можно выбрать из ниспадающего списка доступных свойств. Свойство означает, что данное окно будет родительским в MDI-приложении.

    В палитре компонентов выбираем MainMenu . Это второй компонент слева на вкладке Standart. Помещаем его на проектировщик формы Form1. Дважды щелкаем по нему. Появляется конструктор меню окна. Здесь и далее для того, чтобы вызвать конструктор для размещенного в форме компонента главного меню (MainMenu), необходимо или дважды кликнуть по нему мышкой или дважды кликнуть в Object Inspector по свойству Items. Далее, пользуясь свойством Caption задайте меню такой вид, как на рисунке. Примечание: выбрать изменяемый пункт можно мышкой, для изменения названия выберите в инспекторе объектов свойство Caption, для отделения секций меню линией используйте знак тире в свойстве Caption для нужного пункта меню. Создание меню очень простое. Вы сразу видите результат изменения, добавления или удаления пунктов в редакторе формы. Вы всегда можете пополнить пункты меню, используя дополнения, выделенные пунктирной рамкой. Всегда можно вставить или удалить пункт меню в нужное место. Для этого воспользуйтесь правой клавишей мыши. Выбрать интересующее действие можно из выпадающего меню.

    Итак, содержание главного меню:

    ФайлПравкаОкнаПомощь
    НовыйВырезатьУпорядочитьО программе
    ОткрытьКопировать
    СохранитьВставить
    Сохранить как:
    Закрыть
    Выход


    Выбираем пункт меню «Окна» и переименовываем его имя (идентификатор) Name в инспекторе объектов в WindowMenu.

    Выбираем мышкой форму MainForm. В Object Inspector в свойстве WindowMenu устанавливаем их ниспадающего списка пункт меню WindowMenu . Теперь во время работы приложения все дочерние окна будут списком отображаться в меню » Окна «.

    Поскольку дочерних окон в этом приложении будет много и по внешнему виду они будут подобные, то нет смысла проектировать каждое окно отдельно. Спроектируем одно. Для помещения в проект новой формы выбираем из меню » File » пункт » New Form «.


    Устанавливаем размеры окна Form2 меньшими главного родительского окна.

    Переименовываем свойство Name для этого окна из Form2 в ChildForm.

    Устанавливаем свойство дочернего окна для MDI-приложения FormStyle в fsMDIChild .

    Выбираем из палитры компонентов Memo и устанавливаем его в дочернюю форму ChildForm .

    Меняем свойство Align для этого компонента на alClient . Он немедленно расширяется на все доступное пространство окна.

    Далее будем писать событие выбора пункта меню «Новый». При нажатии на него должно появляться дочернее окно. Поступаем следующим образом. Поскольку дочерних окон будет множество, то мы программа должна их автоматически при необходимости создавать. По умолчанию Delphi устанавливает автоматическое создание окна во время запуска приложения. Это избавляет программиста от написания некоторых команд. В данном случае мы все сделаем сами. Выбираем из меню » Project » пункт » Options: «. На вкладке Forms снимаем установку » Auto-create forms » для окна ChildForm . Для этого выбираем его и перегоняем одинарной стрелкой в сторону » Available forms «. Нажимаем кнопку OK.

    Сохраняем проект на диске. Для этого проводником Windows создаем отдельную папку для хранения этого примера. В меню » File » выбираем пункт » Save Al l». Дальше появляются диалоги сохранения модулей окон и файл проекта. Модуль Unit1 родительского окна можно назвать, сохранив его под именем MainUnit . Модуль Unit2 дочернего окна приложения. можно сохранить под именем ChildUnit . Весь проект можно сохранить под именем MyEdit .

  • Теперь напишем в редакторе кода процедуру создания дочернего окна. Выбираем окно MainForm и дважды щелкаем по компоненту MainMenu1 . Выбираем пункт меню » Новый «. В Object Inspector переходим на вкладку событий Events . Дважды щелкаем по полю, правее OnClick . Или нажимаем Ctrl+Enter. Появляется готовый заголовок процедуры нажатия на пункт меню «Новый». Между begin и end пишем следующую строку:
  • Поскольку в этом окне создается другое, то в переименованный модуль MainUnit необходимо подключить ChildUnit . Для этого выбираем из меню » File » пункт «Use Unit: » и указываем модуль ChildUnit . Нажимаем OK.

    Все, запускаем программу на выполнение. Во время работы обратите внимание на список появившихся окон в меню «Окна», по которым удобно переключаться на необходимое окно.

    В следующих уроках мы подробнее остановимся на написании этого примера. Если в этом уроке у вас что-то не получается, то пример на данном этапе разработки можно скачать здесь (2 KB). Если вы в последствии открываете свой или скаченный проект, не забывайте, что нужно открывать DPR файл.

    Часть урока для продвинутых программистов

    Перетаскивание файлов в приложение

    Иногда очень полезно избавить пользователя от лишних операций при открытии файла. Он должен нажать на кнопку «Открыть», затем найти интересующий каталог, выбрать файл. Проще перетащить мышкой файл сразу в окно приложения. Рассмотрим пример перетаскивания Drag & Drop в окно произвольного текстового файла, который сразу же открывается в компоненте Memo1.

    Для начала в разделе Uses необходимо подключить модуль ShellAPI .

    В private области окна нужно вставить следующую строку:

    procedure WMDropFiles(var Msg: TWMDropFiles); message WM_DROPFILES; //получение сообщений о переносе файла в окно приложения

    Процедура обработки этого сообщения будет выглядеть следующим образом:

    procedure TForm1.WMDropFiles(var Msg: TWMDropFiles);
    var
    CFileName: array[0..MAX_PATH] of Char; // переменная, хранящая имя файла
    begin
    try
    If DragQueryFile(Msg.Drop, 0, CFileName, MAX_PATH)>0 then // получение пути файла
    begin
    Form1.Caption:=CFileName; // имя файла в заголовок окна
    Memo1.Lines.LoadFromFile(CFileName); // открываем файл
    Msg.Result := 0;
    end;
    finally
    DragFinish(Msg.Drop); // отпустить файл
    end;
    end;

    Для того, чтобы форма знала, что может принимать такие файлы, необходимо в процедуре создания окна указать:

    procedure TForm1.FormCreate(Sender: TObject);
    begin
    DragAcceptFiles(Handle, True);
    end;

    С уважением, ведущий уроков Semen

    лабы по информатике, егэ

    лабораторные работы и задачи по программированию и информатике, егэ по информатике

    VB 22. Разработка приложения с многодокументным интерфейсом (MDI-приложения)

    Выполнение:

    Многодокументный интерфейс Windows (MDI) позволяет программам работать с множеством форм, расположенных в одной родительской форме. Применение MDI сделает интерфейс программы более аккуратным, поскольку находящиеся в нем формы не будут разбросаны по всему экрану.

    Стандарт MDI позволяет совершенствовать программы в двух направлениях. Во-первых, Вы сможете ограничиться одной формой-контейнером, которая станет рабочим фоном приложения. Если пользователь переместит контейнер, вместе с ним переместятся и все дочерние формы, что сделает интерфейс программы организованным и самодостаточным. Во-вторых, что еще важнее, пользователь может одновременно работать с несколькими документами. MDI-приложения позволяют использовать несколько экземпляров одной и той же формы, что увеличивает производительность и гибкость ваших программ.

    1. Рассмотрим на примере, как работает MDI-интерфейс, усовершенствовав наш графический редактор.
    2. Загрузите в окно редактора проект приложения из занятия № 16«Обработка событий мыши». Основной форме проекта дайте имя frmPicture. Она в дальнейшем будет выступать в роли макета дочерней формы.
    3. Добавьте в проект родительскую MDI-форму с помощью команды менюProject →AddWindowsForm →MDIParentForm (Проект → Добавить формуWindows → Родительская формаMDI). На панели свойств задайте ей имя mdiGeneral. Установите значение свойства Text родительской формы равным Графический редактор Художник 1.0.
    4. Теперь настроим дочернюю форму frmPicture. Установите значение свойства Text для нее равным Рисунок.

    Каждое загруженное дочернее окно занимает некоторый объем оперативной памяти. Поэтому, если предполагается интенсивно использовать в программе несколько экземпляров дочерних окон, необходимо свести в них к минимуму объем программного кода и количество элементов управления. В противном случае, приложение может резко замедлить работу компьютера из-за нехватки оперативной памяти.

    Родительская MDI-форма является контейнером, в котором располагаются все дочерние окна. Поэтому именно она ответственна за создание дочерних окон. Кроме того, в родительской форме обычно хранятся совместно используемые элементы интерфейса, например, такие как панель инструментов, строка состояния и т.д.

    1. Обратите внимание, что родительская форма имеет собственное меню. Перейдите на меню родительской формы и измените процедуру для пункта меню File →New (Файл → Создать), добавив вместо кода Windows.Forms.Form нашу форму frmPicture:

    Private Sub ShowNewForm(ByVal sender As Object, ByVal e As EventArgs) Handles NewToolStripMenuItem.Click,NewToolStripButton.Click, NewWindowToolStripMenuItem.Click ‘ Создать новый экземпляр дочерней формы. Dim ChildForm As New frmPicture ‘ Сделать ее дочерней для данной формы MDI перед отображением. ChildForm.MdiParent = Me m_ChildFormNumber += 1 ChildForm.Text = «Рисунок » & m_ChildFormNumber ChildForm.Show() End Sub

    Давайте разберемся в приведенном листинге. Вначале, в операторе объявления Dim указано имя объектной переменной (переменной типа Object), в которой будет содержаться экземпляр формы; в объявлении Dim используется ключевое слово New. Затем формируется строка заголовка создаваемой формы (свойство .Text). После того, как создана объектная переменная для вывода формы на экран, используется метод Show() для отображения формы. Однако вместо имени формы ему задается имя объектной переменной.


    1. Установите в качестве формы для запуска родительскую MDI-форму (меню Проект → Свойства:WindowsApplication…) и запустите проект. При выборе меню File →New(Файл → Создать) запускается дочерняя форма, но меню на форме не активно.
    2. Перейдите на форму frmPicture и перенесите полностью меню с дочерней формы на родительскую форму (вырезав элемент меню из дизайна дочерней формы и вставив на родительскую). Точно также вырежьте из кода программы тот фрагмент кода дочерней формы, который относится к пунктам меню, и вставьте его в код родительской формы. В результате на дочерней форме не должно остаться ни одного элемента управления.
    3. Добавьте в проект стандартный модуль и из кода дочерней формы вставьте туда описание переменных, изменив Private на Public:

    Public КнопкаНажата As Boolean Public ИнструментКвадрат As Boolean Public ИнструментЛиния As Boolean

    1. Запустите проект . Сначала выберие меню File →New (Файл → Создать), а затем Инструмент. Нарисуйте что-нибудь на форме. А затем еще раз выберите меню File →New (Файл → Создать) – появился новый экземпляр формы!
    2. Скорее всего, Вы обратили внимание на два неудобства. Первое: при запуске новой формы никакой инструмент не активен. Добавьте в обработку процедуры пункта меню New(Создать) (ShowNewForm) следующий фрагмент:
    1. Кроме того, если Вы хотите, чтобы при запуске программы появлялось окно для нового рисунка, то добавьте в модуль родительской формы следующий код:

    Private Sub MDIGeneral_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load ShowNewForm(sender, e) End Sub

    1. Сразу же можете поменять меню с англоязычного на русский язык, изменив подписи меню в дизайне формы.
    2. Добавим код в обработку события меню Open(Открыть). Открывать будем файл-изображение (расширение bmp), загружая его в качестве фона активной дочерней формы:

    Private Sub OpenFile(ByVal sender As Object, ByVal e As EventArgs) Handles OpenToolStripMenuItem.Click, OpenToolStripButton.Click Dim OpenFileDialog As New OpenFileDialog OpenFileDialog.InitialDirectory = My.Computer.FileSystem.SpecialDirectories.MyDocuments OpenFileDialog.Filter = «Text Files (*.bmp)|*.bmp|All Files (*.*)|*.*» If (OpenFileDialog.ShowDialog(Me) = System.Windows.Forms.DialogResult.OK) Then Dim FileName As String = OpenFileDialog.FileName ‘ TODO: Add code here to open the file. Dim ChildForm As New frmPicture Me.ActiveMdiChild.BackgroundImage = Image.FromFile(FileName) End If End Sub

    1. Посмотрите на то, как ведут себя остальные пункты. Можете их удалить или попробовать адаптировать под программу.
    2. Сохраните проект и запустите программу. Создайте пару новых дочерних окон и посмотрите, как ведут себя дочерние формы внутри родительской. Чтобы лучше понять особенности их поведения выполните следующие действия:
    • Минимизируйте дочернюю форму и обратите внимание на расположение ее пиктограммы.
    • Переместите дочернюю форму. Она не выйдет за границы окна родительской формы.
    • Максимизируйте дочернюю форму.
    • Минимизируйте и максимизируйте родительскую форму.

    Наконец, немного полезной информации. При работе с MDI-приложениями широко используются два особенных ключевых слова – Me и ActiveMdiChild. Ключевое слово Ме используется в любой форме для ссылки на нее саму. В свойстве родительской формы ActiveMdiChild находится указатель на активную в текущий момент дочернюю форму.

    1. Для чего предназначен MDI-интерфейс? В чем его преимущество перед обычным интерфейсом?
    2. Что представляет собой MDI-форма?
    3. Что представляет собой дочерняя форма?
    4. Для чего используется ключевое слово Me?

    Глава 13. Создание многодокументных приложений

    Создание многодокументных приложений

    Ну вот мы и подошли к следующей важной теме. Я надеюсь, что нет необходимости подробно представлять многодокументный интерфейс ( MDI , Multiple Document Interface) , который позволяет в одном приложении работать с несколькими окнами. MDI описан в руководстве по разработке интерфейса пользователя System Application Architecture /Common User Access/ Advanced Interface Design Guide (SAA/CUA) , созданном фирмой IBM. В системе Windows интерфейс MDI используется, начиная с версии 3.0, и его типичным представителем является хорошо всем знакомый текстовый процессор Microsoft Word.

    Как и все приложения Windows, MDI-приложение имеет одно главное окно, которое управляет своими окнами «MDI child».

    Для названия окна документа я буду придерживаться терминологии фирмы Microsoft, которая для дочерних окон MDI-приложений использует названия «MDI child frame», «MDI child window» или просто «MDI child».

    Поведение окон » MDI child» напоминает поведение как дочерних, так и перекрывающихся окон. Так же, как и первые, их невозможно переместить за пределы главного окна приложения. В то же время окна «MDI child» можно сделать активными, при этом их заголовок выделяется цветом. Кроме того, каждое окно «MDI child» имеет, как правило, системное меню и кнопки изменения размера. Если свернуть такое окно, то его пиктограмма расположится в нижней части главного окна приложения. При увеличении размеров окна до максимальных пиктограмма системного меню окна » MDI child» помещается в левую часть основного меню, а в правой части располагаются кнопки изменения размера и закрытия окна.

    Одним словом, можно создать аналогичное приложение с использованием обычных окон, но для этого придется затратить немало усилий. Достаточно серьезной работой будет и создание полноценного MDI-приложения с помощью функций библиотеки SDK (Software Development Kit). Чтобы в этом убедиться, достаточно посмотреть исходные тексты приложения MULTIPAD , поставляемые в составе примеров вместе с системой разработки Microsoft Developer Studio Visual C++. Совсем другая картина предстанет перед вами, когда вы возьмете исходные тексты такого же приложения, но созданного с использованием библиотеки MFC. Мы попытаемся обойти эти две крайние позиции и рассмотрим интерфейс MDI одновременно с деталями его внутренних механизмов, знание которых позволит вам создавать приложения любой сложности наиболее эффективно и с наименьшими затратами на поиск ошибок, которые зачастую возникают из-за непонимания происходящего.

    Начнем с рассмотрения двух основных классов библиотеки MFC, разработанных специально для создания многодокументных приложений, — CMD1-FrameWnd к CMDlChildWnd

    CMDIFrameWnd — это базовый класс для главного окна любого многодокументного приложения (рис. 13.1). Если быть более точными, то в данном случае следует говорить не об окне MDICLIENT , которое управляет рабочей областью фрейма и является, собственно, родительским для окон «MDI child».

    Рис. 13.1. Место класса CMDIFrameWnd в иерархии библиотеки MFC

    Рис. 13.2. Типы окон интерфейса MDI

    Уточним некоторые моменты, связанные с процессом создания MDI-окна. Как обычно, сначала создается объект нашего оконного класса. Если вы сравните тексты функции Initlnstance для создания главных окон SDI- и MDI-приложений, то не увидите никакой разницы. И в том и в другом случае вызываются одни и те же функции, в частности OnCreateClient . Однако и это скрыто в функциях библиотеки, если при создании SDI-окна вызывается функция класса CFrameWnd , то при создании MDI-окна вызывается одноименная функция класса CMDIFrameWnd . В первом случае она отвечает за создание (если необходимо) присоединяемого к окну дочернего окна, а во втором — за создание специального окна MDICLIENT , которое, как мы уже говорили, полностью управляет рабочей «областью главного окна MDI-приложения и отвечает за работу с окнами » MDI child» . Все перечисленные окна показаны на рис. 13.2.

    Разница, безусловно, есть, но явно она заметна только в случае построения приложения на основе архитектуры «документ/представление». В других же случаях она скрыта в недрах библиотеки.

    Прежде чем переходить к примерам, давайте рассмотрим функциональные возможности, которые класс CMDIFrameWnd предоставляет в наше распоряжение.

    void CMDIFrameWnd:: MDIActivate (CWnd *pWndActivate)

    Параметр pWndActivate — указатель на окно «MDI child», которое должно быть активизировано.

    Эта функция посылает сообщение WM_MDIACTIVATE в два окна: сначала в то, которое активизируется, затем в то, которое теряет активность. Такое же сообщение посылается, если пользователь изменил фокус дочернего окна при помощи мыши или клавиатуры. Когда активизируется основное окно, то дочернее, которое было активно последним, посылает сообщение WM_NCACTIVATE , чтобы нарисовать его рамку и заголовок, но при этом не получает сообщения WM_MDIACTIVATE .

    CMDIChildWnd* CMDIFrameWnd:: MDIGetActive ( BOOL *pbMaximized =.NULL)

    Возвращает указатель на текущее активное окно «MDI child». Дополнительно в параметре pbMaximized возвращается информация о том, развернуто это окно или нет.


    void CMDIFrameWnd:: MDIIconArrange ()

    Упорядочивает все свернутые окна «MDI child», не затрагивая те, которые не свернуты.

    void CMDIFrameWnd:: MDMaximize (CWnd *pWnd)

    Разворачивает окно «MDI child». При этом Windows изменяет его размеры таким образом, чтобы оно заполнило всю рабочую область окна фрейма. Windows также размещает системное меню дочернего окна в полосе основного меню, а его заголовок добавляется к заголовку окна фрейма.

    Если активное окно развернуто, и в этот момент активизируется другое окно «MDI-child», то Windows его также разворачивает.

    Активизирует следующее окно «MDI child».

    void CMDIFrameWnd:: MDIRestore (CWnd *pWnd)

    Восстанавливает исходные размеры окна «MDI child», заданного параметром pWnd, если оно было развернуто или свернуто.

    Располагают все окна «MDI child» без перекрытия. Первая версия функции располагает окна по вертикали, а вторая в качестве параметра nТуре может принимать одно из следующих значений:

    Расположить окна по горизонтали

    Расположить окна по вертикали

    Предотвращает отображение блокированных окон «MDI child»

    void CMDIFrameWnd: rMDICascade ()

    void CMDIFrameWnd:: MDICascade(int nType)

    Располагают все окна «MDI child» каскадом. Первая версия функции отображает и блокированные окна, а вторая, которая может быть вызвана только с параметром MDITILE_SKIPDISABLED, — все, кроме блокированных.

    virtual HMENU CMDIFrameWnd:: GetWindowMenuPopup (HMENU hMenuBar)

    Позволяет получить дескриптор раскрывающегося меню для элемента меню Window (Окно) основного меню, заданного параметром hMenuBar. Реализация, заданная по умолчанию, ищет раскрывающееся меню, которое содержит стандартные команды элемента меню Window (Окно) основного меню типа ID_WINDOW_NEW и ID_WINDOW_TILE_HORZ. Если раскрывающееся меню не содержит идентификаторов стандартных команд, то эту функцию следует переопределить.

    CMenu* CMDIFrameWnd:: MDISetMenu (

    Заменяет соответствующие текущие меню на переданные в качестве параметров, если pFrameMenu и/или pWindowMenu не равны NULL. Возвращает временный указатель на замененное меню. После вызова этой функции следует вызвать функцию CWnd::DrawMenuBar для обновления полосы меню. Если функция изменяет раскрывающееся меню, то элементы меню окна «MDI child» удаляются из предыдущего меню окна и добавляются в раскрывающееся меню. Если для управления окнами «MDI child» вы используете фрейм, то эту функцию вызывать не следует.

    Как видите, всю основную работу по поддержанию работы MDI-прило-жений выполняет система Windows совместно с библиотекой MFC.

    Прежде чем двигаться дальше, давайте также внимательно рассмотрим класс, который отвечает за функционирование окон «MDI child».

    Как вы уже поняли, основным назначением этого класса является обеспечение работоспособности дочерних окон MDI-приложений — окон «MDI child», которые, говоря несколько упрощенно, позаимствовали свои свойства как у дочерних, так и у перекрывающихся окон (рис. 13.3).

    Рис. 13.3. Место класса CMDIChildWnd в иерархии библиотеки MFC

    Большую часть возможностей этот класс наследует от своего базового — CFrameWnd , но имеет и свойства, присущие только ему. К ним можно отнести автоматическое отображение меню окна «MDI child» вместо основного меню MDI-приложения, а также добавление заголовка дочернего окна к заголовку его родителя.

    Для того чтобы произошла автоматическая замена основного меню на меню окна «MDI child», в классе определена единственная переменная

    Содержит дескриптор меню, ассоциированного с окном «MDI child».

    Кроме того, в классе определены несколько функций:

    DWORD dwStyle = WS_CHILD|WS_VISIBLE|WS_OVERLAPPEDWINDOW,

    const RECT Srect = rectDefault,

    CMDIFrameWnd *pParentWnd = NULL,

    CCreateContext *pContext = NULL)

    Эта функция абсолютно идентична функции CFrameWnd::Create, однако задачи, которые они решают, достаточно сильно различаются, и нельзя подменять вызов одной из них вызовом другой. Активное текущее окно «MDI child» может определять заголовок своего родительского окна, если для него установить бит стиля FWS_ADDTOTITLE. Описываемая функция вызывается MDI-окном в ответ на команду пользователя создать дочернее окно. При этом параметр pContexi позволяет правильно связать дочернее окно с приложением. Если такой необходимости нет, то для него можно использовать значение по умолчанию (NULL).

    void CMDIChildWnd:: MDIDestroy ()

    Удаляет заголовок окна «MDI child» из MDI-окна и разрушает само окно «MDI child».

    void CMDIChildWnd:: MDIActivate ()

    Активизирует текущее окно «MDI child». Когда MDI-окно становится активным, то активизируется и окно «MDI child», которое находилось в этом состоянии последним.

    void CMDIChildWnd:: MDIMaximize ()

    Разворачивает текущее окно «MDI child». Если у него не сброшен бит стиля FWS_ADDTOTITLE, то его заголовок добавляется в MDI-окно.

    void CMDIChildWnd:: MDIRestore ()

    Восстанавливает исходные размеры текущего окна «MDI child», если оно было развернуто или свернуто.

    CMDIFrameWnd* CMDIChildWnd:: GetMDIFrame ()

    Позволяет получить указатель на родительское MDI-окно объекта класса CMDIFrameWnd. Для того чтобы получить указатель на родительское окно типа MDICLIENT, управляющее объектом класса CMDIChildWnd, необходимо вызвать функцию CWnd::GetParent.

    Теперь, когда мы познакомились с обоими классами CMDIFrameWnd и CMDIChildWnd , позволяющими создавать приложения на базе интерфейса MDI, пришло время рассмотреть конкретный пример, который поможет закрепить полученные сведения и познакомиться с дополнительными возможностями библиотеки MFC.

    В этом разделе мы рассмотрим пример (рис. 13.4), на котором я продемонстрирую основные принципы построения MDI-приложений. В дальнейшем он может послужить основой для разработки многодокументных приложений любой степени сложности.

    Но прежде всего, как и в любом другом случае, необходимо создать проект. Для наших целей воспользуемся мастером MyWizard4, который реализует для нас каркас приложения. Начнем с класса-приложения:

    Рис. 13.4. Главное и дочерние окна MDI-приложения

    // Заголовок будем брать из ресурсов

    // Ищем копию нашего приложения

    CWnd *pWnd = CWnd::FindWindow(NULL, csCaption);

    // Если таковая есть, то .


    // раскрываем его, если оно было свернуто

    // или выводим на передний план

    // Создаем объект главного MDI-фрейма

    CMainFrame* pMainFrame = new CMainFrame;

    Первое, что сразу бросается в глаза при рассмотрении приведенного фрагмента — это практически полная идентичность интерфейса и реализации нашего класса «приложение» ( CMDIApp ) и класса CFirstApp , описанного выше (см. главу 3). А если не считать появления специального обработчика, который мы рассмотрим дальше в этой главе, и вызова функции CWnd::FindWindow, то совпадение будет полным. На самом деле это совсем не случайно. Достаточно большая часть программ для Windows создается с использованием метода «клея и ножниц», т. е. взяв за основу некоторый шаблон (а такие заготовки имеются у всех, даже начинающих, программистов), вы выполняете несколько маленьких изменений и компонуете составные части так, как этого требует конкретная задача.

    Поиск запущенного экземпляра приложения

    Рассмотрим фрагмент кода:

    CWnd *pWnd = CWnd::FindWindow(NULL, csCaption);

    He будем пока акцентировать внимание на новом для нас классе CString , а ограничимся замечанием, что в объекте этого класса будет храниться символьная строка с именем окна, загруженная из файла ресурсов.

    Как создать такой ресурс, вы к этому моменту должны уже хорошо представлять, поэтому ограничусь только его «внешним видом» (рис. 13.5).

    Но вернемся к фрагменту кода. Мы вставили его для того, чтобы не загружать наше приложение еще раз, если оно уже было запущено. Действительно, MDI-приложения позволяют работать с несколькими документами одновременно (в отличие от SDI-приложений), и нет никакой необходимости расходовать дополнительную память. В отличие от ранних версий Windows, для проверки того, не было ли приложение уже загружено, мы не можем воспользоваться параметром hInstance , передаваемом в командной строке, поскольку для приложений Win32 он всегда равен NULL . Поэтому мы воспользовались функцией CWnd:: Find Window, которая позволяет найти окно верхнего уровня по имени оконного класса или по заголовку окна.

    Функцию FlndWindow нельзя использовать для поиска дочерних окон.

    Рис. 13.5. Для поиска экземпляра приложения нам понадобится строковый ресурс

    Через параметр IpszClassName передается указатель на символьную строку, определяющую имя класса искомого окна. Если этот параметр равен NULL, как в нашем случае, то поиск будет осуществляться во всех классах окон. Поскольку нам надо найти вполне определенное окно с заданным заголовком, то имя заголовка мы передаем через второй параметр IpszWindowName, который в общем случае также может быть равен NULL. Если окно найдено, то функция FindWindow вернет временный указатель на этот объект класса CWnd, в противном случае — NULL.

    Если назначение имени класса мы отдаем библиотеке MFC, то параметром IpszClassName пользоваться просто невозможно, т. к. имя класса генерируется каждый раз новое и имеет примерно такой вид: «Агх:400000:8:1526:0:13F7» (при следующем запуске, даже второй копии приложения, класс будет иметь имя «Afx:400000:8:1526:0:251 F» — почувствуйте разницу!).

    После того как главное окно найдено, нужно проверить, не было ли оно свернуто. Для этого используется специальная функция

    Возвращает TRUE, если окно свернуто.

    В этом случае восстанавливаем его нормальный размер с помощью уже известной вам функции CWnd::ShowWindow.

    Теперь осталось только поместить окно на передний план и активизировать. Это легко сделать при помощи функции, которая предназначена именно для подобных целей:

    BOOL CWnd:: SetForegroundWindow ()

    Переводит поток, который создал окно, в приоритетный режим и активизирует его главное окно.

    Ее дополняет функция

    Возвращает указатель на временный объект приоритетного окна (окна, с которым пользователь в текущий момент работает).

    Для полноты картины приведем еще одну функцию (не использованную в нашем приложении), которая позволяет изменить Z-порядок окон:

    Позволяет вывести на передний план окна верхнего уровня, всплывающие или окна «MDI child». По своему действию похожа на функцию CWnd::SetWindowpPos, однако при этом не разрешает изменять стили окна.

    Возвращаясь к нашему примеру, отметим, что если работающая копия приложения обнаружена, то нам остается только завершить работу текущего экземпляра. Если же работающая копия не найдена, то переопределенная функция Initlnstance приступает к инициализации приложения. Этот процесс уже был подробно описан ранее, и мы не будем к нему возвращаться.

    Займемся теперь используемыми классами окон. Прежде всего обратим внимание на базовый класс нашего главного окна:

    class CMainFrame : public CMDIFrameWnd

    Как видите, он образован из класса, специально спроектированного для окон MDI. При этом действия по созданию главного окна MDI-прило-жения осуществляются точно такие же, как и при создании любого другого окна: создание объекта «окно», регистрация оконного класса и создание окна Windows. Чтобы не повторяться, коснемся только одного момента. Строгих правил, определяющих место, где следует регистрировать оконный класс, нет. Как правило, большие классы регистрируют в отдельном модуле, а маленькие — в той же самой функции, что и основное окно. Однако это дело вкуса, и вы вправе разрабатывать или перенимать любой стиль, который вас устраивает. Поскольку мы собираемся работать со многими окнами «MDI child» одновременно, и в данном случае все они принадлежат одному оконному классу, то в качестве места регистрации их оконного класса мы выбрали обработчик сообщения WM_CREATE главного окна приложения:

    int CMainFrame::OnCreate(LPCREATESTRUCT IpCreateStruct)<

    if (CMDIFrameWnd::OnCreate(IpCreateStruct) == -1)

    AfxGetApp (> -XLoadlcon (IDI_MDITEXT) ) ;

    Обратите внимание на то, что нам не надо явно создавать специальное окно MDICLIENT , т. к. оно автоматически формируется функциями библиотеки. Вы, конечно, можете вмешаться в этот процесс, переопределив функцию класса CMDIFrameWnd— CreateClient . Однако еще раз подчеркну— чтобы не нарушать правильную работу составных частей вашего приложения, не забывайте в таких случаях вызывать переопределенную функцию базового класса.

    В этом месте необходимо сделать небольшое отступление и рассмотреть вопросы, связанные с меню многодокументного интерфейса.

    Изменение полосы меню

    Здесь для демонстрации всех команд меню мы рассмотрим не рисунок с внешним видом меню, а фрагменты из файла ресурсов, которые получаются после того, как мы создали меню рассмотренным выше способом.

    Использование графического редактора ресурсов для создания меню значительно проще, чем внесение изменений непосредственно в файл ресурсов. Однако это не означает, что такой подход является обязательным.

    Код, создающий основное меню для нашей программы, достаточно прост:

    IDR_MAINFRAME MENU PRELOAD DISCARDABLE

    MENUITEM «Новое OKHO\t», ID_FILE_TEXT

    MENUITEM «Выход», ID_APP_EXIT

    MENUITEM «О программе . «, ID_APP_ABOUT

    Команду меню «О программе . » я оставил, поскольку она у нас есть. Но вопросы, которые с ней связаны, мы рассмотрим в следующей главе, когда будем знакомиться с блоками диалога .

    Прежде всего мы определяем идентификатор меню — IDR_MAINFRAME , который передаем в функцию LoadFrame для регистрации оконного класса и создания окна Windows. Само тело меню заключено между операторными скобками BEGIN и END . Как видно из текста, элементы меню бывают двух видов: POPUP — целое окно (например, раскрывающийся перечень компонентов, который появляется при открытии меню File (Файл) во многих приложениях) и MENUITEM — единичный элемент меню, после объявления которого в кавычках записана символьная строка, изображаемая в строке меню. Объявляется также константа, которая ассоциирована с данным пунктом меню. (Обратите внимание, что каждый пункт меню ассоциируется с некоторой константой.) Как вы знаете из предыдущей главы, когда пользователь выбирает конкретный пункт меню, соответствующая константа и пункт меню упаковываются в параметры сообщения WM_COMMAND и посылаются в соответствующую оконную процедуру. Связав с этой константой определенный обработчик сообщений, мы можем выполнить любые необходимые нам действия. Например, для того чтобы создать новое окно «MDI child» и вывести его на экран, мы создаем обработчик сообщения Оn-Text и связываем его посредством карты сообщений с командой меню ID_FILE_TEXT аналогично тому, как были связаны между собой сообщение WM_CREATE и обработчик OnCreate :

    afx_msg void OnText();

    afxjnsg int OnCreate(LPCREATESTRUCT IpCreateStruct);

    BEGIN_MESSAGE_MAP (CMainFrame, CMDIFrameWnd)

    Mdi многодокументный интерфейс

    В книгах по программированию при рассмотрении различных приёмов и методов приводятся в основном «игрушечные» примеры. Иногда даже можно встретить высказывания автора: «профессиональные программы так не пишут». В самом начале изучения современного объектно-ориентированного программирования я не задумывался над тем, что значит писать профессионально. Задумался, когда стал писать масштабный проект. В этой статье хочу поделиться своим опытом — описать несколько своих решений.

    Изначально ставилась задача: разработать модель для построения приложений, ориентированных на работу с базами данных (БД). Под таким приложением подразумевается набор форм, каждая из которых обычно отображает одну таблицу БД. Например, в бухгалтерской или складской программе таблицы «Накладные», «Клиенты», «Товары» удобно расположить на отдельных формах. Несколько таблиц с малым числом строк и столбцов можно было бы расположить на одной форме, например: «Категории товаров», «Типы накладных», «Единицы измерения». Пользователь должен иметь возможность выбирать окно, с которым он хочет работать. Поэтому где-то должно быть меню или список всех или почти всех окон. Понятно, что окно «Накладная» в этом списке отсутствует. Оно будет открываться из списка накладных (окно «Накладные»). Было бы так же удобно открывать последнюю приходную накладную (окно «Накладная») для товара под курсором из окна «Товары». Вот для таких приложений и предназначена описанная в статье модель.

    Модель приложения можно свести к абстракции «Окно—>Документ», где Окно — это список Документов, например «Окно—Накладные»—>»Документ—Накладная». Нечто похожее на модель «Master—>Detail», только на разных формах (у нас). В свою очередь Документ может быть Окном, из которого можно открыть другой Документ и т.д., т.е. опять «Окно—>Документ». Например «Окно—Накладная»—>»Документ—Клиенты». И по большому счёту, чем отличается Окно от Документа? Ведь связь может быть и обратной: Документ—>Окно. Под связью понимаем любое действие, инициированное из текущего окна (формы) по отношению к другому окну (форме). Это действие даже может и не требовать отображения того другого окна. Поэтому модель можно упростить ещё: «Документ Документ». Иными словами — множество окон с множеством связей между ними.

    Модель будет рассмотрена на примере Delphi, но может быть реализована и на других объектно-ориентированных языках имеющих такие конструкции, как классы, наследование и интерфейсы. Модель построена на основе многооконного интерфейса MDI . На Рис.1 изображено несколько уровней иерархии классов форм. Начальный, наиболее абстрактный уровень — уровень платформы. Под платформой понимается библиотека абстрактных классов и универсальных функций. На этом уровне расположены два базовых класса — класс главной формы TBaseMDIForm и класс дочерней формы TBaseMDIChildForm . Если мы пишем программу складского учёта (для абстрактного заказчика), переходим на другой уровень путём наследования (пунктирные стрелки) необходимых форм от соответствующих базовых классов. Это я называю уровнем схожих проектов. Здесь содержится вся функциональность окон конкретного проекта для абстрактного приложения. Из этих окон уже можно строить полнофункциональное приложение. Но конкретное приложение для конкретного заказчика строится из окон следующего уровня — уровня конкретного приложения. На этом уровне может быть несколько изменён внешний вид окон, переопределены некоторые методы и функции под конкретного заказчика. Для большей ясности приведён Рис.2. Если мы пишем программу для бухгалтерии с базой данных, отличной от базы данных в программе складского учёта, то мы переходим с уровня платформы путём наследования на уровень схожих проектов 2, т.е. это будет параллельная ветвь. И т.д.

    Связи между окнами (Рис. 1) показаны сплошными линиями. Т.к. основная функциональность окон находится на уровне схожих проектов, все основные связи между окнами тоже. И сейчас возникает интересный вопрос: как правильно организовать эти связи? Если бы мы строили приложение из окон этого уровня, всё было бы хорошо — каждое окно «знало» бы о других окнах (классах форм) из секции uses. Но мы то строим приложение из наследников этих окон. Получается сложная ситуация — наследники должны «знать» о наследниках. Т.е. часть функциональности, общей для ряда заказчиков, должна уйти на уровень конкретного приложения для конкретного клиента. Это недопустимо, потому что теряется преимущество объектного программирования. Не будем же мы каждый раз после изменений основной функциональности копировать программный код между соседними ветвями уровня конкретного приложения. Вот здесь может помочь использование интерфейсов (специальная конструкция языка). Можно создать отдельные интерфейсы для всех классов окон с нужными свойствами, функциями и методами. Тогда уже окнам будет незачем «знать» друг о друге. Им нужно будет «знать» только об интерфейсах, которые реализуют нужные классы окон. Следовательно, связи между окнами будут находиться там, где и положено, а наследники окон будут нести только функциональность для конкретного приложения (заказчика). И при необходимости смогут иметь свои связи к другим окнам (используя интерфейсы), которых не предусмотрено на уровне выше.

    Одно из решений выглядит так. Параллельно с созданием функциональности множества окон надо параллельно создать для каждой группы связей свой интерфейс, содержащий нужные функции, свойства, методы. А при вызове интерфейса надо перебрать все окна в приложении, найти то, которое реализует нужный интерфейс, потом вызвать нужную функцию (свойство). Поскольку функция (свойство) интерфейса может вызываться из многих мест, никто не мешает автоматизировать этот процесс путём создания некого универсального механизма поиска нужного интерфейса среди существующих и «несуществующих»(классов) окон. Дело в том, что окна с нужным интерфейсом в момент его поиска может ещё не существовать. Мы не собираемся при запуске программы создавать сразу все возможные окна. Ведь пользователь может вообще не воспользоваться многими окнами и их интерфейсами в данном сеансе работы с программой. Предположим сейчас, найдено существующее окно «Документ», реализующее связь «Открыть определённый документ». А вдруг пользователь производил там редактирование и не закрыл его (отложил на время). Если мы позволим создать связь с этим окном, оно уже должно будет отображать другой документ и все произведённые пользователем изменения могут пропасть. Значит, необходим некий критерий, позволяющий универсальному механизму поиска определять — можно ли установить связь с окном, либо надо создать другое окно того же класса.

    Предлагается способ решить все вышеуказанные сложности весьма простым механизмом. В абстрактной модели «Документ Документ» есть только один объект — Документ. Поэтому достаточно использовать только один интерфейс (IDoc) с одной функцией (ProcessParams), аргументом которой будет массив с любым числом элементов любого типа. Способ обработки этого универсального параметра определяет сам программист без привлечения других интерфейсов, наследования, функций-оболочек. При помощи такого универсального параметра можно организовать создание большого разнообразия связей между формами. Интерфейс IDoc будет реализоваться на уровне платформы классом TBaseMDIChildForm . Поэтому все наследники от этого класса автоматически реализуют этот интерфейс. Поскольку функция ProcessParams должна быть универсальной, тип единственного параметра ( Params ) используем array of const ( array of TVarRec ) — массив с любым числом членов любого типа. Таким образом, мы сняли необходимость добавлять новый интерфейс для каждого нового класса формы (или набора действий) и добавлять в него новую функцию при создании новой связи между формами. Интерфейс IDoc мы будем вызывать не напрямую, а посредством вспомогательного объекта DocManager . При запуске программы мы регистрируем ( RegisterFormClass ) в DocManager классы всех необходимых окон конкретной программы. Регистрация осуществляется с указанием номера класса и заголовка формы. Номер класса уникален для ветви уровня схожих проектов (Рис. 2). Заголовок формы необходим, т.к. предполагается автоматически создавать меню со списком окон без необходимости сразу создавать все окна. При организации связи с другим окном будем пользоваться функциями ShowDoc и ProcessDocParams . В качестве параметров для этих функций нужно задать номер класса и параметр типа array of const ( Params ). Поэтому для связи с другим окном данное окно должно «знать» только номер класса. Ссылки на класс (вызываемой формы) и интерфейс IDoc не требуются. ShowDoc отображает окно с передачей в него нужного параметра. ProcessDocParams организует обработку параметра без необходимости отображать окно (в фоновом режиме). Обе функции создают при необходимости окно нужного класса и затем вызывают ProcessParams ( IDoc ) созданного окна.

    Этот механизм очень напоминает технологию COM в ОС Windows, только внутри одного приложения.

    Рассмотрим один из случаев применения вышеуказанного принципа. Из списка накладных (окно «Накладные») мы хотим увидеть содержимое накладной под курсором. Для этого мы вызываем ShowDoc с указанием номера класса. В качестве параметра Params массив, один из членов которого является уникальным номером накладной из списка накладных. DocManagerst создаёт окно «Накладная» и передаёт туда массив Params с номером накладной (и др. параметрами при необходимости). В окне «Накладная» по этому номеру мы загружаем список товаров соответствующей накладной. А что будет, если пользователь не закрыв это окно, вернётся к списку накладных и опять инициирует открытие окна «Накладная»? Тут возможно два случая — пользователь хочет просмотреть содержимое той же накладной или он хочет просмотреть уже другую накладную. Для таких случаев существует вот какой механизм. IDoc имеет вспомогательные процедуры SetParams для сохранения Params в форме и ParamsIs для определения идентичности с Params , сохранённым через SetParams . При вызове DocManager.ShowDoc если найдена уже существующая форма нужного класса, происходит вызов ParamsIs для проверки равенства Params из ShowDoc и Params существующей формы. Если они равны, показываем существующую форму на переднем плане, если Params`ы не равны, то создаём новую форму на переднем плане с передачей туда нового Params .

    В форме TBaseMDIChildForm после вызова SetParams происходит сохранение Params не в виде array of const , а в виде динамического массива типа Variant . Конвертация происходит функцией VarOpenArrayToVarArray в модуле Misc . Там же есть функция VarEqual , которая вызывается из ParamsIs . VarEqual и VarOpenArrayToVarArray построены специальным образом, который определяет степень свободы задания элементов массива Params типа array of const . В нём можно задавать элементы практически любых типов. Ординарные типы, ссылки на объекты, адреса переменных с соответствующим преобразованием при их интерпретации. Даже можно задать в качестве элемента динамический массив типа Variant , элементами которого могут быть тоже массивы типа Variant . При этом VarEqual будет работать корректно (на основе рекурсии). Замеченное ограничение — невозможность передачи строк String со служебными кодами типа 0х0, 0х1, 0х2 и т.д. Ничего с этим пока поделать не смог.

    Ещё несколько особенностей. ProcessDocParams не влияет на Params , сохранённый в TBaseMDIChildForm с помощью SetParams (т.е. из ShowDoc ). ProcessDocParams не вызывает ParamsIs и SetParams формы. ProcessDocParams и ShowDoc вызывают вспомогательные методы интерфейса IDoc DocInit и ProcessParams . Их можно переопределить в наследниках. DocInit предназначен для инициализации формы, там можно открывать таблицы БД, обрабатывать Params из ShowDoc . А ProcessParams предназначен для обработки Params из ShowDoc и из ProcessDocParams .

    В DocManager встроен механизм заполнения пункта меню списком заголовков зарегистрированных классов форм с целью предоставления пользователю способа открытия желаемой формы. Функция CreateMenuItems принимает параметр типа TMenuItem , где хотим создать вышеуказанный список (Обычно это пункт главного меню главной формы). Причём параллельно автоматически заполняется свойство объекта DocManager ActionList типа TActionList . Его можно использовать для заполнения «вручную» (программистом) альтернативного средства выбора окон не меняя код TDocManager .

    При регистрации класса окна ( DocManager.RegisterFormClass ) необходимо указать дополнительный параметр — это тип окна. Есть три типа «Окно», «Документ» и «Отчёт». При вызове CreateMenuItems всё, что зарегистрировано как «Документ» не входит в меню, а то, что помечено как «Отчёт», попадает в конец меню после разделителя. Предполагается, что «Документ» вызывается из других окон (например окно «Накладная»), а количество и порядок «Отчётов» могут часто меняться, поэтому в конце. В качестве пункта меню выбора доступных окон удобно использовать пункт главного меню главной формы.

    DocManager создавать вручную не надо, создаётся и уничтожается автоматически при добавлении в проект ссылки на модуль Doc .

    Некоторые рекомендации по использованию Params: array of const . Рекомендуется первым элементом массива использовать целое число — номер команды (связи), достаточно сделать уникальным в пределах класса формы на уровне схожих проектов и ниже. Т.о. при вызове ShowDoc и ProcessDocParams , чтобы попасть в нужное место, указываем номер класса ( TypeId: Integer ), номер команды (Например первый элемент Params: array of const ). В нужной форме в ProcessParams анализируем первый элемент массива Value :Variant , в DocInit анализируем первый элемент массива FParams :Variant (поле данных TBaseMDIChildForm ). В остальных элементах Params: array of const передаём всё, что необходимо для связи с другой формой.

    Рассмотрим один частный случай применения вышеуказанного принципа. Предположим, что мы хотим из нескольких мест программы («Список документов» «Список товаров») открывать окно «Накладная», в котором находится содержимое соответствующего документа. В качестве параметра при организации связи используем уникальный номер накладной в рамках БД. Всё бы хорошо. Но есть одно «но». Реальная ситуация — от общего родителя «Абстрактный документ» наследовано несколько конкретных: «Приход», «Расход», «Акт переоценки». Это разные классы, имеющие разные номера при регистрации. Т.о. напрямую вызывать ShowDoc можем но это не удобно, нам надо ещё знать тип документа: «Приход», «Расход», «Акт переоценки». Это чтоб выбрать необходимый номер класса. Решение у меня такое. Вызываем окно «Список документов» при помощи ProcessDocParams , с передачей номера документа. В окне «Список документов» в ProcessParams организуем механизм запроса из БД типа документа по его номеру. Далее вызываем ShowDoc с указанием номера класса, который соответствует типу данного документа, и транслируем туда же номер документа (другой элемент массива Params ), полученный от другой формы через ProcessDocParams . Что у нас получилось. Допустим, пользователь из «Списка товаров» хочет открыть последний документ, содержащий товар под курсором. Им может оказаться как «Приход», так и «Акт переоценки». После нажатия к примеру он сразу увидит нужное окно, а как организован механизм его открытия он может даже и не догадываться. Ну а из «Списка документов» открыть нужный документ можно вызвав напрямую «свой» ProcessParams либо тоже через DocManager (для однообразия). Изящно, не правда ли?

    Прилагается рабочий код уровня платформы, демонстрационный код уровня схожих проектов и конкретного приложения. См. комментарии в исходном коде. Необходимо: Delphi 7, BDE. После распаковки запустить Proj1Firm1.dpr, скомпилировать.

    Распространение статьи приветствуется, целиком с указанием источника. Использование программного кода и идей приветствуется.

    К материалу прилагаются файлы:

    • Демонстрационный проект (151 K) обновление от 3/5/2007 3:14:00 AM

    Mdi многодокументный интерфейс

    Multiple Document Interface — Multiple Document Interface, MDI … Universal-Lexikon

    Multiple document interface — Graphical computer applications with a multiple document interface (MDI) are those whose windows res >Wikipedia

    Multiple Document Interface — MDI Anwendung Multiple Document Interface (MDI) bezeichnet eine Form der grafischen Benutzeroberfläche für Programme. Sie zeichnet sich dadurch aus, dass in einem Programmfenster gleichzeitig mehrere Dokumente geöffnet werden können, die dann in… … Deutsch Wikipedia

    Multiple document interface — Sommaire 1 Présentation 2 Comparaison MDI/SDI 2.1 Avantages du mode MDI 2.2 Désavantages du mode MDI … Wikipédia en Français

    multiple document interface — daugiadokumentė sąsaja statusas T sritis informatika apibrėžtis Programos ↑grafinė sąsaja, kurioje vienu metu galima tvarkyti daugelį dokumentų, kurių kiekvienas atveriamas pagrindiniame programos lange. Dokumento langas užima ne daugiau vietos… … Enciklopedinis kompiuterijos žodynas

    multiple document interface — Abbreviated MDI. In Microsoft Management Console, an interface that allows more than one independently running snap in to be loaded into the management window at the same time. See also Microsoft Management Console; snap in … Dictionary of networking

    Tabbed document interface — In the area of graphical user interfaces, a tabbed document interface (TDI) is one that allows multiple documents to be contained within a single window, using tabs to navigate between them. It is an interface style most commonly associated with… … Wikipedia

    Single Document Interface — Typisches Beispiel für eine SDI Benutzerschnittstelle ist GIMP, hier werden viele kleine Fenster verwendet … Deutsch Wikipedia

    Single document interface — Inkscape utilise le système SDI En Informatique, Single Document Interface ou SDI désigne une méthode d organisation de l interface graphique d une application multi fenêtrée. L application se décompose en une ou plusieurs fenêtres gérées… … Wikipédia en Français

    Илон Маск рекомендует:  Что такое код mcal_open
    Понравилась статья? Поделиться с друзьями:
    Кодинг, CSS и SQL