ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП |
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы. |
Часть 10 Глава 9. Объекты интерфейса Объекты, представляющие окна, блоки диалога и управления, называются объектами интерфейса пользователя или просто объектами интерфейса. В этой главе рассматриваются общие требования и поведение объектов интерфейса и их взаимоотношения с действительными о кнами, блоками диалога и управлениями, которые появляются на экране. В данной главе также объясняются взаимоотношения между различными объектами интерфейса приложения, и механизм передачи сообщений Windows. Тип TWindowsObject TWindowsObject это абстрактный тип объекта, который унифицирует производные от него типы объектов интерфейса Windows: TDialog, TWindow и его производный тип TControl. TWindowsObject определяет поведение, общее для объектов окна, диалога и управления. Мет оды TWindowObject: - Поддерживают двойственную структуру объекта ObjestWindows/Windows, включая создание и разрушение объектов. - Автоматически поддерживает отношения родители-дети между объектами интерфейса (которые отличаются от отношений наследования). - Регистрирует новые классы Windows; см. Главу 10, "Объекты Windows". Работа TWindowsObject происходит скрытно. Вы будете очень редко, если вообще будете, производить новые типы непосредственно из TWindowsObject, но он будет служить основанием для объектно-ориентированной модели ObjectWindows/Windows. Зачем нужны объекты интерфейса ? Зачем нужны объекты интерфейса, если Windows уже имеет визуальные окна, диалоги и управления ? Каждый объект интерфейса обычно имеет связанный с ним визуальный элемент интерфейса - не объект, а физическое окно, диалог или управление - для которого он служит объектно-ориентированным суррогатом. Объект интерфейса предоставляет методы создания, иници ализации, управления и разрушения связанного с ним элемента интерфейса, и имеет поля для хранения данных, включая регулятор его элемента интерфейса и его родительское и дочернее окно. Методы объекта интерфейса обрабатывают за вас многие детали программирования Windows. Отношения объект/элемент очень похожи на отношения между файлом DOS и переменной файла Pascal. Вы назначаете переменную файла для представления физической структуры действительного файла на диске. В ObjectWindows вы определяете переменную (объект) для пр едставления физического окна, управления или блока диалога, который в действительности управляется менеджером окна Windows. Структура объекта интерфейса с ассоциированным элементом интерфейса требует от вас некоторой осторожности при манипулировании окнами, диалогами и управлениями. Например, для создания полного объекта интерфейса вам нужно вызвать два метода. Первый - это к онструктор (например, Init), который конструирует экземпляр объекта интерфейса и устанавливает его атрибуты (например, его стиль и меню), если они есть. Второй метод - это метод создания MakeWindow, который связывает объект интерфейса с новым элементом интерфейса. Эта связь обеспечивается полем HWindow объекта интерфейса, в котором хранится регулятор окна. MakeWindow это метод объекта приложения. MakeWin dow вызывает метод объекта Create, который дает инструкцию Windows на создание визуального элемента. Create также вызывает SetupWindow, который инициализирует объект интерфейса (например, создает дочерние окна). Обычно в Windows вновь созданный элемент интерфейса получает о Windows сообщение wm_Create, на которое он может отреагировать выполнением инициализации. Объект интерфейса ObjectWindows не получает сообщения wm_Create, поэтому нужно обязательно предусмотр еть инициализацию в определении метода SetupWindow. Аналогично, при удалении объекта интерфейса из вашей программы вы должны разрушить элемент визуального интерфейса и удалить соответствующий объект интерфейса. Метод Destroy, который вызывается непосредственно программой или косвенно при закрытии пользова телем окна или его родительского окна (что будет рассмотрено далее), удаляет элемент интерфейса. Вы можете разрушить элемент интерфейса без удаления соответствующего объекта, например, если вы хотите его создать и отобразить снова. Когда элемент интерфей са разрушен, регулятор его объекта устанавливается в ноль. Анализируя регулятор объекта интерфейса можно сделать вывод о том, связан ли с ним элемент интерфейса. Обратите ваше внимание на то, что создание объекта интерфейса и соответствующего визуального элемента еще не обязательно означает появление некоторого изображения на экране. После создания визуального элемента Windows проверяет, установлен ли стиль окна ws_Visible. ws_Visible и другие стили окна устанавливаются или отменяются конструктором Init путем их установки или очисти в поле Attr.Style объекта интерфейса. Если ws_Visible установлен, то элемент интерфейса будет отображен на экране, а если ws_Visibl e не установлен, то элемент будет скрыт. В любом случае элемент может быть показан или скрыт на экране путем вызова метода объекта интерфейса Show. Отношения родители-дети в окне В приложении Windows элементы интерфейса (окна, блоки диалога и управления) связаны отношениями родителей и детей. Два элемента интерфейса связаны, когда один из них является родительским окном другого, дочернего окна. Не нужно путать этот тип отношений с наследованием или с правами собственности, которые есть отношения между объектами. Дочернее окно не наследует ничего из его родительского окна и не обязательно хранится в поле объекта родительского окна, хотя это и часто встречается. Дочернее окно есть элемент интерфейса (это не обязательно окно), которое управляется другим элементом интерфейса. Например, блоки списка управляются окном или блоком диалога, в котором они появляются. Они отображаются только тогда, когда отображается их родительское окно. В свою очередь блоки диалога являются дочерними окнами, которые управляются породившими их окнами. Когда вы сдвигаете или закрываете родительское окно, дочернее окно автоматически закрывается, а в некоторых случаях и сдвигается вместе с ним. Единственным общим прародителем всех дочерних элементов интерфейса приложения является головное окно, Хотя вы можете иметь окна и диалоги без родителей. Только блоки диалога и окна могут быть родительскими окнами, но не управления. Любой элемент интерфейса (блоки диалога, окна или управления) могут быть дочерним окном. Список дочерних окон Вы должны задать родителя элемента интерфейса в момент его конструирования. Объект родительского окна является обязательным параметром конструктора элемента интерфейса Init (пример см. в Главе 10). Объект дочернего окна отслеживает объект родительского о кна, храня указатель на этот объект в поле Parent. Он также отслеживает объекты своих дочерних окон в скомпонованном списке, хранимом в поле ChildList. ChildList обслуживается автоматически. Текущее дочернее окно, на которое указывает ChildList это после днее созданное дочернее окно. При определении нового типа объекта интерфейса с дочерними окнами нужно определить конструктор Init так, чтобы он также конструировал объекты дочерних окон. Когда приложение вызывает метод Create родительского окна, создается родительский элемент интерфейса. Если операция происходит успешно, то Create вызывает метод SetupWindow, унаследованный всеми объектами интерфейса, который по умолчанию для каждого окна списка ChildList, кроме диалогов, вызовет метод Create. Часто вы будете переопределять SetupWindow родительского окна для выполнения некоторых задач по установке после создания дочерних окон. Наполнение блока списка элементами может служить одним из при меров. В этом случае нужно быть уверенным в том, что первая строка в определении метода SetupWindow есть вызов унаследованного метода SetupWindow. Аналогично вызову метода Create родительского окна, который приводит к вызовам методов Create его дочерних окон, вызов родительского деструктора Done приведет к вызовам деструкторов всех его дочерних окон. Вашим программам нет необходимости непосредствен но вызывать Create и Done для дочерних окон. То же самое верно и для метода CanClose, который возвращает True только после вызовов CanClose для его дочерних окон. Для явного исключения дочернего окна (например, всплывающего окна) из автоматического механизма создания и демонстрации, нужно после конструирования объекта вызвать метод DisableAutoCreate. Для явного включения дочернего окна (например, обычно исключенно го блока диалога) в автоматический механизм создания и демонстрации, нужно после конструирования объекта вызвать метод EnableAutoCreate. Итераторы дочернего окна Вам может потребоваться написание метода, который итеративно выполнял бы функцию над каждым из дочерних окон. Например, нужно проверить все дочерние блоки проверки в окне. В этом случае нужно использовать метод TWindowObject.ForEach следующим образом: procedure TMyWindow.CheckAllBoxes; procedure CheckYheBox(ABox: PWindowsObject); far; begin PCheckBox(ABox)^.Check; end; begin ForEach(@CheckAllBoxes); end; Обратите внимание на то, что использование ForEach (наряду с методами FirstThat и LastThat) аналогично методам TCollection с похожими именами. Хотя ObjectWindows и не использует наборы для управления дочерними окнами, методы итерации работают вполне анал огичным образом. Вам может потребоваться написание методов, которые итеративно просматривали бы список дочерних окон в поисках конкретного окна. Например, вам нужно найти первый уже проверенный блок проверки для окна со множеством дочерних окон блоков проверки. В данном случае нужно использовать метод TWindowsObject.FirstThat следующим образом: function TMyWindow.GetFirstChecked: PWindowsObject; function IsThisOneChecked(ABox: PWindowsObject): Boolean; far; begin IsThisOneChecked:=ABox^.GetCheck; end; begin GetFirstChecked:=FirstThat(@IsThisOneChecked); end; Обработка сообщений Программы ObjectWindows имеют двухсторонний канал связи с Windows. С одной стороны, приложение управляет действиями интерфейса пользователя вызовом соответствующих функций Windows. С другой стороны, Windows посылает сообщения приложению в ответ на событ ия программы, например, выбор пользователем варианта меню (wm_Command) или нажатие левой кнопки на мыши (wm_LButtonDown). Имеется более 100 стандартных сообщений Windows, и вы можете создавать ваши собственные сообщения, как это рассматривалось в Главе 7 , "Обзор ObjectWindows". Функции Windows и сообщения составляют эту двухстороннюю среду коммуникации, показанную на Рис.9.1. Рис.9.1. Коммуникация между Windows и приложениями Приложение Windows выполняет обработку в ответ на поступающее от Windows сообщение.Специфическое для каждого приложения поведение достигается за счет выборочной реакции на поступающие сообщения Windows. В процессе работы могут быть вызваны функции Window s, которые сами могут генерировать новые сообщения Windows. Поступающие сообщения всегда предназначаются конкретному окну - тому окну, на которое текущее событие оказывает непосредственное действие. Например, при нажатии левой кнопки на мыши в конкретном окне, генерируется сообщение wm_LButtonDown для этого окна. ObjectWindows направляет поступающие сообщения на методы реакции, которые вы определяете для типов объектов вашего окна. Это взаимодействие сообщений Windows и методов объектов формирует скелет приложения ObjectWindows. Как вы уже могли видеть, основная обязанность приложения ObjectWindows состоит в реагировании на сообщения Windows. Это совершенно иная ориентация по сравн ению с традиционными программно-управляемыми приложениями DOS. Данное программирование основывается на сообщениях и управляется событиями. Такой подход можно охарактеризовать как: "Не говорите, пока с вами не заговорят." Он требует большой дисциплинирова нности, но оплатой за это служат многозадачные приложения с прекрасными характеристиками поведения. Реакция на сообщения Для разработки реакции объекта вашего окна на поступающие сообщения Windows нужно написать методы, которые соответствовали бы каждому поступающему сообщению, на которое вы желаете отреагировать. Эти методы называются методами реакции на сообщения. Это сп ециальное соответствие методов сообщениям достигается расширением определений виртуальных методов. Для отметки метода (это всегда процедура) в качестве метода реакции на сообщение, нужно добавить сумму wm_First и имени сообщения (вызывающего индекс динам ического метода) в конец заголовка определения виртуального метода: type TMyWindow=object(TWindow) ... procedure WMRButtonDown(var Msg: TMessage): virtual wm_first + wm_RButtonDown; ... В данном примере вызывается метод WMRButtonDown, когда объект TMyWindow получает сообщение wm_RButtonDown. Имя метода и имя сообщения не обязательно должны совпадать, но это может улучшить восприятие текста вашей программы. ObjectWindows использует это с оглашение в своих типах объекта интерфейса. Msg есть обязательный переменный аргумент записи TMessage, который определяет поля для хранения параметров Word (wParam) и Longint (lParam) из сообщения Windows, наряду с полем для хранения результирующего знач ения (Result). wParam и lParam несут информацию о действии, которое вызвало данное сообщение. Например, сообщение wm_LButtonDown передает координаты x и y выбранной точки в lParam. Один параметр часто хранит два элемента информации. Например, при посылке сообщения wm_L ButtonDown, Word младшего разряда lParam содержит координату x выбранной точки, а старший разряд Word хранит координату y. Для удобства TMessage содержит различные поля для хранения частей старшего и младшего разряда wParam, lParam и Result. wParamLo и wParamHi есть поля Byte, которые хранят младший и старший байты Word wParam. lParamLo и lParamHi это поля Word, которые хран ят младшие и старшие разряды слов Longint LParam. И, наконец, ResultLo и ResultHi это поля Word, которые хранят слова младшего и старшего разряда Longint Result. Обратите внимание на то, что поля Lo и Hi не являются копиями первоначальных данных: если вы измените lParamLo, то вы измените lParam. Более часто вы будете использовать wParam, lParam, lParamLo и lParamHi, все типа Word. Msg передается как переменный аргумент, т.к. очень немногие сообщения Windows требуют реакции, которые ваши методы будут хранить в поле Result записи TMessage. В ваших программах ObjectWindows вы вполне спокойно можете игнорировать сообщения Windows, на которые вы не желаете никакой реакции со стороны ваших объектов. Для этого вы просто не определяете методы реакции на данные сообщения. Эти сообщения будут по у молчанию обрабатываться методом DefWndProc, который ваше окно буде наследовать из TWindowsObject. В проследующих разделах даются примечания по методам реакции ObjectWindows на некоторые самые общие сообщения, связанные с меню и дочерними окнами (например, управление редактированием и линейки прокрутки). Сообщения команд и дочерних окон Когда пользователь делает выбор варианта меню или типа акселератора, Windows генерирует основанное на команде сообщение. Когда пользователь активизирует управление (кнопку или блок списка), то генерируется основанное на дочернем ID сообщение. Результатом большинства этих действий будет сообщение wm_Command. Для того чтобы помочь отсортировать все имеющиеся возможности и избежать очень длинного оператора case, ObjectWindows автоматически реагирует на сообщения wm_Command генерацией другого, более специализированного вызова виртуального метода, основанного на содержании сообщения. Вы определяете методы реакции на сообщения, основанные на командах и дочерних ID, используя расширение определения метода, аналогичное уже описанному. Обработка сообщений команд Для идентификации метода определения метода реакции на командное сообщение меню или акселератора, используется идентификатор, который представляет собой сумму двух констант. Первая константа это определенная ObjectWindowы константа cm_First. Вторая - это ID меню или акселератора, определенная в ресурсе меню или акселератора, и переданная в сообщении wm_Command. Например, возьмем окно, объект TFileWindow, у которого есть три опции, File|New, File|Open и File|Save. ID меню, определенные в файле ресурсов е сть 101, 102 и 103 соответственно. Определение объекта TFileWindow может выглядеть следующим образом: TFileWindow=object(TWindow) ... procedure CMFileNew(var Msg: TMessage); virtual cm_First + 101; procedure CMFileOpen(var Msg: TMessage); virtual cm_First + 102; procedure CMFileSave(var Msg: TMessage); virtual cm_First + 103; end; Для улучшения восприятия кода для каждого ID можно определить константы. Например, можно определить cm_FileNew со значением 101. Тогда заголовок метода будет выглядеть следующим образом: procedure CMFileNew(var Msg: TMessage); virtual cm_First + cm_FileNew; Однако, окно, владеющее меню, не всегда должно определять реакцию на основанное на команде сообщение. Вместо этого метод реакции может быть определен одним из его дочерних окон, включая управления. В действительности, дочернее окно, на котором сосредоточ ен ввод пользователя в момент выбора меню или акселератора имеет первую возможность отреагировать на основанное на команде сообщение определением соответствующего метода реакции. Затем сообщение посылается родительскому окну данного дочернего окна, затем к его родительскому окну и т.д., пока не достигнет окна, которое владеет меню. Это значит, что управление редактированием в окне может непосредственно отреагировать на выбор меню редактирования. Пример данного метода можно увидеть в типе TEditWindow. В любой точке метод реакции может остановить цепочку сообщений установкой параметра поля Result записи TMessage в 1: procedure TMyEDit.CMPaste(var Msg: TMessage); begin DoPaste; Msg.Result:=1; end; Обработка дочерних сообщений Когда пользователь воздействует на управление (дочернее окно), например, нажатием кнопки или вводом управления редактированием, его родительское окно или объект диалога получают сообщение, основанное на дочернем ID. Для того, чтобы родитель реагировал на информационные сообщения, определите метод, идентификатор которого есть сумма двух констант. Первая константа это определенная ObjectWindows константа id_First. Вторая определена ID дочернего окна в ресурсе диалога или в конструкции объекта управления. Например, рассмотрим окно с блоком списка. Определим метод IDListBox для реакции на сообщения, основанные на дочернем ID, генерируемые блоком списка: TListBoxWindow=object(TWindow) ... procedure IDListBox(var Msg: TMessage); virtual id_First + id_ListBox; end; где id_ListBox есть константа, определенная равной ID управления. ID дочерних окон должны быть положительными целыми величинами меньше 4094. В качестве альтернативы обработки родительским окном сообщений, основанных на дочерних ID, можно иметь непосредственную реакцию на управление. Информацию по данному вопросу можно найти в Главе 12, "Объекты управления", раздел "Информационные сообщения уп равления". Как и для случая основанных на командах сообщений, сообщения дочерних окон посылаются в цепь объектов интерфейса, начиная с конкретного участвующего дочернего окна. (В данный момент сообщение носит информационную основу, как вы узнаете из материала Главы 12.) Затем сообщение становится сообщением, основанным на дочернем ID, и передается на его родительское окно. Метод реакции дочернего окна может остановить сообщение на его пути к родителю, установив значение 1 параметра поля Result в записи TMessage. Обработка сообщений по умолчанию Данный радел применим к непосредственно поступающим сообщениям Windows, использующим константу wm_First (т.е. сообщения Windows), а не к сообщениям, основанным на командах, информации или дочерних ID. Типичное приложение Windows принимает значительно больше сообщений Windows, чем автор программы захочет обработать с точки зрения реакции на них. Простое нажатие кнопки на мыши в окне может сгенерировать 10 или более сообщений. Знание о том, что происход ит при восприятии или игнорировании приложением сообщения поможет вам корректно запрограммировать ваши методы реакции на сообщения. Когда Windows посылает сообщение в приложение, она должна знать, воспринимается ли и обрабатывается ли оно приложением. Это происходит потому, что Windows обладает стандартной, встроенной реакцией на проигнорированные сообщения. Например, когда пользоват ель осуществляет ввод в управление редактированием, Windows автоматически реагирует на это отображением новых символов и при необходимости прокруткой. Эти реакции вызываются процедурой объекта интерфейса окна по умолчанию, методом DefWndProc. Когда вы игнорируете сообщение Windows (не определяя для него метод реакции), ObjectWindows автоматически вызывает процедуру объекта окна по умолчанию. Однако, даже если вы и воспринимаете сообщение, определив для него метод реакции, вам может потребоват ься явно вызвать DefWndProc. Например, следующий код будет воспринимать сообщение wm_Char, которое посылается на объект управления редактированием, когда пользователь вводит в управление редактированием. Приведем метод реакции на сообщение wm_Char для нашего объекта, производного о TEdit: procedure TMyEdit.wmChar(var Msg: TMessage); var TheText: array[0..255] of Char; begin MessageBeep(0); end; Этот метод отреагирует на ввод пользователем символов с клавиатуры звуковым сигналом. Однако, определение метода реакции отменяет стандартную реакцию, определяемую процедурой окна по умолчанию. Т.о. вы услышите звуковой сигнал, но ввод пользователя не ок ажет никакого действия на управление редактированием. Вероятно, нам нужно оставить реакцию в виде звукового сигнала и дополнить ее стандартной реакцией. Для этого нужно явно вызвать метод DefWndProc: procedure TMyEdit.wmChar(var Msg: TMessage); var TheText: array[0..255] of Char; begin DefWndProc(Msg); MessageBeep(0); end; В приведенном методе звучит звуковой сигнал и добавляется введенный символ. Вообще, следует вызывать DefWndProc в следующих случаях: - Когда вы хотите в информационных целях воспринять сообщение, но не хотите проводить по нему никаких действий. - Когда вы воспринимаете сообщение для произведения некоторых действий, которые отличаются от стандартной реакции, но желаете сохранить и стандартную реакцию на данное сообщение. - Когда восприятие сообщения может привести к отмене использования соответствующего элемента интерфейса. |