Что делают интерфейсные объекты?
-----------------------------------------------------------------
Все интерфейсные объекты ObjectWindows наследуют из единс- твенного абстрактного объектного типа TWindowsObject, который определяет поведение, общее для окна, диалога и объектов управля- ющих элементов, видоизменяемых и специализируемых в производных объектных типах TDialog, TWindow и TControl.
В качестве общего предка всех интерфейсных объектов методы TWindowsObject обеспечивают единообразный способ поддержки взаи- мосвязи между объектами и элементами экрана (включая создание и уничтожение объектов), обслуживают соотношения "родитель-потомок" между интерфейсными объектами и регистрируют новый классы Windows (см. Главу 10).
Новые типы, производные от TWindowsObject, определяются ред- ко, но он служит основой объектно-ориентированной модели окон. Он определяет большинство наследуемых объектами функциональных воз- можностей, когда вы получаете из TWindow и TDialog новые типы.
Для чего нужны интерфейсные объекты?
-----------------------------------------------------------------
Для чего нужны интерфейсные объекты, если в Windows уже есть окна, диалоговые блоки и управляющие элементы?
Одна из основных трудностей программирования в Windows - это достаточно сложная работа с визуальными элементами. Иногда вам нужно послать в окно сообщение, в другой раз - вызвать функцию API Windows. Для разных типов элементов экрана соглашения будут различными.
ObjectWindows уменьшает большую часть этих сложностей, пре- доставляя объекты, инкапсулирующие элементы экрана и избавляющие вас от необходимости иметь дело непосредственно с Windows. К тому же они обеспечивают более удобный интерфейс.
Допустимость описателя окна
-----------------------------------------------------------------
Обычно в Windows вновь созданный интерфейсный элемент полу- чает (от Windows) сообщение wm_Create, на которое требуется отве- тить инициализацией. Интерфейсный объект ObjectWindows не будет получать сообщений wm_Create, поэтому не забудьте определить для инициализации метод SetupWindow.
Если инициализация интерфейсного объекта требует описателя элемента экрана (например, для вызова функции API Windows), то она не должна вызываться раньше SetupWindow. То есть, перед вызо- вом SetupWindow поле HWindow интерфейсного объекта не является допустимым и использоваться не должно. Если вы хотите вызывать функцию API или нечто требующее описателя окна, не вызывайте их в конструкторе Init. Поместите такие вызовы в метод SetupWindow.
|<----HWindow допустим--->| | | |<-------------------интерфейсный объект допустим---------->| --+-----------------------------------------------------------+--> ^ ^ ^ ^ ^ | | | | | Init вызывает | | | | наследуемый Init | | | | | | Done | SetupWindow вызывает наследуемый | SetupWindow | Done вызывает наследуемый | метод Done | Наследуемый SetupWindow вызывает Create
Рис. 9.1 Когда окно имеет допустимый описатель.
Интерфейсные объекты
Объекты, представляющие окна, диалоговые окна и управляющие элементы, называются интерфейсным объектами. В данной главе об- суждаются общие требования к интерфейсным объектам и их поведе- ние, а также взаимодействие с реальными окнами, диалоговыми бло- ками и выводимыми на экран управляющими элементами.
В этой главе поясняется также взаимосвязь между различными интерфейсными объектами приложения, а также механизм передачи со- общений Windows.
Примечание: Приводимый здесь материал относится к ок- нам, диалоговым блокам и управляющим элементам.
Итерация дочерних окон
-----------------------------------------------------------------
Иногда желательно написать методы, для реализации функции выполняющие итерации по каждому дочернему окну данного окна. Нап- ример, можно проверить в окне все кнопки с независимой фиксацией. В этом случае используйте метод TWindowsObject.ForEach:
procedure TMyWindow.CheckAllBoxes;
procedure CheckTheBox(ABox: PWindowsObject); far; begin PCheckBox(ABox)^.Check; end;
begin ForEach(@CheckTheBox); end;
Использование метода ForEach (и аналогичных методов FirstThat и LastThat) похоже на применение методов с аналогичными названиями в TCollection. Хотя ObjectWindows не использует наборы для обслуживания дочерних окон, методы итерации работают анало- гично.
Поиск определенного дочернего окна
-----------------------------------------------------------------
Иногда желательно иметь методы, выполняющие итерацию по списку окон в поиске конкретного окна. Например, в окне с нес- колькими кнопками вам может потребоваться найти первую установ- ленную кнопку с независимой фиксацией. В этом случае метод TWindowsObject.FirstThat можно записать так:
function TMyWindow.GetFirstChecked: PWindowsObject;
function IsThisOneChecked(ABox: PWindowsObject): Boolean; far; begin IsThisOneChecked := (ABox^.GetCheck = bf_Checked); end;
begin GetFirstChecked := FirstThat(@IsThisOneChecked); end;
Построение дочерних окон
-----------------------------------------------------------------
Как и в случае интерфейсных объектов, объекты дочерних окон создаются в два этапа (построение объекта и создание элемента эк- рана). Объекты порожденного окна следует строить с помощью конс- труктора Init порождающего окна. Например, объект окна, наследую- щий из TWindow и содержащий командную кнопку должен иметь пример- но следующий вид:
constructor TMyWindow.Init(AParent: PWindowsObject; ATitle: PChar); begin inherited Init(AParent, ATitle); TheButton := New(PButton, Init(@Self, id_TheButton, 'Текст кнопки', 20, 10, 100, 25, True)); end;
Обратите внимание на использование указателя Self для связи дочернего объекта (TheButton) с порождающим (экземпляром TMyWindow). Конструктор интерфейсного объекта автоматически до- бавляет к своему списку дочерних окон новые объекты.
Создание дочерних элементов экрана
-----------------------------------------------------------------
Когда построен список дочерних элементов интерфейсного объ- екта, создание элементов экрана для дочерних окон выполняется ав- томатически. Создание родительского окна (через вызов MakeWindow) включает в себя вызов метода SetupWindow порождающего окна. Одним из наследуемых действий SetupWindow является вызов для каждого из окон в списке дочерних окон методов SetupWindow.
Примечание: Автоматическое создание можно запретить. См. ниже раздел "Запрещение автоматического создания".
При создании нового производного объектного типа нужно пом- нить об инициализации объекта в SetupWindow после вызова наследу- емого метода SetupWindow, например:
procedure TMyCheckBox.SetupWindow; begin inherited SetupWindow; { сначала по умолчанию } . . . { выполнить инициализацию объекта } end;
Создание интерфейсных объектов
-----------------------------------------------------------------
Задание полного интерфейсного объекта с соответствующими ин- терфейсными элементами требует двух шагов:
* Построения объекта.
* Создания элемента экрана.
Первым шагом является вызов конструктора Init, который стро- ит интерфейсный объект и устанавливает его атрибуты, такие как стиль и меню.
Второй шаг заключается в вызове метода создания окна объекта приложения, MakeWindow, связывающего интерфейсный объект с новым элементом экрана. Эта связь поддерживается полем HWindow экрана (описателем окна).
Примечание: Об описателях окон рассказывается в Главе 7 "Иерархия ObjectWindows".
MakeWindow вызывает метод Create объекта, который всегда со- общает Windows о необходимости создания элемента на экране. Create создает также метод SetupWindow, который инициализирует интерфейсный объект путем создания, например, дочерних окон.
Список дочерних окон
-----------------------------------------------------------------
Когда вы строите объект дочернего окна, то можете в качестве параметра конструктора Init можете задать порождающее окно (при- мер вы можете найти в Главе 10). Объект дочернего окна отслежива- ет свое порождающее окно через указатель на его поле Parent. Он отслеживает также объекты его дочерних окон, сохраненные в поле ChildList. Дочернее окно, на которое в данный момент установлен ChildList, является последним созданным дочерним окном.
Связь порождающего и дочернего объектов
-----------------------------------------------------------------
В приложении Windows совместная работа элементов экрана (окон, диалоговых блоков и управляющих элементов) обеспечивается с помощью связей "родитель-потомок". Порождающие окна управляют своими дочерними окнами, а Windows отслеживает эти связи. ObjectWindows поддерживает параллельный набор связей между соот- ветствующими интерфейсными объектами.
Дочернее окно - это элемент экрана (оно не обязано быть ок- ном), который управляется другим элементом экрана. Например, бло- ки списка обслуживаются окном или диалоговым блоком, в котором они выводятся. Они выводятся на экран только при выводе их порож- дающих окон. Диалоговые блоки, в свою очередь, являются дочерними окнами, управляемыми порождающими их окнами.
Когда вы перемещаете или закрываете порождающее окно, дочер- ние окна автоматически закрываются, и в некоторых случаях переме- щаются в нем. Конечным предком всех дочерних интерфейсных элемен- тов является основное окно, хотя вы можете иметь окна и диалоги без порождающих окон.
Порождающими окнами могут быть только диалоговые блоки и ок- на, но не порождающие элементы. Дочерним окном может быть любой интерфейсный элемент.
Уничтожение дочерних окон
-----------------------------------------------------------------
Вызов деструктора порождающего окна приводит к вызову дест- рукторов всех его дочерних окон, так что вашей программе не нужно явно вызывать деструкторы дочернего окна. Это же справедливо для метода CanClose, который возвращает True только после вызова CanClose для всех его дочерних окон.
Уничтожение интерфейсных объектов
-----------------------------------------------------------------
Как и в случае создания интерфейсный объектов, их уничтоже- ние предполагает выполнение двух шагов:
* Уничтожение визуального интерфейсного элемента (Destroy).
* Уничтожение интерфейсного объекта (Dispose).
Уничтожением экранного элемента занимается метод Destroy ин- терфейсного объекта, который делает следующее: он вызывает функ- цию Windows DestroyWindow, чтобы избавиться от элемента экрана, и устанавливает поле HWindow объекта в 0. Таким образом, проверив указатель, вы можете сообщить, связан ли еще объект с элементом экрана.
Уничтожить элемент экрана вы можете без уничтожения объекта (если хотите создавать и выводить его снова).
Примечание: Уничтожение самого окна обычно не требует- ся. Это делается автоматически при закрытии окна.
Когда пользователь закрывает на экране окно, ObjectWindows обнаруживает, что данный элемент экрана уничтожен, устанавливает поле HWindow соответствующего объекта в 0 и вызывает деструктор объекта Done.
Видимость на экране
-----------------------------------------------------------------
Создание интерфейсного объекта и соответствующего визуально- го элемента не обязательно означает, что вы что-то видите на эк- ране. Когда метод Create указывает Windows на создание элемента экрана, Windows проверяет, включает ли стиль окна ws_Visible. Ес- ли да, то интерфейсный элемент будет выводиться. В противном слу- чае он будет скрытым.
ws_Visible и другие стили окна обычно устанавливаются или сбрасываются конструктором Init в поле Attr.Style объекта.
В любой момент после создания элемента экрана вы можете вы- вести или скрыть его, вызвав метод Show интерфейсного объекта.
Запрещение автоматического создания
-----------------------------------------------------------------
Чтобы явно исключить дочернее окно из механизма автоматичес- кого создания и вывода, вызовите после его создания метод DisableAutoCreate. Чтобы явно добавить в механизм создания и вы- вода дочернее окно (такое как диалоговый блок, который при нор- мальном выводе будет исключен), вызовите после построения его ме- тод EnableAutoCreate.
Атрибуты порожденного окна
-----------------------------------------------------------------
Конструктор TWindowType отвечает за построение его дочерних объектов, таких как всплывающие окна и блоки списка. Тип порож- денного окна, в свою очередь, может устанавливать атрибуты в сво- ем собственном конструкторе Init:
constructor TChilwWindowType.Init(AParent: PWindowsObject; ATitle: PChar); begin inherited Init(AParent, ATitle); with Attr do begin Style := Style or ws_PopupWindow or ws_Caption; X := 100; Y := 100; W := 300; H := 300; end; end;
В качестве альтернативы вы можете не определять потомка типа окна, а сначала построить объект окна, а затем переустано- вить его атрибуты (все это в конструкторе Init порождающего ок- на):
constructor TWindowType.Init(AParent: PWindowsObject; ATitle: PChar); begin inherited Init(AParent, ATitle); Attr.Menu := LoadMenu(HInstance, 'TheMenu'); AChildWindow := New(PChildWindowType, Init(@Self, 'Заголовок дочернего окна')); with Attr do begin Style := Style or ws_PopupWindow or ws_Caption; X := 100; Y := 100; W := 300; H := 300; end; . . . end;
Что такое объект прокрутки?
-----------------------------------------------------------------
TScroller содержит значения, определяющие, насколько должно прокручивается окно. Эти значения записываются в полях XUnit, YUnit, XLine, YLine, XRange, YRange, XPage и YPage объекта TScroller. Поля, начинающиеся с буквы X, представляют горизон- тальные значения, а начинающиеся с буквы Y - вертикальные.
Единицы прокрутки
Единица прокрутки определяет минимальную величину прокрутки. Она задается в наименьших единицах устройства (обычно в элементах изображения, но может зависеть от текущего режима отображения), на которые вы можете выполнять прокрутку в горизонтальном или вертикальном направлении. Это значение обычно основывается на ви- де выводимой на экран информации.
Например, если вы выводите текст с шириной символа 8 элемен- тов изображения и высотой 15, то в качестве значений XUnit и YUnit полезно задать, соответственно, 8 и 15.
Строки, страницы и диапазон
Другие атрибуты прокрутки - строка, страница и диапазон - выражаются в единицах прокрутки. Значения Line (строка) и Page (страница) - это число единиц, на которые выполняется прокрутка в ответ на запрос пользователя. Запрос может иметь форму щелчка кнопкой "мыши" на концах полосы прокрутки (построчная прокрутка). Щелчок "мышью" в самой полосе прокрутки (но не на маркере полосы прокрутки) позволяет выполнять постраничную прокрутку. Атрибуты диапазона (XRange, YRange) представляют общее число единиц, на которое можно выполнять прокрутку. Обычно этот диапазон определя- ется на основе размера редактируемого документа.
Типичный объект прокрутки
В качестве примера рассмотрим текстовое окно редактирования. Если вы хотите вывести на экран текстовый файл, имеющий 400 строк текста с границей 80 символов и 50 строками на странице, то можно выбрать следующие значения:
Типичные значения для окна редактирования Таблица 10.4 +-----------------+-------------+-------------------------------+ | Поле | Значение | Смысл | +-----------------+-------------+-------------------------------| | XUnit | 8 | ширина символа | | YUnit | 15 | высота символа | | XLine, YLine | 1 | 1 единица на строку | | XPage | 40 | 40 символов по горизонтали на| | | | страницу | | YPage | 50 | 50 символов по вертикали на| | | | страницу | | XRange | 80 | максимальный горизонтальный| | | | диапазон | | YRange | 400 | максимальный вертикальный ди-| | | | апазон | +-----------------+-------------+-------------------------------+
Объект TScroller с данными значениями позволяет выполнять построчную или постраничную прокрутку. С помощью полос прокрутки или автоматической прокрутки выполняется просмотр всего файла.
Значения по умолчанию
По умолчанию XLine и YLine имеют значение 1, так что без яв- ной необходимости устанавливать их в другие значения не нужно. Для установки значений прокрутки на страницу также существует ис- пользуемая по умолчанию схема, согласно которой страница прокрут- ки будет соответствовать текущей высоте или ширине области клиен- та окна (в зависимости от направлений прокрутки). Если вы не хо- тите переопределить данный механизм, переустанавливать эти значе- ния не требуется.
Что такое объекты окон?
-----------------------------------------------------------------
Термин "объект окна" относится к любому интерфейсному объек- ту, представляющему окно, а в Windows это почти все, что выводит- ся на экран. В качестве шаблона определения большей части фунда- ментального поведения основного окна и любого всплывающего окна приложения ObjectWindows использует тип TWindow.
Где найти объекты окон
-----------------------------------------------------------------
Каждое приложение Windows имеет основное окно. Это окно мо- жет выводиться в виде пиктограммы или не выводиться снова (скры- тое окно), но существует всегда. Приложения ObjectWindows не яв- ляются исключением: они должны иметь основное окно, представлен- ное оконным объектом.
Примером минимальной программы ObjectWindows ("скелета" программы) является TestApp в Главе 8. Основное окно программы ObjectWindows является обычно экземпляром TWindow или определяе- мого в программе наследующего типа. Многие приложения имеют дру- гие окна, которые обычно являются дочерними окнами основного ок- на. Эти дополнительные окна также являются экземплярами TWindow или одного из его потомков.
Например, графическая программа может определять для своего основного окна тип TPaintWindow, а для окна, показывающего графи- ческий рисунок - тип TZoomWindow. В этом случае TPaintWindow и TZoomWindow являются наследниками TWindow.
Объекты окон
Объекты окон (или оконные объекты) - это интерфейсные объек- ты, предназначенные для облегчения работы с окнами. В данной гла- ве поясняется, как создавать и заполнять окна приложения. Это предусматривает следующие задачи:
* Инициализацию оконных объектов.
* Установку атрибутов создания.
* Создание экранных элементов окна.
* Установку атрибутов регистрации.
* Использование специализированных окон.
* Прокрутку окон.
Инициализация объектов окон
-----------------------------------------------------------------
Оконные объекты представляют элементы окна, связанные через описатели, сохраненные в наследуемом из TWindowsObject поле HWindow. Так как объект окна имеет две части, его создание требу- ет двух шагов: инициализации объекта и создания визуального эле- мента.
Инициализация окна - это процесс создания оконного объекта ObjectWindows путем вызова конструктора Init:
Window1 := New(PWindow,Init(nil, 'Заголовок окна 1')); Window2 := New(PNewWindowType,Init(nil,'Заголовок окна 2'));
Init создает новый оконный объект и устанавливает поле Title в Attr в передаваемый аргумент PChar. Первый аргумент вызова Init - это оконный объект порождающего окна. Если окно является основ- ным окном (не имеющим порождающего окна), то это nil.
Использование файловых окон
-----------------------------------------------------------------
Файловое окно - это окно редактирования с дополнительными возможностями, позволяющими считывать и записывать данные в файл. TFileWindow.Init воспринимает в качестве аргумента заголовок окна и устанавливает поле FileDialog таким образом, чтобы оно указыва- ло на файловый диалоговый объект.
Для работы с файлами TFileWindow имеет четыре метода. Методы Open, Save и SaveAs для вывода пользователю подсказки с именем файла используют поле TFileWindow.FileDialog (см. Главу 11). Ме- тод New дает пользователю возможность отмены, если редактирование нового файла приведет к потере изменений текущего текста. Чтобы дать пользователю возможность доступа к этим методам, создайте свое меню со следующими идентификаторами меню:
Методы и идентификаторы меню файлового окна Таблица 10.3 +---------------------+-----------------------------------------+ | Метод | Идентификатор меню для вызова | +---------------------+-----------------------------------------| | New | cm_FileNew | | Open | cm_FileOpen | | Save | cm_FileSave | | SaveAs | cm_FileSaveAs | +---------------------+-----------------------------------------+
Вы можете использовать файловые окна без модификации как простые автономные текстовые редакторы. Однако, иногда желательно создать производные от TFileWindow типы и обеспечить дополнитель- ные функциональные возможности. Например, можно предусмотреть средство поиска. Помните, что вы все равно будете иметь доступ к управляющему элементу редактирования TFileWindow.Editor.
Использование окон редактирования
-----------------------------------------------------------------
Окно редактирования - это окно с управляющим элементом ре- дактирования, заполняющим его область клиента. TEditWindow.Init инициализирует поле Editor окна редактирования, чтобы оно указы- вало на управляющий элемент объекта редактирования. TEditWindow.SetupWindow устанавливает размеры управляющего эле- мента редактирования в соответствии с областью клиента окна и создает экранный управляющий элемент редактирования.
Метод WMSize обеспечивает изменение размера управляющего элемента редактирования при изменении размера его окна. Метод WMSetFocus обеспечивает, что управляющий элемент редактирования получает фокус ввода при получении окном сообщения wm_SetFocus.
Показанная ниже программа EditWindowTester использует окно редактирования, чтобы пользователь мог редактировать текст для простой (нефункциональной) электронной почты.
+-----------------------------------------------------------+-+-+ |#=#XXXXXXXXXXXXXXXXXXXXEdit Window TesterXXXXXXXXXXXXXXXXXX|^|v| +-----------------------------------------------------------+-+-| | Edit Text | +---------------------------------------------------------------| | Кого это может касаться: | | | | Я хотел бы зарегистрировать жалобу по поводу попугая, которого| | я купил в вашем магазине полгода назад. Он умер. | | +-----------------------------------+ | | Брюс |#=#@@@@@@Передано сообщение@@@@@@@@| | | +-----------------------------------| | | | | | | | 6 строк послано | | | | +------------+ | | | | |####OK######| | | | | +------------+ | | | +-----------------------------------+ | | | +---------------------------------------------------------------+
Рис. 10.2 Окно редактирования.
program EditWindowTester; {$R EWNDTEST.RES} uses ODialogs, WinTypes, WinProcs, Strings, OStdWnds; const cm_sendText = 399; type TestApplication = object(TApplication) procedure InitMainWindow; virtual; end;
PMyEditWindow = ^MyEditWindow; MyEditWindow = object(TEditWindow) constructor Init(AParent: PWindowsObject; ATitle: PChar); procedure CMSendText(var Msg: TMessage); virtual cm_First + cm_SendText; end;
constructor MyEditWindow.Init(AParent: PWindowsObject; Atitle: PChar); begin inherited Init(AParent, ATitle); Attr.Menu := LoadMenu(HInstance, MakeIntResource(102)); end
procedure MyEditWindows.CMSendText(var Msg: TMessage); var Lines: Integer; TestString: string[3]; Text: array[020] of Char; begin Lines := Editor^.GetNumLines; Str(Lines, TextString); StrCat(Text, ' строк послано'); MessageBox(HWindow, @Text, 'Передано сообщение', mb_Ok); end;
procedure TestApplication.InitMainWindow; begin MainWindow := New(PMyEditWindow, Init(nil, 'Окно редактирования - попробуйте набор и редактирование')); end;
var TestApp: TestApplication; begin TestApp.Init('EditWindowTester'); TestApp.Run; TestApp.Done; end.
Использование специализированных окон
-----------------------------------------------------------------
ObjectWindows предусматривает два потомка TWindow, являющих- ся специализированными окнами для редактирования текста. Объект- ный тип TEditWindow обеспечивает простой текстовый редактор, не обладающий возможностями чтения из файла или записи в него. Тип TFileWindow, наследующий из TEditWindow, обеспечивает текстовый редактор с возможностями чтения/записи файлов.
Эти объекты можно использовать непосредственно как стандарт- ные компоненты ваших приложений. Вы можете также построить произ- водные от них типы и создать свои собственные специализированные редакторы. Программы или модули, использующие окна редактирования или файловые окна, должны включать в свой оператор uses модуль OStdWnds.
Используемые по умолчанию атрибуты окна
-----------------------------------------------------------------
По умолчанию TWindow.Init устанавливает Attr.Style в ws_Visible. Если окно является основным окном приложения, то Style равно ws_OverlappedWindow or ws_Visible.
Menu по умолчанию устанавливается в 0. Это означает, что ме- ню не определено.
X, Y, W и H устанавливаются в cw_UseDefault, что дает в ре- зультате перекрывающееся окно удовлетворительного размера. Когда создается окно, не являющееся основным, значения X, Y, W и H вы обычно устанавливаете сами.
Используемые по умолчанию атрибуты регистрации
-----------------------------------------------------------------
Тип TWindow определяет класс окна 'TurboWindow' с пустой пиктограммой, курсором-стрелкой и стандартным цветом окна. Ис- пользуемый по умолчанию класс ObjectWindows (TurboWindow) имеет следующие атрибуты:
* стиль: cs_HRedraw or cs_VRedraw (повторное отображение после каждого изменения размера);
* пиктограмма: idi_Application (пустой прямоугольник);
* курсор: idc_Arrow (стандартная стрелка Windows);
* фоновый цвет: HBrush(color_Window + 1);
* меню по умолчанию: nil.
Изменение имени класса
-----------------------------------------------------------------
GetClassName - это функция, которая возвращает имя (PChar) класса окна. TWindow.GetClassName возвращает 'TurboWindow', имя используемого по умолчанию класса окна. TWindow.GetClassName возвращает 'TurboWindow' - имя используемого по умолчанию класса окна:
function TWindow.GetClassName: PChar; begin GetClassName := 'TurboWindow'; end;
Чтобы определить тип объекта окна с именем IBeamWindow, ко- торый использует вместо стандартной стрелки I-образный курсор, переопределите наследуемый метод следующим образом:
function TBeamWindow.GetClassName: PChar; begin GetClassName := 'IBeamWindow'; end;
Примечание: Имя класса не обязательно должно соответс- твовать имени объектного типа.
Имя класса должно быть уникальным.
Изменение позиции прокрутки
-----------------------------------------------------------------
Windows с помощью методов ScrollTo и ScrollBy может выпол- нять принудительную прокрутку. Каждый из них воспринимает два це- лочисленных аргумента в терминах горизонтальных и вертикальных единиц прокрутки. Например, если нужно переместиться к левому верхнему углу картинки, то используется ScrollTo:
Scroller^.ScrollTo(0, 0);
Приведем другой пример. Если картинка имеет длину 400 единиц в вертикальном направлении, то позицию прокрутки можно перемес- тить к середине картинки следующим образом:
Scroller^.ScrollTo(0, 200);
Метод ScrollBy может перемещать позицию просмотра на задан- ное число единиц вверх, вниз, влево или вправо. Отрицательные значения осуществляют сдвиг к левому верхнему углу, а положитель- ные - к правому нижнему. Если нужно сместиться на 10 единиц впра- во и на 20 единиц вниз, то это можно сделать командой:
Scroller^.ScrollBy(10, 20);
Классы окон
-----------------------------------------------------------------
С каждым типом оконного объекта связан список атрибутов ре- гистрации, которые называются классом окна. Список атрибутов ре- гистрации во многом напоминает список атрибутов создания, запи- санных в поле записи Attr объекта окна. Однако, атрибуты регист- рации сохраняются в записи с именем TWndClass, который определя- ется и поддерживается Windows.
Процесс связи класса окна с типом оконного объекта называет- ся регистрацией класса окна. ObjectWindows автоматизирует процесс регистрации. Таким образом, если вы хотите изменить какую-либо из используемых по умолчанию характеристик объекта, то можете не беспокоиться о классе регистрации окна.
Поля записи TWndClass и их типы перечислены в следующей таб- лице:
Атрибуты регистрации окна Таблица 10.2 +-------------------------+-------------------+-----------------+ | Характеристика | Поле | Тип | +-------------------------+-------------------+-----------------| | стиль класса | style | Word | | пиктограмма | hIcon | HIcon | | курсор | hCursor | HCursor | | фоновый цвет | hbrBackground | HBrush | | меню по умолчанию | lpszMenuName | PChar | +-------------------------+-------------------+-----------------+
Поля стиля класса
Это поле стиля отличается от атрибута стиля окна (ws_), за- даваемого при инициализации окна, поскольку задает поведение, присущее операциям окна (в отличие от их визуального представле- ния). Это поле может заполняться комбинацией констант стиля (cs_).
Например, cs_HRedraw приводит к повторному отображению окна при изменении его размера по горизонтали; cs_DoubleClk позволяет окну получать сообщения о двойном нажатии кнопки "мыши"; cs_NoClose предотвращает выбор параметра Close меню Control, а cs_ParentDC дает окну контекст дисплея порождающего окна.
Поле пиктограммы
Это поле содержит описатель пиктограммы, которое использует- ся для представления окна в его минимизированном состоянии. Обыч- но для представления основного окна программы выбирается ресурс пиктограммы.
Поле курсора
Поле hCursor содержит описатель курсора, который использует- ся для представления указателя "мыши" при позиционировании его в окне.
Поле фонового цвета
Это поле задает фоновый цвет окна. Для большинства приложе- ний используется стандартный назначаемый по умолчанию цвет окна, который может устанавливаться пользователем в управляющей панели. Однако вы можете путем установки этого поля в описатель физичес- кой кисти подставить конкретный цвет. Либо вы можете установить любое из значений цветов Windows, такие как color_ActiveCaption. К любому значению цвета всегда добавляйте 1.
Поле используемого по умолчанию меню
Это поле указывает на имя ресурса меню, которое служит ис- пользуемым по умолчанию меню для данного класса. Например, если вы определите тип EditWindow, который всегда имеет стандартное меню редактирования, то можете задать здесь это меню. Это устра- нит необходимость задания меню в методе Init. Если данный ресурс меню имеет идентификатор 'MyMenu', вы можете установить это поле следующим образом:
AWndClass.IpszMenuName := 'MyMenu';
Модификация единиц прокрутки и диапазона
-----------------------------------------------------------------
В приведенных выше примерах мы предполагали, что к моменту построения TScroller известны значения единиц и диапазонов. Во многих случаях эта информация неизвестна или может меняться при изменении размеров отображаемой информации. В этом случае может потребоваться установить или изменить значения диапазона (а может быть и единиц) позднее. Если значения заранее неизвестны, то их можно задать как 0 в конструкторе TScroller.
Изменение диапазона
Метод SetRange воспринимает два целочисленных аргумента - число горизонтальных и вертикальных единиц, которые определяют общий диапазон прокрутки. Метод SetRange должен использоваться при изменении размеров картинки. Например, при подготовке изобра- жения картинки шириной 1 0 единиц и высотой 300, данная команда установит диапазон прокрутки надлежащим образом:
Scroller^.setRange(100, 300);
Изменение единиц прокрутки
Если при инициализации объекта TScroller единицы неизвестны, то их значения могут быть установлены непосредственно перед прок- руткой. Например, они могут быть установлены методом окна SetupWindow:
procedure ScrollWindow.SetupWindow; begin TWindow.SetupWindow; Scroller^.XUnit:=10; Scroller^.YUnit:=20; end;
Окна, которые не являются окнами
-----------------------------------------------------------------
TWindow имеет три типа-потомка: TMDIWindow, TControl и TEditWindow, так что все они также являются оконными объектами, хотя на самом деле это не окна в полном смысле слова. Типы MDI используются в приложениях ObjectWindows, которые соответствуют стандарту многодокументального интерфейса Windows. Об MDI и этих типах рассказывается в Главе 14. TControl определяет управляющие элементы, такие как командные кнопки и блоки списков (см. Главу 12). Чаще всего новые оконные типы являются производными от TWindow.
Эта глава охватывает типы TWindow и TEditWindow и содержит примеры регистрации новых классов окон.
Определение новых атрибутов регистрации
-----------------------------------------------------------------
Чтобы отклониться от стандартных характеристик, вы должны заполнить поля записи TWndClass с различными данными в методе GetWindowClass.
GetWindowClass воспринимает в качестве аргумента-переменной запись TWndClass и заполняет ее поля новыми атрибутами регистра- ции. Когда вы определяете новый метод GetWindowClass, вам следует всегда сначала для установки значений по умолчанию вызывать нас- ледуемый метод TWindow.GetWindowClass, а затем устанавливать по- ля, которые вы хотите изменить.
Например, в поле hCursor хранится описатель ресурса курсора. Для IBeamWindow определяется метод GetWindowClass:
procedure IBeamWindow.GetWindowClass(var AWndClass: TWndClass); begin inherited GetWindowClass(AWndClass); AWndClass.hCursor := LoadCursor(0, idc_IBeam); end;
Примечание: idc_Beam - это константа, представляющая один из курсоров Windows.
Кроме окон, диалоговым окнам (не диалоговым блокам) необхо- димо регистрировать классы окна (см. Главу 11). Диалоговым блокам и управляющим элементам классы окон не требуются.
Оптимизация методов Paint для прокрутки
-----------------------------------------------------------------
В приведенном выше примере рисуется 50 прямоугольников, но не делается даже попытки определить, все ли прямоугольники видны в области клиента окна. Это может привести к излишним усилиям на дорисовку невидимых изображений. Для оптимизации рисования в окне методом Paint можно использовать метод TScroller.IsVisibleRect.
Приведенный ниже метод ScrollWindow.Paint использует IsVisibleRect для определения, нужно ли вызывать функцию Windows Rectange. Rectange воспринимает аргументы в единицах устройства, а VisibleRect в единицах прокрутки. С этой целью вершина прямоу- гольника X1 Y1 и ширина прямоугольника (X2-X1) и его высота (Y2-Y1) должны быть разделены на соответствующее число единиц до вызова IsVisibleRect:
procedure TScrollWindow.Paint(PaintDC: HDC; var PaintInfo: TPaintStruct); var X1, Y1, X2, Y2, I: Integer; begin for I:=0 to 49 do begin X1 := 10 + I * 8; Y1 := 30 + I * 5; X2 := X1 + X1; Y2 := X1 + Y1 * 2; if Scroller^.IsVisibleRect(X1 div 8, Y1 div 15, (X2-X1) div 8, (Y2-Y1) div 15) then Rectangle(PaintDC, X1, Y1, X2, Y2); end; end;
Отслеживание полос прокрутки
-----------------------------------------------------------------
В дополнение к автоматической прокрутке, приведенный выше пример программы будет отслеживать запросы на прокрутку, сдвигая при нажатой кнопке "мыши" маркер полосы прокрутки. Другими слова- ми картинка сдвигается уже при нажатой кнопке. Эта особенность дает действительную обратную связь, и пользователь может сдвигать нужную часть изображения не отпуская кнопку "мыши".
Однако, в некоторых случаях этот эффект нежелателен. Напри- мер, если вы просматриваете большой текстовый файл, такое отсле- живание может замедлить работу, поскольку возникает необходимость постоянно считывать информацию с диска и отображать порцию текста для каждого движения "мыши". В такой ситуации лучше отменить этот эффект:
Scroller^.TrackMode:=False;
Теперь никакой прокрутки не происходит до момента отпускания кнопки на мыши, и в области клиента будет лишь однократно показа- на нужная часть картинки.
Переопределение используемых по умолчанию атрибутов
-----------------------------------------------------------------
При создании новых оконных типов, производных от TWindow, вы обычно определяете новый конструктор Init (особенно если хотите получить атрибут создания, отличных от используемого по умолча- нию). Если вы хотите переопределить Init, то можете заново задать атрибуты объекта, непосредственно изменяя поле Attr после вызова Init.
Если вы переопределили Init, убедитесь, что первое, что он делает - это вызов наследуемого метода TWindow.Init, устанавлива- ющего используемые по умолчанию атрибуты. Затем вы можете изме- нить по своему выбору любой из атрибутов. Например, типичное окно может определять конструктор Init, который устанавливает атрибут Menu:
constructor TWindowType.Init(AParent: PWindowsObject; ATitle: PChar); begin inherited Init(AParent, ATitle); Attr.Menu := LoadMenu(HInstance, 'TheMenu'); AChildWindow := New(PChildWindowType, Init(@Self, 'Заголовок дочернего окна')); List1 := New(PListBox, Init(@Self, id_ListBox, 201, 20, 20, 180, 80)); . . . end;
Пример прокрутки
-----------------------------------------------------------------
Scroll - это полное приложение с графическим выводом, допус- кающим прокрутку. Показанная ниже программа рисует последователь- ность прямоугольников, затем увеличивает их размер, так что вся картинка не умещается в область клиента окна, отображенного на обычном экране VGA. С помощью полос прокрутки вы можете просмат- ривать различные части рисунка или автоматически прокручивать картинку, удерживая нажатой левую кнопку "мыши" и перемещая ее из области клиента.
program Scroll;
uses Strings, WinTypes, WinProcs, OWindows;
type TScrollApp = object(TApplication) procedure InitMainWindow; virtual; end;
PScrollWindow = ^TScrollWindow; TScrollWindow = object(TWindow) constructor Init(ATitle: PChar); procedure Paint(PaintDC: HDC; var PaintInfo: TPaintStruct); virtual; end;
procedure TScrollApp.InitMainWindow; begin MainWindow := New(PScrollWindow, Init('Boxes')); end;
constructor TScrollWindow.Init(ATitle: PChar); begin inherited Init(nil, ATitle); Attr.Style := Attr.Style or ws_VScroll or ws_HScroll; Scroller := New(PScroller, Init(@Self, 8, 15, 80, 60)); end;
procedure TScrollWindow.PAint(PaintDC: HDC; var PaintInfo: TPaintStruct); var X1, Y1, I: Integer; begin for I := 0 to 49 do begin X1 := 10 + I*8; Y1 := 30 + I*5; Rectangle(PaintDC, X1, Y1, X1 + X1, X1 + Y1 * 2); end; end;
var ScrollApp: TScrollApp;
begin ScrollApp.Init('ScrollApp'); ScrollApp.Run; ScrollApp.Done: end.
Прокрутка содержимого окон
-----------------------------------------------------------------
В большинстве случаев для прокрутки текущей области просмот- ра пользователи используют полосы прокрутки вдоль края окна. В отличие от стандартных управляющих элементов типа полос прокрутки полосы прокрутки окна являются частью самого окна.
ObjectWindows управляет прокруткой окна, предоставляя каждо- му оконному объекту поле Scroller, которое может указывать на объект TScroller. Объект прокрутки TScroller обеспечивает автома- тизированный способ прокрутки в окнах текста и графики. Кроме то- го, TScroller может прокручивать окна, когда пользователь переме- щает "мышь" за область клиента окна (это называется автоматичес- кой прокруткой и действует даже для окон, которые не имеют полос прокрутки).
Регистрация нового класса
-----------------------------------------------------------------
Чтобы изменить атрибут регистрации, такой как курсор или пиктограмму, вам нужно написать два метода - GetClassName и GetWindowClass - и определить новый класс окна. Каждый раз, когда вы изменяете атрибуты регистрации, вам нужно изменить имя класса. Если класс регистрации с данным именем уже зарегистрирован в Windows, другие классы с тем же именем класса регистрироваться не будут - они получат атрибуты уже зарегистрированного класса.
Создание элементов окна
-----------------------------------------------------------------
После построения оконного объекта вам нужно сообщить Windows, что требуется создать связанные с объектом элементы эк- рана. Это делается с помощью вызова MakeWindow объекта приложения и передачи ему в качестве параметра указателя на объект окна.
if Application^.MakeWindow(AWindow) <> nil then { успешное создание } else { неуспешное создание }
MakeWindow вызывает два важных метода: ValidWindow и Create. ValidWindow проверяет успешность построение объекта окна, прове- ряя поле Status. Если по каким-либо причинам конструктор завер- шился неуспешно, то MakeWindow возвращает nil. При успешном вы- полнении конструктора MakeWindow переходит на метод Create окон- ного объекта.
Create - это метод, который фактически сообщает Windows о создании элемента экрана. Если Create завершается неудачно, MakeWindow возвращает nil. В противном случае возвращается указа- тель на оконный объект. Для работы с элементом экрана Create так- же устанавливает поле HWindow.
Хотя этот метод фактически создает элемент экрана, вы обычно не можете вызывать Create явно. Основное окно приложения автома- тически создается при запуске программы методом TApplication.InitInstance.
Все прочие окна приложения являются дочерними окнами, прямо или косвенно порождаемыми основным окном, а дочерние окна созда- ются обычно в методе SetupWindow или в его порождающих оконных объектах, либо с помощью MakeWindow динамически на этапе выполне- ния.
Примечание: Дочерние окна и SetupWindow описываются в Главе 9 "Интерфейсный объекты".
В общем случае порождающие окна обычно вызывают для своих дочерних окон методы Init и MakeWindow. Атрибуты оконного объекта обычно устанавливаются их методами объекта порождающего окна. Поскольку основное окно приложения не имеет порождающего окна, объект приложения строит и создает его при запуске приложения.
Установка атрибутов создания
-----------------------------------------------------------------
Типичное приложение Windows имеет много различных типов окон: перекрывающиеся или всплывающие, окна с рамкой, прокручива- емые окна, окна с заголовком и др. Эти атрибуты стилей, а также заголовок и меню окна задаются при инициализации оконного объекта и используются при создании элементов окна.
Атрибуты создания оконного объекта, такие как стиль, заголо- вок и меню, записываются в поле Attr объекта - записи типа TWindowAttr. TWindowAttr содержит следующие поля:
Атрибуты создания окна Таблица 10.1 +------------+--------------+-----------------------------------+ | Поле | Тип | Использование | +------------+--------------+-----------------------------------| | Title | PChar | Строка заголовка. | | | | | | Style | Longint | Комбинированная константа стиля. | | | | | | Menu | HMenu | Описатель ресурса меню. | | | | | | X | Integer | Горизонтальная координата экрана| | | | верхнего левого угла окна. | | | | | | Y | Integer | Вертикальная координата экрана| | | | верхнего левого угла окна. | | | | | | W | Integer | Начальная ширина окна в координа-| | | | тах экрана. | | | | | | H | Integer | Начальная высота окна в координа-| | | | тах экрана. | | | | | +------------+--------------+-----------------------------------+
^################################################################ #(0,0)########################################################### #####(X,Y)####################################################### #####v########################################################### #####+---------------------------------------+--+--+--########### #####| XXXXXXXXXXXXXXXX Title XXXXXXXXXXXXXX| | | ^########### #####+---------------------------------------+--+--| |########### #####| Menu | |########### #####+---------------------------------------------| |########### #####| | |########### #####| | |########### #####| | H########### #####| | |########### #####| | |########### #####| | |########### #####| | |########### #####| | |########### #####| | v########### #####+---------------------------------------------+--########### #####|<------------------W------------------------>|############# ################################################################# #################################################################
Рис. 10.1 Атрибуты окна.
Установка размеров страницы
-----------------------------------------------------------------
По умолчанию размер страницы (XPage и YPage) устанавливается в соответствии с размером области клиента окна. При изменении размеров окна механизм прокрутки учитывает эту информацию. Метод окна WMSize вызывает метод прокрутки SetPageSize, который уста- навливает поля объекта XPage и YPage на основании текущих разме- ров области клиента окна и значений XUnit и YUnit. Для отмены этого механизма и непосредственной установки размеров страницы вы должны переписать унаследованный метод объекта окна WMSize и не вызывать SetPageSize:
procedure TTestWindow.WMSize(var Msg: TMessage); begin DefWndProc(Msg); end;
Затем вы можете непосредственно установить XPage и YPage в конструкторе окна (или в производном конструкторе TScroller):
constructor ScrollWindow.Init(AParent:PWindowsObject; ATitle: PChar); begin inherited Init(AParent, ATitle); Attr.Style:=Attr.Style or ws_VScroll or ws_HScroll; Scroller:=New(PScroller, Init(@Self, 8, 15, 80, 400)); Scroller^.XPage:=40; Scroller^.YPage:=100; end;
Задание атрибутов регистрации
-----------------------------------------------------------------
В ходе инициализации оконного объекта путем заполнения поля объекта Attr вы можете установить несколько атрибутов окна, такие как его стиль, расположение и меню. Эти атрибуты используются для создания соответствующего оконного элемента, поэтому они называ- ются атрибутами создания.
Другие атрибуты, включая фоновый цвет, пиктограмму представ- ления и курсора "мыши", более тесно связаны с данным типом окон- ного объекта и не могут изменяться в ходе работы программы. Эти присущие окну атрибуты называются атрибутами регистрации, так как они устанавливаются при регистрации класса окна в Windows.
Задание для окна объекта прокрутки
-----------------------------------------------------------------
Чтобы задать для окна объект прокрутки, постройте в конс- трукторе своего оконного объекта объект TScroller и присвойте его полю Scroller. Вам нужно установить начальный размер единицы и диапазона, но позднее вы можете их изменить.
При использовании объекта прокрутки для автоматической прок- рутки полосы прокрутки не требуются, но многие прокручиваемые окна их имеют. Чтобы добавить в окно полосы прокрутки, добавьте в поле Attr.Style ws_VScroll, ws_HScroll (или то и другое).
Приведем пример конструктора для текстового окна редактиро- вания:
constructor TTextWindow.Init(AParent: PWindowsObject; ATitle: PChar); begin inherited Init(AParent, ATitle); Attr.Style := Attr.Style or ws_VScroll or ws_YScroll; Scroller := New(PScroller, Init(@Self, 8, 15, 80, 400)); end;
В качестве аргументов TScroller воспринимает прокручиваемое окно и начальные значения для полей XUnit, YUnit, XRange и YRange соответственно. Атрибуты строки и страницы получают значения по умолчанию.
После вывода окна на экран содержимое его области клиента можно прокручивать вертикально и горизонтально, используя для этого полосу прокрутки или автоматическую прокрутку. Метод Pant окна просто рисует на экране графическую информацию, необходимую для уведомления о прокрутке. Как описывается в конце этого разде- ла, метод Paint можно оптимизировать для вывода только части ри- сунка.
Запрещение автоматической прокрутки
-----------------------------------------------------------------
Объект TScroller может по умолчанию выполнять автоматическую прокрутку, но установка поля AutoMode TScroller в значение False отключает это средство. Окно-владелец может сделать это в конс- трукторе после построения объекта TScroller:
Scroller := New(PScroller, Init(@Self, 8, 15, 80, 60)); Scroller^.AutoMode :=False;
Если AutoMode равно False, то прокрутка может выполняться только с помощью полос прокрутки. Полезная особенность автомати- ческой прокрутки состоит в том, что чем дальше вы сдвинете "мышь" от области клиента окна, тем быстрее будет происходить прокрутка окна. В зависимости от удаления мыши приращение прокрутки будет обратно пропорционально значению параметра строк и прямо пропор- ционально значению параметра страницы.
Ассоциирование объектов управляющих элементов
-----------------------------------------------------------------
До этого момента мы имели дело с реакцией блоков диалога на управляющие информационные сообщения, которая использовала методы реакции, основанные на дочерних идентификаторах. Однако, иногда более предпочтительно, чтобы управляющий элемент сам реагировал на сообщение. Например, вам может потребоваться управляющий эле- мент редактирования, который позволяет вводить только цифры, или командная кнопка, которая меняет стиль при своем "нажатии". Это можно реализовать с помощью объектов управляющих элементов в ок- нах (см. Главу 12). Однако, чтобы это имело место для управляющих элементов диалога, созданного с файлом ресурса, вам нужно исполь- зовать для конструирования объекта другой конструктор.
При организации связей вы создаете объект управляющего эле- мента для представления управляющего объекта диалога. Этот объект управления дает вам гибкость в реакции на управляющие сообщения. Он дает вам возможность использования набор методов объектов уп- равляющих элементов, описанных в Главе 12.
Для связи объекта с управляющим элементом определите сначала объект управляющего элемента. Он должен быть создан в конструкто- ре диалога. Однако, вместо того, чтобы использовать конструктор Init, как это показано в Главе 12, следует использовать InitResource, который берет в качестве параметров порождающее ок- но и идентификатор управляющего элемента (из ресурса диалога). Это приводит к вызову методов реакции на сообщения объектов уп- равляющих элементов вместо обработки элементов по умолчанию. Для этого нужно определить новый тип объекта, производный от предус- мотренного типа управляющего элемента.
Обратите внимание, что в отличие от задания оконного объек- та, которое предполагает два шага (Init и MakeWindow), поскольку управляющий элемент уже существует, связь объекта с управляющим элементов выполняется за один шаг: он загружается из диалогового ресурса. Вам нужно только сообщить InitResource, какой управляю- щий элемент из ресурса вы хотите связать с объектом, используя идентификатор управляющего элемента.
Файловые диалоговые блоки
-----------------------------------------------------------------
Файловые диалоговые блоки являются другим типом диалогов, поставляемых с ObjectWindows в типе TFileDialog. Файловый диало- говый блок следует использовать каждый раз, когда вы желаете по- будить пользователя ввести имя файла, например в функциях File Open и File Save во всех приложениях. См. Рис. 11.1.
+-----------------------------------------------------+ |#=#XXXXXXXXXXXXXXXXXXXXFile OpenXXXXXXXXXXXXXXXXXXXXX| +-----------------------------------------------------| | | | +------------------+ +----------+ | | Имя файла: | *.pas | |####OK####| | | +------------------+ +----------+ | | +----------+ | | Каталог: a:\ |##Cancel##| | | +----------+ | | Файлы: Каталоги: | | +--------------+-+ +--------------+-+ | | |collect3.pas |^| |[-a-] |^| | | |collect4.pas +-| |[-c-] +-| | | |diatest.pas |#| |[-f-] |X| | | |edittest.pas |X| |[-g-] |#| | | |ewndtest.pas |#| |[-h-] |#| | | |helpwind.pas |#| |[-i-] |#| | | |lboxtest.pas |#| |[-j-] |#| | | |mditest.pas +-| |[-k-] +-| | | |paltest.pas |v| |[-w-] |v| | | +--------------+-+ +--------------+-+ | | | +-----------------------------------------------------+
Рис. 11.1 Файловый диалоговый блок.
В большинстве случаев файловые диалоговые блоки выполняются как режимные. С объектом файлового диалога связан ресурс файлово- го диалогового блока, имеющийся в ObjectWindows в файле OSTDDLGS.RES. Использование модуля OStdDlgs автоматически включа- ет файл ресурса.
Объекты диалоговых блоков
Блоки диалога, или просто диалоги, являются интерфейсными объектами, инкапсулирующими поведение диалоговых блоков. Это до- черние окнам, которые обычно выводятся для выполнения специфичес- ких задач (например, конфигурирования принтера или организации ввода текста). Объект TDialog обеспечивает инициализацию, созда- ние и исполнение всех типов блоков диалога. Для каждого типа диа- лога вашего приложения, как и в случае оконных объектов, вы може- те определить производные от TDialog диалоговые блоки.
ObjectWindows всегда предоставляет два типа диалогов наибо- лее общего типа, ввод текста и диалог файла. Кроме того, в ObjectWindows имеется тип TDlgWindow, который позволяет вам соз- давать диалоги, поведение которых более похоже на окно.
Данная глава охватывает следующие темы:
* Использование объектов диалоговых блоков. * Работа с управляющими элементами в диалоговых блоках. * Связь объектов с управляющими элементами. * Связь окон с ресурсами.
Инициализация файлового диалогового блока
-----------------------------------------------------------------
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;
Последний параметр указывает, будет ли диалог диалогом отк- рытия или сохранения (как описывается в следующем разделе).
Использование диалоговых блоков ввода
-----------------------------------------------------------------
Диалоговые блоки ввода - это простые объекты диалоговых бло- ков, определяемых типом TInputDialog, которые выводят пользовате- лю подсказку со строкой текста.
Вы можете запускать диалоги ввода как режимные или безрежим- ные диалоговые блоки, но обычно вы будете выполнять их как режим- ные. С объектом диалога ввода связан ресурс диалога ввода. Он на- ходится в файле ObjectWindows OSTDDLGS.RES.
Примечание: Использование модуля StdDlgs автоматически включает ресурсы в OSTDDLGS.RES.
Каждый раз при конструировании диалога ввода с использовани- ем метода Init, вы задаете для диалога заголовок, подсказку и текст по умолчанию. Покажем вызов конструктора Init объекта диа- лога ввода:
var SomeText: array[079] 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[0255] 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;
Использование диалоговых окон
-----------------------------------------------------------------
Основная разница между диалоговыми блоками и окнами состоит в том, что диалог имеет соответствующий ресурс и задает тип и расположение своих управляющих элементов. Но окно также может иметь управляющие элементы.
Одним из подходов размещения управляющих элементов в окне является использование объектов управляющих элементов (как пока- зано в Главе 12). Другой подход - это слияние возможностей диало- говых блоков и окон, как это делается в объектном типе TDlgWindow, что позволяет получить гибридный объект, называемый диалоговым окном. Второй подход предусматривает более удобный способ построения и управления многими управляющими элементами в окне. Кроме того, он предлагает для диалоговых блоков более гиб- кие средства окон.
TDglWindow является потомком TDialog и наследует его методы, такие как Execute, Create, Ok и EndDlg. Как и диалоговые блоки, диалоговые окна имеют соответствующий ресурс диалогового блока. С другой стороны, как и окна, диалоговые окна имеют соответствующий класс окон, определяющий среди всего прочего пиктограмму, курсор и меню. Из-за связи с оконным классом в потомке TDlgWindow следу- ет переопределять методы GetClassName и GetWindowClass. Этот класс должен быть тем же, что и перечисленный в диалоговом ресур- се.
В большинстве случаев вы будете выполнять диалоговые окна как и другие окна или безрежимные диалоговые окна с помощью мето- дов Create и Show, а не метода Execute.
В тех случаях, когда основное окно должно содержать много сложных управляющих элементов, хорошим использованием диалоговых окон является основное окно приложения. Например, программа-каль- кулятор может иметь в качестве основного окна диалоговое окно, где кнопки калькулятора заданы как управляющие элементы диалого- вого ресурса. Это позволило бы вывести в основном окне также ме- ню, пиктограмму и курсор.
Использование объектов диалоговых блоков
-----------------------------------------------------------------
Использование объектов диалоговых блоков аналогично исполь- зованию объектов всплывающего окна. Диалоги являются дочерними окнами своего порождающего окна. Для простых диалоговых блоков, которые появляются на короткое время, вся обработка диалога может быть выполнена одним методом объекта порождающего окна. Диалог может быть сконструирован, выполнен и удален в одном методе, и нет необходимости хранить диалог в поле объекта. Для более слож- ных диалогов может потребоваться записать диалоговый блок в поле оконного объекта вашего диалогового блока.
Подобно всплывающим окнам и управляющим элементам, диалого- вые блоки являются дочерними окнами и при конструировании добав- ляются к списку ChildList порождающих окон.
Использование объекта диалогового блока предусматривает сле- дующие шаги:
* Построение объекта.
* Выполнение диалогового окна.
* Закрытие диалогового окна.
Использование предопределенных диалоговых окон
-----------------------------------------------------------------
Для выполнения двух типов общих функций ObjectWindows пре- дусматривает стандартные диалоговые блоки. Одно их них, окно диа- логового блока, выводит пользователю однострочную подсказку. Дру- гое, файловое диалоговое окно, позволяет пользователю задать имя файла и каталог для открытия или сохранения файла.
Построение объекта
-----------------------------------------------------------------
Диалоговые блоки конструируются и специфицируются с помощью описания ресурса, создаваемого вне программы. Ресурс диалогового окна описывает внешний вид и размещение управляющих элементов, таких как кнопок, блоков списка, областей редактирования и текс- товых строк. Он описывает только внешний вид диалогового блока и не касается его поведения - за это отвечает прикладная программа.
Каждый ресурс диалогового блока имеет идентификатор, который может быть номером идентификатора (Word) или строкой (PChar). Этот идентификатор позволяет объекту диалогового блока задавать, какой ресурс используется для определения его внешнего вида.
Пример связи
----------------------------------------------------------------- В файле с текстом программы DIALTEST.PAS, основное окно име- ет режимный диалог, определенный типом диалога TTestDialog. Эта программа обеспечивает двухстороннюю связь между объектом диалога и его управляющими элементами. Два метода - IDBN1 и IDLB1 - явля- ются методами реакции, основанными на дочерних идентификаторах, и вызываются при выборе пользователем управляющих элементов (дочер- них окон). Например, при выборе пользователем кнопки диалога BN1 ('Fill List Box') вызывается метод IDBN1. Аналогично, когда поль- зователь делает выбор в блоке списка, вызывается IDLB1. С другой стороны, для заполнения блока списка элементами текста код метода IDBN1 посылает в диалог управляющее сообщение, lb_AddString, ис- пользуя метод диалога SendDlgItemMsg,
Эта программа также показывает как путем создания нового ти- па диалога и связывания его с ресурсом диалога в вызове конструк- тора Init метода TestWindow.RunDialog создаются новые диалоги. Полный текст программы вы можете найти на дистрибутивных дисках.
Работа с безрежимными диалоговыми блоками
-----------------------------------------------------------------
Диалоговые блоки отличаются от других дочерних окон, таких как всплывающие окна и управляющие элементы, тем, что они за вре- мя существования своего порождающего окна создаются и уничтожают- ся многократно и редко выводятся на экран и уничтожаются вместе с порождающим окном. Обычно программа создает диалоговый блок в от- вет на выбор меню, щелчок кнопкой "мыши", ошибку или другое собы- тие.
Таким образом, нужно убедиться, что вы не строите объекты диалоговых блоков снова и снова, не уничтожая их. Помните о том, что все построенные диалоговые объекты автоматически включаются в списки дочерних окон их порождающих окон.
Примечание: К режимным диалоговым блокам это не отно- сится, так как они автоматически уничтожаются при закрытии.
Работа с управляющими элементами
-----------------------------------------------------------------
Все блоки диалога, кроме самых простейших, имеют (как дочер- ние окна) несколько управляющих элементов (например, управления редактированием, блоки списка и командные кнопки). Обратите вни- мание на то, что эти управляющие элементы являются не объектами управляющих элементов, а только управляющими интерфейсными эле- ментами, без методов и полей объекта. Эта глава кроме того пока- зывает альтернативные методы, позволяющие связывать объекты уп- равления с элементами управления диалоговых блоков с использова- нием InitResource.
Примечание: Использование управляющих объектов в окне (но не блоков диалога) показано в Главе 12.
Между объектом диалога и его элементами управления имеется двухсторонняя связь (можно сказать диалог). С одной стороны диа- логу нужно манипулировать его управляющими элементами, например, для заполнения блока списка. С другой стороны ему нужно обрабаты- вать и реагировать на сгенерированные сообщения управляющих собы- тий, например, когда пользователь выбирает элемент блока списка.
Режимные и безрежимные диалоговые блоки
-----------------------------------------------------------------
Режимные диалоговые блоки являются наиболее общими блоками диалога. Аналогично генерируемым функцией MessageBox блокам сооб- щений, режимные диалоги отображаются для специфических целей на короткий отрезок времени. Слово "режимный" означает, что пока отображается диалог, пользователь не может выбрать или использо- вать его порождающее окно. Пользователь должен воспользоваться диалогом и выбрать командную кнопку OK или Cancel для прекращения диалога и возвращению к работе с программой. Режимный диалог как бы "замораживает" выполнение оставшейся части программы.
Безрежимный диалоговый блок не приостанавливает выполнения программы. Как и оконный объект, он может создаваться и выпол- няться в одном шаге с помощью MakeWindow:
Application^.MakeWindow(ADlg);
В любое момент вы можете считать данные из диалогового окна (если объект диалогового блока еще существует). Чаще всего это выполняется в методе OK, который вызывается при активизации поль- зователем командной кнопки OK.
Выполнение безрежимных диалоговых блоков
-----------------------------------------------------------------
Безрежимные диалоги похожи на всплывающие окна и управляющие элементы. Основная причина, по которой вы не можете удалять без- режимными диалогами сразу же после их отработки (в отличие от ре- жимных), состоит в том, что вы заранее не знаете, когда пользова- тель закроет блок диалога. (Помните о том, что в режимных диало- гах метод 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;
И, наконец, отдельный объект диалога будет автоматически удаляться при закрытии его порождающего окна.
Выполнение диалоговых блоков
-----------------------------------------------------------------
Выполнение диалогов аналогично созданию и отображению окна. Однако, поскольку диалоги обычно появляются на более короткий от- резок времени, некоторые этапы могут быть сокращены. Это в значи- тельной степени зависит от того, будет ли диалоговый блок отобра- жаться как режимный или безрежимный.
Выполнение файловых диалоговых блоков
-----------------------------------------------------------------
Существует два вида файловых диалоговых блоков: диалоговый блок открытия файла и диалоговый блок сохранения файла. Они раз- личаются текстом кнопки в правом верхнем углу диалогового окна. В зависимости от того, запрашивает пользователь открытие или сохра- нение файла, на командной кнопке будет написано Open или Save. Когда вы вызовите ExecDialog, то получите тип диалогового блока, заданных в конструкторе IsOpen параметром типа Boolean. Если фай- ловый диалоговый блок строится с IsOpen, установленным в True, то диалоговый блок будет работать как диалоговый блок открытия фай- ла. Если он строится с IsOpen, установленным в False, то файловый диалоговый блок будет блоком сохранения файла.
Дополнительным средством файлового диалогового блока ObjectWindows является то, что он выводит пользователю подсказку, хочет ли пользователь сохранить файл с именем уже существующего файла (см. Рис. 11.2). В другой раз вы можете запросить пользова- теля, хочет ли он открыть новый файл или очистить текущий текст без сохранения. Поскольку это должно происходить перед выводом файлового диалогового блока, то не является частью поведения это- го блока. В примере программы Steps в первой части данного руко- водства перед загрузкой рисунка из файла проверяется метод CanClose его основного окна.
+-------------------------------------------+ |#=#XXXXXXFile exists! Overwrite it?XXXXXXXX| +-------------------------------------------| | | | C:\TEMP\NICELINE.PTS | | | | +----------+ +----------+ | | |###Yes####| |###No#####| | | +----------+ +----------+ | | | +-------------------------------------------+
Рис. 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;
Выполнения режимных диалоговых блоков
-----------------------------------------------------------------
В случае режимных диалоговых блоков лучше всего, вероятно, строить, выполнять и уничтожать все объекты в одном методе (как показано в примерах данной главы). Таким образом, при каждом вы- воде диалогового блока это будет новый объект.
Объекты приложения имеют режимный эквивалент 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 }
Вызов конструктора
-----------------------------------------------------------------
Чтобы построить объект диалогового блока, вызовите конструк- тор Init. Init воспринимает в качестве своих параметров указа- тель на порождающее окно и параметр типа PChar, представляющий имя ресурса диалога:
ADlg:=New(PSampleDialog, Init(@Self, 'EMPLOYEEINFO'));
Если идентификатор задается номером, его требуется привести с помощью MakeIntResource к PChar:
Dlg := New(PSampleDialog, Init(@Self, PChar(120)));
Так как диалоговые блоки обычно строятся внутри метода окон- ного объекта, порождающее окно почти всегда задается как Self. Объекты диалоговых блоков, не создаваемые оконными объектами, должны иметь в качестве порождающего Applicartion^.MainWindow (поскольку это единственный оконный объект, всегда присутствующий в каждой программе ObjectWindows).
Взаимодействие с управляющим элементом
-----------------------------------------------------------------
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.
Завершение диалогов
-----------------------------------------------------------------
Каждый блок диалога должен иметь способ его закрытия пользо- вателем. Чаще всего это кнопки OK и/или Cancel. Потомки TDialog автоматически отреагируют на нажатие одной из этих кнопок вызовом метода EndDlg, который заканчивает диалог. Вы можете разработать новые средства завершения диалога, если только они приводят к вы- зову EndDlg. Для изменения поведения при закрытии вы можете пере- определить методы OK и Cancel.
Например, вы можете переопределить метод OK таким образом, что введенные данные будут копироваться в буфер, который находит- ся вне объекта блока диалога. Если ввод был осуществлен некор- ректно, вы можете вывести блок сообщения или сгенерировать звуко- вой сигнал. Если ввод был сделан верно, вы можете вызвать EdnDlg. Переданное в EndDlg значение становится возвращаемым значением ExecDialog.
Как и в случае оконных объектов, объект диалога вызывает CanClose до закрытия блока диалога, как это имело место для объ- ектов окна. Вы можете переписать CanClose для учета условий зак- рытия, как для блока диалога, который проверяет ввод пользовате- ля. При переписывании CanClose нужно быть уверенным в том, что вызывается унаследованный от него метод, т.к. он вызывает методы CanClose дочерних окон.