Лекция № 18
Тема: "Многодокументные приложения. Компонент RichEdit"
План
1. Создание программ с MDI интерфейсом
2. Регистрация и MDI интерфейс
3. Работа с компонентом RichEdit
4. Пример корректного открытия и сохранения файлов
1. Создание программ с MDI интерфейсом
В приложении MDI есть родительская (начальная) форма и ряд дочерних форм (форм документов). Окна документов могут создаваться пользователем в процессе выполнения приложения. Число дочерних окон заранее неизвестно – пользователь может создать их столько, сколько ему потребуется. Окна документов располагаются в клиентской области родительской формы. Поэтому чаще всего целесообразно в родительской форме ограничиваться только главным меню, инструментальными панелями и, если необходимо, строкой статуса, оставляя остальное место в окне для окон дочерних форм. При этом обычно окно родительской формы в исходном состоянии разворачивают на весь экран.
Дочерние окна не могут выходить за контуры главного окна. При сворачивании таких окон они сворачиваются не в панель задач, а вниз главного окна.
На рисунке показано главное MDI окно, в котором открыто два дочерних окна и еще два окна свернуто.

Для создания MDI приложения необходимо спроектировать родительскую и дочернюю формы. В родительской форме свойство FormStyle устанавливается в fsMDIForm, а в дочерней – в fsMDIChild.
По умолчанию при запуске MDI приложения автоматически отображается и дочерняя форма. Для устранения этого недостатка дочернюю форму необходимо исключить из числа создаваемых автоматически. Для этого выполните команду Project – Option и на вкладке Forms переместите дочернюю форму из списка Auto-Create forms в список Available Forms.
Для вызова экземпляра дочерней формы на экран можно использовать следующий стандартный код:
var Переменная:TИмя_формы;
begin
Переменная:=TИмя_формы.Create(self);
Переменная.Show;
Переменная.Repaint;
end;
Пример. Пусть на родительской форме Form1 есть пункт меню "Файл – Создать" под именем mnuNew. Этот пункт позволяет создать экземпляр дочерней формы Form2. Для создания экземпляра дочерней формы с помощью меню необходимо написать код:
procedure TForm1.mnuNewClick(Sender: TObject);
var f:TForm2;
begin
f:= TForm2.Create(self);
f.Show;
f.Repaint;
end;
По умолчанию принято, чтобы в заголовке каждой открытой дочерней формы отображался ее порядковый номер. Для этого опишите глобальную целую переменную:
k:integer=0;
В код создания дочерней формы внесите изменения:
procedure TForm1.mnuNewClick(Sender: TObject);
var f:TForm2;
begin
f:= TForm2.Create(self);
k:=k+1;
f.caption:='Документ '+inttostr(k);
f.Show;
f.Repaint;
end;
В родительской форме есть ряд свойств, позволяющих управлять дочерними окнами. Все они доступны только для чтения и только во время выполнения программы.
Свойство MDIChildCount определяет количество открытых дочерних окон.
Обычно пользователь может открыть сколько угодно однообразных форм документа. В большинстве случаев это верный подход. Однако, иногда это не всегда целесообразно. Для открытия дочерней формы только в одном экземпляре можно проверять свойство MDIChildCount как показано в следующем примере:
//если количество открытых дочерних форм ровно 0
if MDIChildCount=0 then
//то открываем дочернюю форму
begin
Form2:=TForm2.Create(self);
Form2.Show;
Form2.Repaint;
end;
В некоторых программах необходимо создавать разные виды дочерних форм по одном экземпляру для каждого вида.
Для решения этой проблемы используются свойства MDI формы:
- MDIChildCount - количество открытых дочерних форм;
- MDIChildren - это массив, который содержит общее число открытых дочерних форм. Каждый элемент этого массива MDIChildren[i] - является отдельной формой. Нумерация форм начинается с 0.
Поэтому достаточно в коде проверять наличие в массиве формы нужного вида. Если такая форма найдена, то она уже была открыта раньше и ее создание не происходит. Кроме того очень часто в программах при попытке создать уже существующую дочернюю форму, программа вместо создания делает эту форму активной.
Пусть есть две дочерние формы Form2 и Form3. На главной форме есть два пункта меню (N2 и N3), каждый из которых открывает по одном экземпляру дочерних форм. Для решения этой задачи нужно написать код:
procedure TForm1.N2Click(Sender: TObject);
var i:integer;
begin
//цикл по всем дочерних формах
for i := 0 to MDIChildCount - 1 do
//если форма TForm2 уже создана
if (MDIChildren[i] is TForm2) then
begin
//если форма TForm2 свернута пользователем
if MDIChildren[i].WindowState = wsMinimized then
//форма разворачивается к обычному виду
MDIChildren[i].WindowState := wsNormal
//если форма Form2 не свернута
else
//отображаем форму Form2 поверх других окон
MDIChildren[i].BringToFront;
//завершаем процедуру, новая форма не создается
Exit;
end;
//если форма Form2 не найдена среди созданных
//создаем ее
Form2:=TForm2.Create(nil);
//отображаем форму на экране
Form2.Show;
Form2.Repaint;
end;
Аналогично напишем код для пункта меню n3
procedure TForm1.N3Click(Sender: TObject);
var i:integer;
begin
//цикл по всем дочерних формах
for i := 0 to MDIChildCount - 1 do
//если форма TForm3 уже создана
if (MDIChildren[i] is TForm3) then
begin
//если форма TForm3 свернута пользователем
if MDIChildren[i].WindowState = wsMinimized then
//форма разворачивается к обычному виду
MDIChildren[i].WindowState := wsNormal
//если форма Form3 не свернута
else
//отображаем форму Form3 поверх других окон
MDIChildren[i].BringToFront;
//завершаем процедуру, новая форма не создается
Exit;
end;
//если форма Form3 не найдена среди созданных
//создаем ее
Form3:=TForm3.Create(nil);
//отображаем форму на экране
Form3.Show;
Form3.Repaint;
end;
Для обращения к текущему экземпляру дочерней формы главная форма имеет свойство ActiveMDIChild. Для обращения к конкретному экземпляру дочерней формы используют запись вида:
(ActiveMDIChild as TИмя_формы)
Для нашей дочерней формы Form2 обращение запишется так:
(ActiveMDIChild as TForm2)
В момент создания окон документов они автоматически располагаются каскадом в клиентской области родительской формы. Есть ряд методов родительской формы, которые упорядочивают размещение дочерних окон.
Cascade; //располагает все открытые (не свернутые) окна каскадом
TileMode:= tbVertical;
Tile; //располагает окна слева направо
TileMode:= tbHorizontal;
Tile; //располагает окна сверху вниз
При закрытии дочерней формы вызовом метода Close или с помощью кнопки закрытия форма не закрывается, а сворачивается вниз главной формы. Для решения этой проблемы необходимо в событии OnClose дочерней формы вставить код:
Action:=caFree;
Проектирование меню в MDI приложениях
В главном окне, когда нет дочерних форм по умолчанию отображается минимальный набор команд меню. Как только открывается дочерняя форма, набор команд меню значительно увеличивается. Это достигается за счет того, что дочерняя форма может иметь собственное меню, которое автоматически встраивается в меню главной формы. При закрытии дочерней формы происходит возвращения меню в начальное состояние.
Если нужно, чтобы меню вторичных форм объединялись с меню главной формы, то в каждой дочерней форме надо создать свой компонент MainMenu и для него установить свойство AutoMerge= true. При этом свойство AutoMerge главной формы должно оставаться равным false.
Способ объединения меню определяется свойством разделов GroupIndex. По умолчанию все разделы меню имеют одинаковое значение GroupIndex=0. Если нужно объединенить меню, то разделам надо задать возрастающие номера свойств GroupIndex. Тогда, если разделы встраиваемого меню имеют те же значения GroupIndex, что и какие-то разделы меню основной формы, то эти разделы заменяют соответствующие разделы основного меню. А если нет, то разделы вспомогательного меню встраиваются между элементами основного меню согласно номерам GroupIndex. Если встраиваемый раздел имеет GroupIndex меньший, чем каждый из разделов основного меню, то данный раздел встраивается в начало.
Теперь остановимся на одном из вопросов, связанных с меню в MDI приложениях. В них пользователь может открывать произвольное числодочерних окон. В подобных приложениях есть меню "Окно", в котором всегда есть список открытых пользователем дочерних форм. Пользователь может перейти в нужную форму, выбрав ее из данного списка.
Для включения в меню списка открытых окон, надо в свойстве WindowMenu главной MDI формы указать имя меню, в конец которого должен помещаться список. Указывается имя самого меню, а не его команд.
Отображение ярлыков дочерних форм
Использование списка дочерних форм через пункт "Окно" является стандартным, но не всегда удобным. Для удобства пользователя можно отображать все дочерние формы как закладки (аналогично панели задач Windows). Для создания таких закладок можно использовать специальные компоненты, например, сторонний компонент MDIPanel (по умолчанию его нет на панели интсрументов и он требует установки).
Для его использования достаточно нанести компонент на главную MDI форму и через свойство AlignPanel задать вид его размещения (по умолчанию внизу).
2. Регистрация и MDI интерфейс
Если программа имеет главное MDI окно, а также окно регистрации, которое появляется перед главной формы, то в этом случае возникает проблема. Она связана с тем, что форма регистрации должна появится первой, то есть должна быть главной в проекте. Однако по стандарту только родительская MDI форма может быть главной. Поэтому при использовании окна регистрации корректное построение MDI интерфейса становится проблематическим.
В этом случае приходится окно регистрации отображать через код проекта до запуску основной программы. Если пароль верный, то запускается основная программа. Иначе - выход.
Пример. Пусть форма Form2 - это форма регистрации. Форма не создается автоматически (перемещена в список Available Forms) и при закрытии удаляется из памяти (в событии OnClose формы введите команду Action:=caFree;).
На форме есть поле для ввода пароля и две кнопки. Надо проверить пароль и, если он верный, то "сообщить" об этом главной программе и запустить ее. Для "сообщения" главной программе будем использовать глобальную переменную key, которая принимает значение: 0 - пароль не верный, 1 - пароль верный.
Опишем глобальную переменную в коде формы Form2.
var key:integer=0;
Для второй кнопки (Отмена) напишем код закрытия формы:
procedure TForm2.Button2Click(Sender: TObject);
begin
//форма закрывается
Close;
end;
Для первой кнопки напишем код:
procedure TForm2.Button1Click(Sender: TObject);
begin
//если пароль верный
if Edit1.Text='111' then
//переменная принимает значение 1
key:=1;
//форма закрывается
Close;
end;
При закрытии окна проверим значение переменной key.
procedure TForm3.FormClose(Sender: TObject; var Action: TCloseAction);
begin
//если переменная key=0 (пароль не верный)
if key=0 then
//завершаем работу приложения
Application.Terminate;
end;
В коде проекта введите команду отображения формы регистрации, а потом проанализирует значение переменной key и выполните соответствующие действия.
begin
//создаем форму регистрации
Form2:=TForm2.Create(nil);
//отображаем форму для регистрации
Form2.ShowModal;
...
end.
3. Работа с компонентом RichEdit
Компонент RichEdit (Win32) является окном редактирования многострочного текста. Данный компонент имеет те же свойства, методы и события, что и компонент Memo. Однако компонент RichEdit работает с текстом в обогащенном формате RTF и позволяет форматировать выделенные фрагменты и отдельные абзацы.
При желании изменить атрибуты выделенного фрагмента текста вы можете задать свойство SelAttributes. Например, для форматирования выделенного фрагмента с помощью FontDialog используют команду вида:
if FontDialog1.Execute then
RichEditl.SelAttributes.Assign(FontDialog1.Font);
За выравнивание, отступы и т.п. в пределах текущего абзаца отвечает свойство Paragraph. Это свойство в свою очередь имеет ряд параметров:
|
Alignment |
Определяет выравнивание текста. Может принимать значение taLeftJustify (влево), taCent( по центру) или taRightJustify (вправо). |
|
FirstIndent |
Число пикселей отступа красной строки. |
|
Numbering |
Задает вставку маркеров, как в списках. Может принимать значения: nsNone – отсутствие маркеров, nsBullet – маркеры отображаются. |
|
LeftIndent |
Отступ в пикселях от левого поля. |
|
RightIndent |
Отступ в пикселях от правого поля. |
Значение параметров свойства Paragraph можно задавать только в процессе выполнения. Значение параметров свойства Paragraph относятся к тому абзацу, в котором находится курсор (если выделить несколько абзацев, то ко всем выделенным). Например, каждый из следующих операторов осуществит соответствующее выравнивание текущего абзаца:
RichEditl.Paragraph.Alignment:=taLeftJustify; //влево
RichEditl.Paragraph.Alignment:=taCenter; //по центру
RichEditl.Paragraph.Alignment:=taRightJustify; //вправо
Следующий оператор приведет к тому, что текущий абзац будет отображаться в виде маркированного списка:
RichEditl.Paragraph.Numbering:=nsBullet;
Отключение списка в текущем абзаце осуществляется оператором:
RichEditl.Paragraph.Numbering:=nsNone;
4. Пример корректного открытия и сохранения файлов
Пусть на главной форме есть пункт меню "Файл – Открыть" под именем mnuOpen. Данная команда меню загружает выбранный в диалоге файл в компонент RichEdit, расположенный на дочерней форме Form2.
Вначале в коде второй формы (которая будет дочерней) опишите глобальную текстовую переменную для хранения имени открытого файла.
fname:string='';
Для команды меню на главной форме напишем код:
procedure TForm1.mnuOpenClick(Sender: TObject);
//описываем переменную для создания дочерней формы
var f:TForm2;
begin
if OpenDialog1.Execute then
begin
//открываем новую дочернюю форму
f:=TForm2.Create(self);
f.show;
//загружаем файл в компонент RichEdit1 на дочерней форме
(ActiveMDIChild as TForm2).RichEdit1.Lines.LoadFromFile(OpenDialog1.FileName);
//запоминаем полный путь к файлу
(ActiveMDIChild as TFdoc).fname :=Opendialog1.FileName;
// в заголовке дочернего окна отображаем имя открытого файла
(ActiveMDIChild as TFdoc).Caption:= ExtractFileName(OpenDialog1.FileName);
end;
end;
Пусть на дочерней форме есть пункт меню "Файл – Сохранить как…" под именем mnuSaveAs. Данная команда сохраняет в файл содержимое компонента RichEdit1.
procedure TForm2.mnuSaveAsClick(Sender: TObject);
begin
//указываем имя файла из заголовка окна
SaveDialog1.FileName:=Caption;
if SaveDialog1.Execute then
begin
//сохраняем компонент RichEdit1 на форме Form2 в файл
RichEdit1.Lines.SaveToFile(SaveDialog1.FileName);
//запоминаем полное имя файла, в который сохранены данные
fname:=SaveDialog1.FileName;
//в заголовке окна отображаем имя файла
Caption:= ExtractFileName(SaveDialogl.FileName);
end;
end;
Пусть в меню дочерней формы есть пункт "Файл – Сохранить" под именем mnuSave. Данная команда при сохранении нового файла должна открыть диалог для ввода имени файла, а при сохранении существующего файла – переписать файл под имеющимся именем.
procedure TForm2.mnuSaveClick(Sender: TObject);
begin
//если файл новый
if fname='' then
//вызываем метод для пункта меню Сохранить как…
mnuSaveAsClick(Sender)
//если файл не новый
else
//перезаписываем файл под имеющимся именем
RichEdit1.Lines.SaveToFile(fname);
end;
Особенности открытия и сохранение документов в Richedit
Компонент RichEdit может работать как с документами RTF, так и с обычными текстовыми файлами. Свойство Plaintext (плоский текст) компонента RichEdit определяет, с каким именно форматом работает RichEdit при чтении из файла и при записи в файл. По умолчанию Plaintext = false, то есть компонент настроен на работу с форматом RTF. При Plaintext = true компонент настроен на работу с обычными текстовыми файлами.
Возможность сохранения документов как в текстовом формате, так и в формате RTF, можно обеспечить таким способом. В компоненте SaveDialog надо установить фильтры (свойство Filter):
в формате rtf|*.rtf|только текст txt|*.txt
В свойстве DefaultExt нужно задать расширение по умолчанию "rtf", которое соответствует первому фильтру.
При изменении пользователем типа фаайла в диалоге сохранения файла возникает событие OnTypeChange компонента SaveDialog. В обработчике этого события нужно задать расширение по умолчанию и свойство Plaintext.
procedure TForm1.SaveDialog1TypeChange(Sender: TObject);
begin
//если выбранный первый фильтр (rtf)
if SaveDialogl.FilterIndex = 1 then
begin
//указываем формат текста rtf
RichEditl.Plaintext:=false;
//задаем расширение по умолчанию для файлов rtf
SaveDialogl.DefaultExt:='rtf';
end
//если выбранный второй фильтр (txt)
else
begin
//указываем формат текста txt
RichEditl.Plaintext:=true;
//задаем расширение по умолчанию для текстовых файлов
SaveDialogl.DefaultExt:='txt';
end;
end;
Для открытия файла в разных форматах этот же код можно написать в событии OnTypeChange для компонента OpenDialog.
Вопросы для самоконтроля
1. Как создать главную MDI форму? Как создать дочернюю форму?
2. Как обратиться к активной дочерней форме? Как упорядочить дочерние окна на главной?
3. Для чего нужен компонент RichEdit? Приведите его основные свойства. Как можно форматировать выделенный текст и абзацы в поле?
4. Как сохранить содержимое компонента RichEdit в разных форматах?