Глава 3. Меню и диалоговые ресурсы


    Большинство приложений Windows имеют в своих основных окнах меню, которые предоставляют пользователю возможность выбора - например, команды File|Save, File|Open и Help. В шаге 4 мы добавим в программу Steps строку меню. Ввод пользователем данных и выбор параметров в программах Windows часто происходит в диалоговых блоках. В шаге 5 в программу Steps будет добавлен диалоговый блок.

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

    Ресурсы для программы проще всего создавать с помощью редакторов ресурсов, таких как Resource Workshop (пакет разработчика ресурсов) фирмы Borland. Подробно о создании и редактировании ресурсов рассказывается в "Руководстве пользователя по пакету разработчика ресурсов".

Шаг 4: Добавление строки меню


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

    Меню прикладной программы - это не отдельный объект, а атрибут основного окна. Все оконные объекты имеют набор атрибутов, записанных в поле записи Attr объекта. В поле Menu записи Attr хранится не описатель меню, а меню. Чтобы установить атрибут меню, вы должны переопределить конструктор своего типа окна TStepWindow.

Ресурсы меню


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

Определение идентификаторов ресурса
    Приложение обращается к присоединенным к нему ресурсам по идентификатору ресурса. Этот идентификатор представляет собой целое значение, например, 100, или целочисленную константу, такую как MyMenu. Кроме того, приложение отличает один выбор меню от другого по идентификатору, связанному с элементом меню.

Определение констант меню
    Чтобы сделать программу более читаемой, замените идентификаторы меню константами, определяемыми во включаемом файле. При создании своего ресурса меню с помощью Resource Workshop или компилятора ресурсов вы можете включить те же константы и использовать те же идентификаторы, которые вы используете для доступа к ресурсу к своей программе. Константы меню для программы Steps определены в файле STEPS.INC:

     const
        cm_FilePrint  = 105;
        cm_FileSetup  = 107;
        cm_Pen        = 200;
        cm_About      = 201;
        cm_PalShow    = 301;
        cm_PalHide    = 302;

    Заметим, что число элементов меню в файле STEPS.INC не определено. Это связано с тем, что ObjectWindows в файле IWINDOWS.INC определяет для вас некоторые общие команды меню, включая cm_FileOpen, cm_FileNew, cm_FileSave и cm_FileSaveAs.

Включение файлов ресурсов

    Чтобы продолжить работу с программой Steps, используйте пакет разработчика ресурсов или компилятор ресурсов для создания ресурса меню и сохраните его в файле с расширением .RES - STEPS.RES. Формат файла ресурса в исходном виде вы можете посмотреть в файле STEPS.RC. Вы можете также использовать файл STEPS.RES, который можно найти на дистрибутивных дисках. Имея файл STEPS.RES, вы можете включить его с помощью директивы компилятора $R:

     {$R STEPS.RES}

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

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

    На Рис. 3.1 показан внешний вид этого меню (идентификатор ресурса 100). Оно включает в себя пункты File (Файл), Options (Параметры) и Palette (Палитра), а меню File содержит элементы New (Новый), Open (Открытие), Save (Сохранение), Save As (Сохранение под именем), Print (Печать), Printer Setup (Установка принтера) и Exit (Выход). Элементы верхнего уровня, у которых есть подэлементы, не имеют идентификаторов меню, а их вывод не вызывает никаких действий кроме вывода подэлементов.

    Примечание: Не путайте идентификатор ресурса меню с идентификаторами меню отдельных элементов (пунктов) меню.


Рис. 3.1 Программа Steps с ресурсом меню.

Загрузка ресурса меню


    Получить ресурс меню можно с помощью вызова функции Windows LoadMenu:

     
     LoadMenu(HInstance, MakeIntResource(100));

    MakeIntResource(100) приводит число 100 к ссылочному типу PChar, представляющему собой указатель на массив символов. Функции Windows, воспринимающие в качестве аргументов строки, требуют, чтобы они имели тип PChar. Имея дело с ресурсами, Windows ожидает, что целые числа должны быть представлены в виде PChar, поэтому если вы хотите обратиться к ресурсу, имеющему числовой идентификатор, нужно преобразовать его тип с помощью MakeIntResource.

    Примечание: Для использования типа PChar требуется установка $X+ (по умолчанию).

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

     LoadMenu(HInstance, 'SAMPLE_MENU');

    Вот как это делает TStepWindow.Init (заметим, что первое, что он делает - это вызов конструктора Init, наследуемого из TWindow, для выполнения инициализации, необходимой для всех оконных объектов):

     constructor TStepWindow(AParent: PWindowObject;
                                                  ATitle: PChar);
     begin
        inherited Init(AParent, ATitle);
        Attr.Menu := LoadMenu(HInstance, MakeIntResource(100));
        BottomDown := False;
        HasChanged := False;
     end;

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

Перехват сообщений меню


    Когда пользователь выбирает элемент меню, окно, к которому присоединено меню, получает командное сообщение Windows. ObjectWindows обрабатывает и диспетчеризует эти сообщения wm_Command аналогично другим сообщениям, но облегчает для вас работу со специальными командами.

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

     procedure CMFileNew(var Msg: TMessage);
          virtual cm_First + cm_FileNew;

где cm_First - это константа ObjectWindows, определяющая начало диапазона констант для команд, а cm_FileNew - это желаемая команда меню. Это означает, что все элементы меню должны иметь уникальные идентификаторы (если только не предполагается реагировать на них одинаковым образом).

    Примечание: О диапазонах сообщений и смещениях рассказывается в Главе 16.

Не путайте основанный на cm_First динамический индекс метода с индексом, соответствующим поступающему сообщению Windows (основанному на wm_First). cm_First - это специальное смещение, используемое только для определения методов реакции для команд меню и командных клавиш.

Определение методов реакции на команду


    Теперь вы можете определить все методы реакции на команды:

     procedure CMFileNew(var Msg: TMessage);
         virtual cm_First + cm_FileNew;
     procedure CMFileOpen(var Msg: TMessage);
         virtual cm_First + cm_FileOpen;
     procedure CMFileSave(var Msg: TMessage);
         virtual cm_First + cm_FileSave;
     procedure CMFileSaveAs(var Msg: TMessage);
         virtual cm_First + cm_FileSaveAs;
     procedure CMFilePrint(var Msg: TMessage);
         virtual cm_First + cm_FilePrint;
     procedure CMFileSetup(var Msg: TMessage);
         virtual cm_First + cm_FileSetup;

    Определять процедуру CMExit не требуется, поскольку TWindowsObject уже определяет завершающую программу процедуру, которая вызывается при поступлении в основное окно сообщения cm_Exit.

Связывание клавиш с командами


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

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

    Каждая прикладная программа может иметь только один набор командных клавиш. Чтобы загрузить в программу ресурс командных клавиш, переопределите метод InitInstance:

     
     procedure TMyApplication.InitInstance;
     begin
       inherited InitInstance;
       HaccTable := LoadAccelerators(HInstance, 'ShortCuts');
     end;

    Командные клавиши 'ShortCuts' в STEPS.RES связывают знакомые вам по IDE функциональные клавиши с аналогичными функциями программы Steps. Например, клавиша F3 генерирует команду cm_FileOpen.

Реакция на команды меню


    Теперь для каждого выбора в меню у вас есть метод, который будет вызываться в ответ на соответствующую команду. Выбор команды File|Print вызывает ваш метод CMFilePrint. Пока вызовем просто окно сообщения:

     procedure TStepWindow.CMFilePrint(var sg: TMessage);
     begin
       Message(HWindow, 'Средство не реализовано',
               'Печать файла', mb_Ok);
     end;

    На Рис. 3.2 показана реакция программы Steps на выбор команды File|Print.


Рис. 3.2 Программа Steps реагирует на команду File|Print.

    Для CMFileOpen, CMFileSave, CMFileSaveAs и CMFileSetup напишите фиктивные методы, аналогичные CMFilePrint. Позднее вы перепишете данные методы для выполнения осмысленных действий.

    Теперь, очистив окно, вы можете реагировать на выбор команды меню File|New более интересным образом. Добавьте следующий метод CMFileNew:

     procedure TStepWindow.CMFileNew(var Msg: TMessage);
     begin
        InvalidateRect(HWindow, nil, True);
     end;

    InvalidateRect выполняет принудительное повторное отображение окна. Полный исходный код программы Steps для данного этапа содержится в файле STEP04A.PAS.

Добавление диалогового блока


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

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

    Вы будете выводить файловое диалоговое окно в ответ на выбор пользователем команды File|Open или File|Save As. Файловое диалоговое окно заменяет окно сообщения "Средство не реализовано". В шаге 8 оно будет приспособлено для некоторых реальных файлов, а также сохранения и открытия их для записи и считывания реальных данных. Пока просто выведем диалоговые окна. Вид файлового диалогового окна показан на Рис. 3.3.


Рис. 3.3 Программа Steps с диалоговым блоком File Open.

    Добавление к программе Steps файлового диалогового блока требует трех шагов:

Добавление поля объекта


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

    Построение файлового диалогового блока требует трех параметров: порождающего окна, шаблона ресурса и имя или маску файла (в зависимости от того, используется файловое окно для открытия или закрытия файла). Шаблон ресурса определяет, какое из стандартных файловых диалоговых окон вы хотите использовать. Стандартные файловые диалоговые ресурсы определяются идентификаторами ресурсов sd_FileOpen и sd_FileSave. Параметр имени файла используется для передачи используемой по умолчанию маски файла диалогу открытия файла (а также для возврата выбранного имени файла) и для передачи используемого по умолчанию имени для сохранения файла.

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

    Определение типа TStepsWindow должно теперь выглядеть следующим образом:

     
     TStepWindow = object(TWindow)
        .
        .
        .
       FileName: array[0...fsPathName] of Char;
        .
        .
        .

    Примечание: Для работы с константой fsPathName нужно использовать модуль WinDos.

Модификация конструктора

    Для создания экземпляра объекта справочного окна вы можете использовать конструктор Init типа TStepWindow. Теперь вам потребуется добавить к нему код для инициализации FileName:

     StrCopy(FileName, '*.PTS');

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

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

    CMFileOpen и CMFileSaveAs следует переписать следующим образом:

     procedure TStepWindow.CMFileOpen(var Msg: TMessage);
     begin
        if Application^.ExecDialog(New(PFileDialog,
           Init(@Self, PChar(sd_FileOpen), FileName))) = id_Ok
        then MessageBox(HWindow, FileName, 'Открыть файл:',
             mb_Ok);
     end;

     procedure TStepWindow.CMFileSaveAs(var Msg: TMessage);
     begin
        if Application^.ExecDialog(New(PFileDialog,
           Init(@Self, PChar(sd_FileSave), FileName))) = id_Ok
        then MessageBox(HWindow, FileName, 'Сохранить файл:',
             mb_Ok);
     end;
    Заметим, что при выполнении файлового диалогового окна используется тот же метод ExecDialog, который вы вызывали для выполнения диалогового окна ввода в шаге 3. С помощью метода ExecDialog выполняются все режимные диалоговые окна в приложении.

    Полный исходный код программы Steps для данного шага вы можете найти в файле STEP04B.PAS.

Шаг 5: Добавление диалогового блока


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

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

    Создание диалогового блока из ресурса требует следующих шагов:

Создание ресурсов диалогового блока


    Проектировать ресурсы диалогового блока можно несколькими способами, используя пакет разработчика ресурса Resource Workshop или компилятор ресурсов. Диалоговый блок (блок диалога) - это специализированное окно с рамкой, содержащее один или более управляющих элементов (командные кнопки, блоки списка и пиктограммы). Ваша прикладная программа не знает о том, как выглядят управляющие элементы и как они позиционированы; она знает только о типе управляющих элементах и их идентификаторах.

    Примечание: Не забывайте, что ресурс - это просто некое описание того, что будет создавать ваша программа.

Идентификаторы управляющих элементов


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

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

Построение объекта диалогового блока


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

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

    Файл ресурса для программы Steps определяет диалоговый блок с именем 'ABOUTBOX', которое вы можете использовать в качестве окна About box, показанного на Рис. 3.4. Построение объекта диалогового блока из данного ресурса выглядит следующим образом:

     
     New(PDialog, Init(@Self, 'ABOUTBOX'));


Рис. 3.4 Окно About Box для программы Steps.

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


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

    
    Application^.ExecDialog(New(PDialog,Init(@Self,'ABOUTBOX')));

    Естественно, нужно определить команду для вывода диалогового блока About box; Steps использует сообщение cm_About, генерируемое выбором меню Options|About. Теперь такой вид реакции на команду должен быть вам достаточно знаком (см. файл STEP05.PAS):

     
     type
       TStepWindow = object(TWindow)
            .
            .
            .
         procedure CMAbout(var Msg: TMessage);
            virtual cm_First + cm_About;
     end;

     procedure TStepWindow.CMAbout(var Msg: TMessage);
     begin
        Application^.ExecDialog(New(PDialog, Init(@Self,
                                 'ABOUTBOX')));
     end;
    В шаге 6 мы создадим более сложное диалоговое окно с несколькими управляющими элементами.

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


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

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