Глава 11. Объекты диалоговых блоков


    Блоки диалога, или просто диалоги, являются интерфейсными объектами, инкапсулирующими поведение диалоговых блоков. Это дочерние окнам, которые обычно выводятся для выполнения специфических задач (например, конфигурирования принтера или организации ввода текста). Объект TDialog обеспечивает инициализацию, создание и исполнение всех типов блоков диалога. Для каждого типа диалога вашего приложения, как и в случае оконных объектов, вы можете определить производные от TDialog диалоговые блоки.

    ObjectWindows всегда предоставляет два типа диалогов наиболее общего типа, ввод текста и диалог файла. Кроме того, в ObjectWindows имеется тип TDlgWindow, который позволяет вам создавать диалоги, поведение которых более похоже на окно.

    Данная глава охватывает следующие темы:

Использование объектов диалоговых блоков


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

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

    Использование объекта диалогового блока предусматривает следующие шаги:

Построение объекта


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

    Каждый ресурс диалогового блока имеет идентификатор, который может быть номером идентификатора (Word) или строкой (PChar). Этот идентификатор позволяет объекту диалогового блока задавать, какой ресурс используется для определения его внешнего вида.

Вызов конструктора


    Чтобы построить объект диалогового блока, вызовите конструктор Init. Init воспринимает в качестве своих параметров указатель на порождающее окно и параметр типа PChar, представляющий имя ресурса диалога:

     ADlg:=New(PSampleDialog, Init(@Self, 'EMPLOYEEINFO'));

    Если идентификатор задается номером, его требуется привести с помощью MakeIntResource к PChar:

     Dlg := New(PSampleDialog, Init(@Self, PChar(120)));

    Так как диалоговые блоки обычно строятся внутри метода оконного объекта, порождающее окно почти всегда задается как Self. Объекты диалоговых блоков, не создаваемые оконными объектами, должны иметь в качестве порождающего Applicartion^.MainWindow (поскольку это единственный оконный объект, всегда присутствующий в каждой программе ObjectWindows).

Выполнение диалоговых блоков


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

Режимные и безрежимные диалоговые блоки


    Режимные диалоговые блоки являются наиболее общими блоками диалога. Аналогично генерируемым функцией MessageBox блокам сообщений, режимные диалоги отображаются для специфических целей на короткий отрезок времени. Слово "режимный" означает, что пока отображается диалог, пользователь не может выбрать или использовать его порождающее окно. Пользователь должен воспользоваться диалогом и выбрать командную кнопку OK или Cancel для прекращения диалога и возвращению к работе с программой. Режимный диалог как бы "замораживает" выполнение оставшейся части программы.

    Безрежимный диалоговый блок не приостанавливает выполнения программы. Как и оконный объект, он может создаваться и выполняться в одном шаге с помощью MakeWindow:

     Application^.MakeWindow(ADlg);

    В любое момент вы можете считать данные из диалогового окна (если объект диалогового блока еще существует). Чаще всего это выполняется в методе OK, который вызывается при активизации пользователем командной кнопки OK.

Выполнения режимных диалоговых блоков


    В случае режимных диалоговых блоков лучше всего, вероятно, строить, выполнять и уничтожать все объекты в одном методе (как показано в примерах данной главы). Таким образом, при каждом выводе диалогового блока это будет новый объект.

    Объекты приложения имеют режимный эквивалент MakeWindow, который называется ExecDialog. Аналогично MakeWindows, ExecDialog проверяет допустимость передаваемого объекта диалогового блока (то есть успешность выполнения конструктора объекта и отсутствие ситуации нехватки памяти), а затем выполняет диалоговый блок, делая его модальным.

    ExecDialog возвращает целочисленное значение, указывающее, что пользователь закрывает диалоговое окно. Возвращаемое значение - это идентификатор задействованного пользователем управляющего элемента, такой как id_Ok для командной кнопки OK или id_Cancel для командной кнопки Cancel. После завершения выполнения диалогового окна ExecDialog уничтожает объект диалогового окна.

    Таким образом, с помощью одного вызова метода ExecDialog вы можете создать, вывести на экран и завершить диалоговый блок.

     ADlg := New(PSampleDialog, Init(@Self, 'RESOURCEID'));
     ReturnValue := Application^.ExecDialog(ADlg);
     if ReturnValue = id_OK then
           { кодирование для выборки данных и обработки диалога }
     else
     if ReturnValue = id_Cancel then { нажата Cancel }

Выполнение безрежимных диалоговых блоков


    Безрежимные диалоги похожи на всплывающие окна и управляющие элементы. Основная причина, по которой вы не можете удалять безрежимными диалогами сразу же после их отработки (в отличие от режимных), состоит в том, что вы заранее не знаете, когда пользователь закроет блок диалога. (Помните о том, что в режимных диалогах метод ExecDialog не возвращает значения до закрытия диалога.) Следовательно лучше всего конструировать безрежимные диалоги в конструкторе его порождающего окна и хранить в поле порождающего объекта.

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

     constructor ParentWindow.Init(AParent: PWindowsObject;
                                   ATitle: PChar);
     begin
       TWindow.Init(AParent, ATitle);
       ADlg := New(PSampleDialog, Init(@Self, 'EMPLOYEEINFO'));
     end;

    Затем, каждый раз, когда вы хотите отобразить диалог, создайте и выведите его:

     begin
       Application^.MakeWindow(ADlg)
     end;

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

Работа с безрежимными диалоговыми блоками


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

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

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

Завершение диалогов


    Каждый блок диалога должен иметь способ его закрытия пользователем. Чаще всего это кнопки OK и/или Cancel. Потомки TDialog автоматически отреагируют на нажатие одной из этих кнопок вызовом метода EndDlg, который заканчивает диалог. Вы можете разработать новые средства завершения диалога, если только они приводят к вызову EndDlg. Для изменения поведения при закрытии вы можете переопределить методы OK и Cancel.

    Например, вы можете переопределить метод OK таким образом, что введенные данные будут копироваться в буфер, который находится вне объекта блока диалога. Если ввод был осуществлен некорректно, вы можете вывести блок сообщения или сгенерировать звуковой сигнал. Если ввод был сделан верно, вы можете вызвать EdnDlg. Переданное в EndDlg значение становится возвращаемым значением ExecDialog.

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

Работа с управляющими элементами


    Все блоки диалога, кроме самых простейших, имеют (как дочерние окна) несколько управляющих элементов (например, управления редактированием, блоки списка и командные кнопки). Обратите внимание на то, что эти управляющие элементы являются не объектами управляющих элементов, а только управляющими интерфейсными элементами, без методов и полей объекта. Эта глава кроме того показывает альтернативные методы, позволяющие связывать объекты управления с элементами управления диалоговых блоков с использованием InitResource.

    Примечание: Использование управляющих объектов в окне (но не блоков диалога) показано в Главе 12.

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

Взаимодействие с управляющим элементом


    Windows определят набор сообщений управляющих элементов, которые посылаются от приложения к Windows. Например, имеются следующие сообщения блока списка: lb_GetText, lb_GetCurSel и lb_AddString. Сообщения управляющих элементов задают специфическое управление и несут с собой информацию в аргументах wParam и lParam. Каждый управляющий элемент в ресурсе диалога имеет номер идентификатора, который вы используете для задания управляющего элемента, принимающего сообщение. Для посылки сообщения управляющему элементу нужно вызвать метод TDialg SendDlgItemMsg. Например, данный метод заполнит блок списка диалога элементами текста путем посылки сообщения lb_AddString:

     procedure TestDialog.FillListBox(var Msg: TMessage);
     var
       TextItem: PChar;
     begin
      TextItem := 'Item 1';
      SendDlgItemMsg(id_LB1, lb_AddString, 0, Longint(TextItem));
     end;

где id_LB1 есть константа, равная ID блока списка.

    Если вам потребуется описатель одного из управляющих элементов диалога, его можно получить методом GetItemHandle:

     GetItemHandle(id_LB1);

Реакция на сообщения управляющих элементов


    Когда пользователь выбирает управляющий элемент, например, "нажимает" командную кнопку или делает выбор в блоке списка, диалоговым блоком управляющего элемента порождающего окна принимается специальное сообщение, основанное на дочернем окне и называемое управляющим сообщением (сообщение управляющего элемента). Определим метод реакции на сообщение, основанное на дочернем идентификаторе, в порождающем типе диалога для каждого дочернего управляющего элемента:

     TTestDialog = object(TDialog)
     procedure HandleBN1Msg(var Msg: TMessage); virtual
                              id_First + id_BN1;
     procedure HandleListBox(var Msg: TMessage); virtual
                              id_First + id_LB1;
     end;

    В данном примере id_BN1 - это идентификатор кнопки управляющего элемента, а id_LB1 - это идентификатор блока списка. Щелчок "мышью" на командной кнопке даст сообщение, посылаемое в диалоговый блок. Объект диалогового блока реагирует через динамический метод с индексом, основанным на идентификаторе кнопки IDBN1.

    Примечание: Уведомляющие сообщения подробно поясняются в Главе 16 "Сообщения Windows"

    Каждое управляющее информационное сообщение поступает с кодом уведомления - целочисленной константой, которая идентифицирует произведенное действие. Например, результатом выбора элемента из блока списка будет сообщение с кодом lbn_SelChange, где lbn_ уведомление блока списка (List Box Notification). Нажатие кнопки "мыши" дает сообщение bn_Clicked. Ввод в управляющем элементе редактированием приводит к сообщению с кодом en_Changed. Имеются коды уведомления для блоков списка, управляющих элементов редактированием, комбинированных блоков и командных кнопок. Код уведомления передается в Msg.lParamHi сообщения. Для восприятия управляющих информационных сообщений напишем метод реакции для типа диалога, обрабатывающий важные коды уведомления:

     procedure TestDialog.HandleLB1Msg(var Msg: TMesage);
     begin
       case Msg.lParamHi of
              lbn_SelChange:    { изменение порядка выбора };
              lbn_DblClk:    { выбор с помощью двойного щелчка };
       end;
     end;

    Управляющие элементы, имеющие соответствующие объекты, могут отвечать на уведомления сами. См. Главу 16.

Пример связи


    В файле с текстом программы DIALTEST.PAS, основное окно имеет режимный диалог, определенный типом диалога TTestDialog. Эта программа обеспечивает двухстороннюю связь между объектом диалога и его управляющими элементами. Два метода - IDBN1 и IDLB1 - являются методами реакции, основанными на дочерних идентификаторах, и вызываются при выборе пользователем управляющих элементов (дочерних окон). Например, при выборе пользователем кнопки диалога BN1 ('Fill List Box') вызывается метод IDBN1. Аналогично, когда пользователь делает выбор в блоке списка, вызывается IDLB1. С другой стороны, для заполнения блока списка элементами текста код метода IDBN1 посылает в диалог управляющее сообщение, lb_AddString, используя метод диалога SendDlgItemMsg.

    Эта программа также показывает как путем создания нового типа диалога и связывания его с ресурсом диалога в вызове конструктора Init метода TestWindow.RunDialog создаются новые диалоги. Полный текст программы вы можете найти на дистрибутивных дисках.

Ассоциирование объектов управляющих элементов


    До этого момента мы имели дело с реакцией блоков диалога на управляющие информационные сообщения, которая использовала методы реакции, основанные на дочерних идентификаторах. Однако, иногда более предпочтительно, чтобы управляющий элемент сам реагировал на сообщение. Например, вам может потребоваться управляющий элемент редактирования, который позволяет вводить только цифры, или командная кнопка, которая меняет стиль при своем "нажатии". Это можно реализовать с помощью объектов управляющих элементов в окнах (см. Главу 12). Однако, чтобы это имело место для управляющих элементов диалога, созданного с файлом ресурса, вам нужно использовать для конструирования объекта другой конструктор.

    При организации связей вы создаете объект управляющего элемента для представления управляющего объекта диалога. Этот объект управления дает вам гибкость в реакции на управляющие сообщения. Он дает вам возможность использования набор методов объектов управляющих элементов, описанных в Главе 12.

    Для связи объекта с управляющим элементом определите сначала объект управляющего элемента. Он должен быть создан в конструкторе диалога. Однако, вместо того, чтобы использовать конструктор Init, как это показано в Главе 12, следует использовать InitResource, который берет в качестве параметров порождающее окно и идентификатор управляющего элемента (из ресурса диалога). Это приводит к вызову методов реакции на сообщения объектов управляющих элементов вместо обработки элементов по умолчанию. Для этого нужно определить новый тип объекта, производный от предусмотренного типа управляющего элемента.

    Обратите внимание, что в отличие от задания оконного объекта, которое предполагает два шага (Init и MakeWindow), поскольку управляющий элемент уже существует, связь объекта с управляющим элементов выполняется за один шаг: он загружается из диалогового ресурса. Вам нужно только сообщить InitResource, какой управляющий элемент из ресурса вы хотите связать с объектом, используя идентификатор управляющего элемента.

Использование диалоговых окон


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

    Одним из подходов размещения управляющих элементов в окне является использование объектов управляющих элементов (как показано в Главе 12). Другой подход - это слияние возможностей диалоговых блоков и окон, как это делается в объектном типе TDlgWindow, что позволяет получить гибридный объект, называемый диалоговым окном. Второй подход предусматривает более удобный способ построения и управления многими управляющими элементами в окне. Кроме того, он предлагает для диалоговых блоков более гибкие средства окон.

    TDglWindow является потомком TDialog и наследует его методы, такие как Execute, Create, Ok и EndDlg. Как и диалоговые блоки, диалоговые окна имеют соответствующий ресурс диалогового блока. С другой стороны, как и окна, диалоговые окна имеют соответствующий класс окон, определяющий среди всего прочего пиктограмму, курсор и меню. Из-за связи с оконным классом в потомке TDlgWindow следует переопределять методы GetClassName и GetWindowClass. Этот класс должен быть тем же, что и перечисленный в диалоговом ресурсе.

    В большинстве случаев вы будете выполнять диалоговые окна как и другие окна или безрежимные диалоговые окна с помощью методов Create и Show, а не метода Execute.

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

Использование предопределенных диалоговых окон


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

Использование диалоговых блоков ввода


    Диалоговые блоки ввода - это простые объекты диалоговых блоков, определяемых типом TInputDialog, которые выводят пользователю подсказку со строкой текста.

    Вы можете запускать диалоги ввода как режимные или безрежимные диалоговые блоки, но обычно вы будете выполнять их как режимные. С объектом диалога ввода связан ресурс диалога ввода. Он находится в файле ObjectWindows OSTDDLGS.RES.

    Примечание: Использование модуля StdDlgs автоматически включает ресурсы в OSTDDLGS.RES.

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

     var SomeText: array[0..79] of Char;
     begin
       AnInputDlg.Init(@Self, 'Caption', 'Prompt', SomeText,
             SizeOf(SomeText))
         .
         .
         .
     end;

    В данном примере EditText - это текстовый буфер, который заполняется вводом пользователя, когда он "нажимает" кнопку OK. Когда пользователь "нажимает" кнопку OK или клавишу Enter, строка введенного в диалоге ввода текста автоматически передается в массив символов, который хранит текст по умолчанию. В данном примере конструируется и отображается блок диалога и считывается текст:

     procedure TSampleWindow.Test(var Msg: TMessage);
     var
       EditText: array[0..255] of Char;
     begin
       EditText:='Frank Borland';
       if ExecDialog(New(PInputDialog, Init(@Self, 'Data Entry',
       'Введите имя:', EditText, SizeOf(EditText)))) = id_OK then
       MessageBox(HWindow, EditText, 'Имя =', mb_OK);
       else MessageBox(HWindow, EditText,
                       'Имя пока имеет значение:',mb_OK);
     end;

Файловые диалоговые блоки


    Файловые диалоговые блоки являются другим типом диалогов, поставляемых с ObjectWindows в типе TFileDialog. Файловый диалоговый блок следует использовать каждый раз, когда вы желаете побудить пользователя ввести имя файла, например в функциях File Open и File Save во всех приложениях. См. Рис. 11.1.


Рис. 11.1 Файловый диалоговый блок.

    В большинстве случаев файловые диалоговые блоки выполняются как режимные. С объектом файлового диалога связан ресурс файлового диалогового блока, имеющийся в ObjectWindows в файле OSTDDLGS.RES. Использование модуля OStdDlgs автоматически включает файл ресурса.

Инициализация файлового диалогового блока


    TFileDialog определяет конструктор Init, который позволяет задать маску файла и буфер для считывания имени файла. Маска файла (такая как '*.TXT') ограничивает файлы, перечисляемые в комбинированном блока (аналогично тому, как это делается в команде DOS DIR *.TXT). Имя файла и маска передаются в записи типа TFileDlgRec. Приведем пример вызова файлового диалогового блока Init:

     var
       FileRec: TFileDlgRec;
       IsOpen: Boolean;
     begin
       StrCopy(FileRec.Name, 'TEST1.TXT');
       StrCopy(FileRec.Mask, 'C:\*.TXT');
       IsOpen := True;
       AFileDlg.Init(@Self, FileRec, IsOpen);
        .
        .
        .
     end;

    Последний параметр указывает, будет ли диалог диалогом открытия или сохранения (как описывается в следующем разделе).

Выполнение файловых диалоговых блоков


    Существует два вида файловых диалоговых блоков: диалоговый блок открытия файла и диалоговый блок сохранения файла. Они различаются текстом кнопки в правом верхнем углу диалогового окна. В зависимости от того, запрашивает пользователь открытие или сохранение файла, на командной кнопке будет написано Open или Save. Когда вы вызовите ExecDialog, то получите тип диалогового блока, заданных в конструкторе IsOpen параметром типа Boolean. Если файловый диалоговый блок строится с IsOpen, установленным в True, то диалоговый блок будет работать как диалоговый блок открытия файла. Если он строится с IsOpen, установленным в False, то файловый диалоговый блок будет блоком сохранения файла.

    Дополнительным средством файлового диалогового блока ObjectWindows является то, что он выводит пользователю подсказку, хочет ли пользователь сохранить файл с именем уже существующего файла (см. Рис. 11.2). В другой раз вы можете запросить пользователя, хочет ли он открыть новый файл или очистить текущий текст без сохранения. Поскольку это должно происходить перед выводом файлового диалогового блока, то не является частью поведения этого блока. В примере программы Steps в первой части данного руководства перед загрузкой рисунка из файла проверяется метод CanClose его основного окна.


Рис. 11.2 Предупреждение пользователя о перезаписи существующих файлов.

     File exists! Overwrite it? - файл существует, затереть его?

    Приведем пример типичного использования диалогового окна:

     procedure TMyWindow.OpenSelectedFile;
     var FileRec: TFileDlgRec;
     begin
       StrCopy(FileRec.Name, 'HEDGEHOG.PAS');
       StrCopy(FileRec.Mask, '*.PAS');
       if ExecDialog(New(PFileDialog,
                     Init(@Self, FileRec, True))) = id_Ok then
       begin
         Assign(AFile, StrPas(FileRec.Name));
           .
           .
           .
       end;
     end;