Проект ЦИТадель
Обзоры • курсы • практикумы
Не «что нажать», а «как устроено»
Глава 1. Знакомство с Windows В следующих нескольких главах мы построим графическую инте- рактивную программу Windows, дополненную меню, средствами сохра- нения и загрузки файлов, графикой и отображением текста, а также специализированными управляющими элементами. По ходу дела вы поз- накомитесь с основными принципами проектирования программы Windows, такими как обработка сообщений, управление дочерним и порождающим окном и автоматическое повторное отображение графики. Этот процесс разбит на следующие шаги: - создание базового приложения; - вывод текста в окне; - изображение линий в окне; - добавление строки меню; - добавление диалогового окна; - изменение атрибутов пера; - повторное отображение графики; - сохранение изображения в файле; - печать образа; - добавление всплывающего окна; - добавление специализированных управляющих элементов; - создание специализированного управляющего элемента окна. Исходный код приложения для различных этапов вы можете найти на дистрибутивных дисках. Описываемым в руководстве шагам соот- ветствуют файлы STEP01.PAS, STEP02.PAS и так далее (можно найти также промежуточные программы). Шаг 1: Создание базового приложения ----------------------------------------------------------------- +-----------------------+ |XStepX1:XBasicXAppXXXXX| Базовая программа | Step 2: Text | Текст | Step 3: Lines | Строки | Step 4: Menu | Меню | Step 5: About Box | Об окне | Step 6: Pens | Перья | Step 7: Painting | Рисование | Step 8: Streams | Потоки | Step 9: Printing | Печать | Step 10: Palette | Палитра | Step 11: BWCC | Управляющие элементы окна | Step 12: Custom ctrls | Специализированные элементы +-----------------------+ Отправным пунктом для всех программ, которые вы пишете с применением ObjectWindows, является программа STEP01A.PAS. Эта программа, которая называется Steps, создает основное окно прило- жения. Все программы ObjectWindows должны использовать модуль OWindows, которые содержит стандартные объекты, используемые ObjectWindows для приложений и окон. Большинство приложений вклю- чают в себя также диалоговые блоки и соответствующие управляющие элементы. ObjectWindows предусматривает для них объекты в модуле ODialogs. Объекты, относящиеся к печати, находятся в модуле OPrinter. Программам, применяющим наборы и потоки, необходим мо- дуль Objects. Кроме модулей ObjectWindows большинству программ необходимы также модули WinTypes и WinProcs. Эти два модуля определяют типы и константы (WinTypes) и процедуры и функции (WinProcs), образую- щие прикладной программный интерфейс Windows (API). Приложениям, использующим продвинутые средства Windows (версий старше 3.0), кроме данных двух нужны также другие модули. Примечание: Обзор модулей ObjectWindows вы можете най- ти в Главе 7 "Иерархия ObjectWindows". Требования к приложению ----------------------------------------------------------------- Все приложения Windows имеют основное окно, которое выводит- ся при запуске программы пользователем. Пользователь выходит из приложения, закрывая основное окно. В приложении ObjectWindows основное окно представляет собой объект окна. Этот объект принад- лежит объекту приложения, который отвечает за создание и вывод на экран основного окна, обработку сообщений Windows и завершение программы. Объект приложения действует как объектно-ориентирован- ная замена самого приложения. Аналогично, чтобы сделать скрытыми детали программирования в Windows, ObjectWindows предусматривает окно, диалоговый блок и другие объектные типы. Каждая программа ObjectWindows должна определять новый тип приложения, являющегося наследником предоставляемого типа TApplication. В программе Steps этот тип называется TMyApplication. Приведем основной блок программы Steps: var MyApp: TMyApplication; begin MyApp.Init('Steps'); MyApp.Run; MyApp.Done; end. Init - это конструктор TMyApplication, создающий новый объ- ект MyApp. Он позволяет также задать имя приложения (поле объек- та) 'Steps' и создает (и выводит) основное окно приложения. Run запускает последовательность вызовов методов, составляющих ход выполнения приложения Windows. Done - это деструктор TMyApplication. Определение типа приложения ----------------------------------------------------------------- Ваша прикладная программа должна создавать новый тип из стандартного типа ObjectWindows TApplication (или некоторых ти- пов, производных от TApplication). Этот новый тип должен переоп- ределять по крайней мере один метод - InitMainWindow. TApplication.InitMainWindow вызывается ObjectWindows автоматичес- ки для установки основного окна программы. Каждое приложение ObjectWindows должно строить свое основное окно. Примечание: Объекты приложения подробно описываются в Главе 8. Определение TMyApplication имеет следующий вид: type TMyApplication = object(TApplication) procedure InitMainWindow; virtual; end; Инициализация основного окна ----------------------------------------------------------------- InitMainWindow отвечает за построение объекта окна, исполь- зуемого в качестве основного окна программы. Этот объект основно- го окна хранится в поле объекта приложения MainWindow. Объекту приложения принадлежит объект основного окна, но эти два объекта не являются родственными в иерархии наследования. precedure TMyApplication.InitMainWindow; begin MainWindow := New(PWindow, Init(nil, 'Steps')); end; Обычно метод InitMainWindow модифицируется для создания но- вого типа основного окна. Указанный метод использует экземпляр объекта TWindow - предоставляемый ObjectWindows тип окна, который определяет наиболее общее окно. На шаге 2 мы заменим его более интересным оконным типом. Пока программа Steps просто выводит на экран пустое окно, которое можно перемещать по экрану, изменять его размер, миними- зировать, максимизировать и закрывать. Приведем полный листинг программы Steps, которую мы получили к данному моменту: program Steps; uses OWindows; type TMyApplication = object(TApplication) procedure InitMainWindow; virtual; end; procedure TMyApplication.InitMainWindow; begin MainWindows := New(PWindow, Init(nil, 'Steps')); end; var MyApp: TMyApplication; begin MyApp.Init('Steps'); MyApp.Run; MyApp.Done; end. Объект основного окна ----------------------------------------------------------------- Пока программа Steps состоит из двух объектов - объекта при- ложения и объекта окна. Объект приложения (MyApp) является эк- земпляром TMyApplication - типом, производным от TApplication. Оконный объект, который содержится в поле MainWindow объекта MyApp, является экземпляром TWindow (общее окно ObjectWindows). Во всех программах, кроме простейших, вам нужно определить тип своего основного окна, соответствующий поведению приложения. В данном разделе мы выведем на экран основное окно, тип которого является производным от TWindow. Приложения: Более подробно об основном окне рассказы- вается в Главе 8 "Объекты приложения". Что такое объект окна? ----------------------------------------------------------------- Объект приложения инкапсулирует стандартное поведение прило- жения Windows, включая построение основного окна. Тип TApplication обеспечивает фундаментальное поведение для каждого создаваемого вами приложения. Аналогично, объект окна инкапсулирует поведение, реализуемое приложениями ObjectWindows, включая их основные окна. Это поведе- ние включает в себя вывод на экран, изменение размера и закрытие; ответ на пользовательские события, такие как щелчок кнопкой "мы- ши", буксировку и выбор пунктов меню; вывод управляющих элемен- тов, таких как блоки списка и командные кнопки. Тип TWindow и его предок TWindowsObject предусматривают для данного базового пове- дения методы и поля. Примечание: Объекты окна подробно описываются в Главе 10 "Объекты окна". Чтобы сделать ваши программы полезными и интересными, вам нужно создать новый тип, производный от TWindow. Новый тип насле- дует поля и методы TWindow и добавляет часть своих. В общем слу- чае объектно-ориентированный подход позволяет вам не "изобретать" каждый раз окно. Описатели ----------------------------------------------------------------- Объект окна имеет по крайней мере три поля: HWindow. Parent и ChildList. HWindow содержит описатель окна. Описатель окна - это уникальное число, которое связывает интерфейсный объект (та- кой как окно, диалоговый блок или объект управляющего элемента) с соответствующим элементом экрана. Примечание: Подробно об описателях окна их использо- вании рассказывается в Главе 10 "Объекты окна". Таким образом, HWindow содержит целое значение, идентифици- рующее соответствующий элемент экрана. Это напоминает бирку на связке ключей. Аналогично тому как вы выбираете ключ, чтобы дос- тать из шкафа пальто, вы выбираете описатель для получения окна. В большинстве случаев вы работаете с объектами окна, и у вас нет необходимости манипулировать описателем окна непосредственно, но они используются при вызове функций Windows. Например, на данном шаге вы вызываете функцию MessageBox. Эта функция требует указа- ния параметра, идентифицирующего порождающее окно сообщений. Вы указываете основное окно, описатель которого записан в его поле HWindow: MessageBox(MainWindow^.HWindow, 'Хотите сохранить?', 'Файл не изменен', mb_YesNo or mb_IconQuestion); Порождающие и дочерние окна ----------------------------------------------------------------- Большинство приложений используют несколько окон, и, чтобы они взаимодействовали, их требуется связать. Например, когда вы завершаете приложение, оно должно иметь способ очистки всех окон, за которые отвечает. В общем случае Windows справляется с этим, связывая окна как дочернее и порождающее. Порождающее окно отве- чает за свое дочернее окно. ObjectWindows предусматривает для каждого объекта окна поля для отслеживания порождающего и дочер- них окон. Примечание: Взаимодействие этих окон подробнее описы- вается в Главе 9. Поле Parent содержит указатель на порождающий оконный объ- ект. Это не порождающее окно в смысле предка, а скорее окно-вла- делец. Взаимосвязь этих окон описывается в шаге 10. Третье поле оконного объекта - это поле ChildList, содержа- щее связанный список дочерних окон. Создание нового типа окна ----------------------------------------------------------------- Теперь у вас есть некоторое представление о том, что содер- жит оконный объект, и вы можете создать новый оконный тип, произ- водный от TWindow, используя его как основное окно программы Step. Сначала измените определения и задайте новый тип TStepWindow. Не забудьте также определить новый указатель на тип TStepWindow - PStepWindow, который будет полезен при создании эк- земпляров объектов TStepWindow. type PStepWindow = ^TStepWindow; TStepWindow = object(TWindow) end; Затем измените TMyApplication.InitMainWindow, чтобы создать в качестве основного окна вместо TWindow TStepWindow. procedure TMyApplication.InitMainWindow; begin Main := New(PStepWindow, Init(nil, 'Step')); end; Определение нового типа и создание его экземпляра в InitMainWindow - это все, что требуется для определения нового типа основного окна для TMyProgram. Объект приложения вызывает методы для создания интерфейсного элемента окна (Create) и вывода его на экран (Show). Вам почти никогда не потребуется использо- вать эти методы непосредственно. Обычно они вызываются при вызове метода MakeWindow объекта приложения. Примечание: MAkeWindow поясняется в Главе 9 "Интер- фейсные объекты". Однако TStepWindow не определяет новых видов поведения, от- личных от тех, которые наследуются от TWindow и TWindowObject. Другими словами, программа Step не становится более интересной. Такие виды поведения будут добавлены в следующем разделе. Реакция на сообщения ----------------------------------------------------------------- Скорейший способ сделать оконный объект полезным - это зас- тавить его отвечать на некоторые сообщения Windows. Например, когда вы щелкаете "мышью" в основном окне программы Step, Windows посылает окну сообщение wm_LButtonDown, которое перехватывается ObjectWindows и посылается затем соответствующему оконному объек- ту. Это указывает оконному объекту, что пользователь щелкнул в нем кнопкой "мыши". При этом передаются также координаты точки, где пользователь нажал кнопку. (Эту информацию мы используем в шаге 2.) Примечание: Сообщения Windows определены в модуле WinTypes. Аналогично, когда пользователь щелкает правой кнопкой "мы- ши", основной оконный объект получает сообщение wm_RButtonDown, переданное Windows. На следующем шаге мы узнаем, как сделать так, чтобы основное окно (экземпляр TStepWindow) отвечало на эти сооб- щения и делало что-нибудь полезное. Чтобы перехватывать сообщения Windows и отвечать на них, для каждого типа поступающего сообщения, на которое вы хотите реаги- ровать, вам нужно определить метод оконного объекта. Такие методы называются методами реакции на сообщение. Чтобы определить заго- ловок определения метода как метод реакции, нужно добавить к вир- туальному методу расширение, представляющее собой идентификатор сообщения, на которое нужно реагировать. Например, определенный ниже метод реагирует на все сообщения wm_LButtonDown. type TStepWindow = object(TWindow) procedure WMLButtonDown(var Msg: TMessage); virtual vm_First + wm_LButtonDown; end; Примечание: Все программы и модули, переопределяющие методы ответа на сообщение, должны использовать WinTypes. Все сообщения в Windows, включая системные сообщения Windows и команды меню, представляются в виде чисел. Каждый метод реакции на сообщение должен иметь уникальное число, так что для сообщений Windows и команд, если они имеют одинаковые номера, вызываются различные методы. Чтобы облегчить для вас эту задачу, ObjectWindows определяет для каждого вида сообщений константы: wm_First для сообщений окон, cm_First для командных сообщений и nf_First для уведомляю- щих сообщений. Подробнее об этих константах рассказывается в Гла- ве 7, но сейчас нужно только помнить, что когда вы пишете метод реакции на сообщение, начинающееся с wm_, к нему добавляется wm_First. Msg - это запись типа TMessage, содержащая такую информацию, как координаты точки, где была нажата кнопка "мыши". Все методы реакции на сообщение должны воспринимать один параметр-переменную типа TMessage. Аргумент Msg мы рассмотрим в программе Step позд- нее. В данный момент вы можете просто определить методы реакции, которые выводят на экран окно сообщения о нажатии кнопки "мыши". Позднее вы сможете добавить более полезную реакцию. Приведем оп- ределение метода реакции на нажатие левой кнопки "мыши": procedure TStepWindow.WMLButtonDown(var Msg: TMessage); begin MessageBox(HWindow, 'Вы нажали левую кнопку мыши', 'Диспетчеризуемое сообщение', mb_OK); end; Примечание: Программы, которые вызывают MessageBox или другие функции API Windows, должны использовать модуль WinProcs. +-----------------------------------------------------------+-+-+ |#=#XXXXXXXXXXXXXXXXXXXXStepsXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX|^|v| +-----------------------------------------------------------+-+-| | | | +----------------------------------------------+ | | |@=@#########Диспетчеризуемое сообщение########| | | +----------------------------------------------| | | | | | | | Вы нажали левую кнопку мыши | | | | +-----------+ | | | | |####OK#####| | | | | +-----------+ | | | +----------------------------------------------+ | | | +---------------------------------------------------------------+ Рис. 1.2 Программа Steps реагирует на пользовательское собы- тие. Завершение прикладной программы ----------------------------------------------------------------- Программа Steps завершает выполнение, когда пользователь дважды щелкает "мышью" в блоке управляющего меню основного окна - маленьком квадрате в левом верхнем углу. Окно и приложение немед- ленно закрываются. Такое поведение годится для простых программ, но может вызвать затруднения в более сложных случаях. Перед выходом хорошо написанное приложение всегда спрашива- ет, хочет ли пользователь сохранить несохраненные результаты ра- боты. Такой вид поведения вы легко можете добавить в свою прик- ладную программу ObjectWindows. Начните со Step и добавьте двой- ную проверку запроса пользователя на выход. Когда пользователь пытается закрыть приложение ObjectWindows, Windows посылает основному окну сообщение wm_Close, которое вызывает метод CanClose приложения. CanClose - это булевская функция, указывающая, можно ли завершить (OK) при- ложение (True). По умолчанию метод CanClose наследуется из вызова TApplication метода CanClose основного оконного объекта. В боль- шинстве случаев решение о закрытии (OK) принимается объектом ос- новного окна. Переопределение CanClose ----------------------------------------------------------------- Тип основного окна TStepWindow наследует метод CanClose от TWindowObject, которые вызывает методы CanClose каждого из своих дочерних окон (если они имеются). Если дочерних окон нет (как в данном случае), CanClose просто возвращает значение True. Чтобы модифицировать поведение приложения при закрытии, вы можете пере- определить метод CanClose для объектного типа своего основного окна: function TStepWindow.CanClose: Boolean; var Reply: Integer; begin CanClose := True; Reply := MessageBox(HWindow, 'Хотите сохранить?', 'Графическое изображение изменено', mb_YesNo or mb_IconQuestion); if Reply = id_Yes then CanClose := False; end; Теперь когда пользователи попытаются закрыть Step, они полу- чат окно сообщений с запросом "Хотите сохранить". Щелчок "мышью" на командной кнопке Yes (Да) приводит к тому, что CanClose возв- ращает значение False и предотвращает закрытие основного окна и приложения. Щелчок "мышью" на No (Нет) возвращает True, и прило- жение завершает работу. На шаге 8 это окно сообщений получит не- который смысл. Модифицированная программа Steps показана на Рис. 1.3. +-----------------------------------------------------------+-+-+ |#=#XXXXXXXXXXXXXXXXXXXXStepsXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX|^|v| +-----------------------------------------------------------+-+-| | | | | | +----------------------------------------------+ | | |@=@#########Изображение изменилось############| | | +----------------------------------------------| | | | @@@ | | | | @?@ Хотите сохранить? | | | | @@@ +-----------+ +-----------+ | | | | |###Yes#####| |####No#####| | | | | +-----------+ +-----------+ | | | +----------------------------------------------+ | | | +---------------------------------------------------------------+ Рис. 1.3 Программа Steps с переопределенным поведением окна. Дальнейшее изменение закрытия ----------------------------------------------------------------- Естественно, сообщение о том, что изображение изменилось, полезно только в том случае, если программа действительно обнару- живается изменение изображения. Добавив в TStepWindow поле типа Boolean, вы можете задать флаг, указывающий на изменение изобра- жения, и выводить окно сообщения только тогда, когда этот флаг установлен. Нужно помнить о том, что когда вы добавляете это поле, поле нужно также инициализировать, поэтому переопределим конструктор TStepWindow: type PStepWindow = ^TStepWindow; TStepWindow = object(TWindow) HasGhanged: Boolean; constructor Init(AParent: PWindowsObject: ATitle: PChar); . . . end; constructor TStepWindow.Init(AParent: PWindowsObject: ATitle: PChar); begin inherited Init(AParent, ATitle); HasChanged := False; end; Далее измените метод CanClose для проверки перед выводом ок- на сообщения HasChanged: function TStepWindow.CanClose: Boolean; var Reply: Integer; begin CanClose := True; if HasChanged then begin Reply := MessageBox(HWindow, 'Хотите сохранить?', 'Изображение изменилось', mb_YesNo or mb_IconQuestion); if Reply = id_Yes then CanClose := False; end; end; Позднее, когда вы фактически изменяете изображение, HasChanged нужно установить в True. Следующий листинг показывает полный исходный под программы Steps на данном шаге: program Steps; uses WinTypes, WinProcs, OWindows; type TMyApplication = object(TApplication) procedure InitMainWindow; virtual; end; type PStepWindow = ^TStepWindow; TStepWindow = object(TWindow) Haschanged: Boolean; constructio Init(AParent: PWindowsObject; ATitle: PChar); function CanClose: Boolean; virtual; procedure CanClose: Boolean; virtual; procedure WMLButtonDown(var Msg: TMessage); virtual wm_First + wm_LButtonDown; procedure WMRButtonDown(var Msg: TMessage); virtual sm_First +? wm_RButtonDown; end; constructor TStepWindow.Init(AParent: PWindowsObject; ATitle: PChar); begin inherited Init(AParent, ATitle); HasChanged := False; end; function TStepWindow.CanClose: Boolean; var Reply: Integer; begin if HasChanged then begin CanClose := True; Reply := MessageBox(HWindow, 'Хотите сохранить?', 'Изображение изменилось', mb_YesNo or mb_IconQuestion); if Reply = id_Yes then CanClose := False; end; end; procedure TStepWindow.WMLButtonDown(var Msg: TMessage); begin MessageBox(HWindow, 'Вы нажали левую кнопку мыши', 'Диспетчеризуемое сообщение', mb_OK); end; procedure TStepWindow.WMRButtonDown(var Msg: TMessage); begin MessageBox(HWindow, 'Вы нажали правую кнопку мыши', 'Диспетчеризуемое сообщение', mb_OK); end; procedure TMyApplication.InitMainWindow; begin MainWindows := New(PStepWindow, Init(nil, 'Steps')); end; var MyApp: TMyApplication; begin MyApp.Init('Steps'); MyApp.Run; MyApp.Done; end.
Назад | Содержание | Вперед