Pascal 7 & Objects

         

Автоматические дочерние окна


-----------------------------------------------------------------

Может потребоваться, чтобы ваша окно-рамка воспроизводило только одно дочернее окно MDI при своем первом появлении. Для этого первого дочернего окна вы можете явно задать его размер. В отличие от других дочерних окон, дочерние окна MDI должны быть сконструированы и созданы в методе SetupWindow окна-рамки MDI, а не в Init. Вы также должны явно создать экранный элемент дочерне- го окна с помощью вызова MakeWindow:

procedure MyMDIWindow.SetupWindow; var ARect: TRect; NewChild: PMyChild; begin TMDIWindow.SetupWindow; NewChild:=PMyChild(InitChild); GetClientRect(HWindow, ARect); with NewChild^.Attr, ARect do begin W:=(right*4) div 5; H:=(bottom*3) div 5; Title:='Child #1'; end; Application^.MakeWindow(NewChild); end;

В некоторых приложениях вам может потребоваться создать до- чернее окно MDI в ответ на более чем один выбор в меню. Например, пункты меню New и Open в редакторе файла могут приводить к воз- никновению нового дочернего окна с заголовком в виде имени файла. В этом случае определите для построения дочернего окна методы ав- томатической реакции. ObjectWindows определяет команды cm_MDIFileOpen и cm_MDIFileNew, что облегчает дифференциацию от стандартных cm_FileOpen и cm_FileNew.



Дочерние окна MDI


-----------------------------------------------------------------

Каждое дочернее окно MDI имеет некоторые характеристики пе- рекрывающего окна. Его можно максимизировать до полного размера окна клиента MDI или минимизировать в пиктограмму, которая поме- щается к нижней границе окна-рамки. Дочернее окно MDI никогда не выходит за границы его окна-рамки (обрамляющего окна). Дочернее окно MDI не может иметь меню, поэтому все его функции реализуются меню окна-рамки. Заголовок каждого дочернего окна MDI часто представляет собой имя открытого файла, связанного с этим окном, хотя его поведение заранее неизвестно и определяется программой. Можно рассматривать приложение MDI как мини-сеанс Windows, когда несколько приложений представлены окнами или пиктограммами.



Объекты MDI


Многодокументальный интерфейс (MDI) - это стандарт интерфей- са для приложений Windows, которые позволяют пользователю однов- ременно работать с несколькими открытыми документами. Документ, в этом смысле, это обычно связанная с файлом задача, например, ре- дактирование текстового файла или работа с файлом электронной таблицы. В приложениях MDI пользователь может, например, иметь несколько открытых файлов в одном приложении. Возможно, что вы уже использовали приложения MDI: Microsoft Excel, администратор программ Windows, администратор файлов Windows. Стандарт MDI яв- ляется также частью спецификации общего доступа пользователя (CUA) фирмы IBM. ObjectWindows предусматривает объекты, позволяющие легко пи- сать приложения MDI.

Что такое приложение MDI? ----------------------------------------------------------------- Имеются определенные компоненты, которые присутствуют в каж- дом приложении MDI. Чаще всего основное окно вызывает окно с рам- кой. В области клиента окна-рамки есть невидимое окно - окно кли- ента MDI - которое содержит дочернее окно, вызывающее дочерние окна MDI. Это очень важно, т.к. обработка дочерних окон MDI про- исходит скрытно от пользователя. +-----------------------------------------------------------+-+-+ |#=#XXXXXXXXXXXXXXXXXXXXMDI ConformistXXXXXXXXXXXXXXXXXXXXXX|^|v| +-----------------------------------------------------------+-+-| | MDI Children | +---------------------------------------------------------------| |+---------------------------------------------------+ | ##################Child #1#########################| | |+---------------------------------------------------| | +--+---------------------------------------------------+ | | X|####################Child #2#######################| | +--+---------------------------------------------------| | | +--+-----------------------------------------------+-+-+| | | X|#=#XXXXXXXXXXXXXXXXChild #3XXXXXXXXXXXXXXXXXXXX|^|v |+----| +--+-----------------------------------------------+-+- | | | +---+ ^ | | | | X | CanClose блоки минимизации и ---+ | +----| +---+ максимизации | | ^ | +-----------------------------+---------------------+| | дочернее окно MDI -+ | | +---+ | | | <+--- пиктограмма | | +---+ | | Child #4 ^ | +----------------------------+----------------------------------+ | ^ окно клиента MDI -+ | окно-рамка MDI -+ Рис. 14.1 Компоненты приложения MDI.



Меню дочернего окна


-----------------------------------------------------------------

Строка меню окна-рамки содержит меню, управляющее дочерними окнами MDI. Меню дочернего окна содержит такие элементы как Tile (Вывод без перекрытия), Cascade (Вывод с перекрытием), Arrange (Упорядочить) и Close All (Закрыть все). Имя каждого открытого окна MDI автоматически добавляется к концу этого меню с выбором текущего окна.





Настройка активизации дочернего окна


-----------------------------------------------------------------

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



Обработка сообщений в приложении MDI


-----------------------------------------------------------------

Как и для обычных порождающих и дочерних окон, основанные на командах и дочерних идентификаторах сообщения Windows сначала поступают в дочерние окна для их восприятия и обработки. Затем сообщения поступают в порождающее окно. Однако, в случае приложе- ния MDI сообщения поступают к текущему дочернему окну MDI, затем к окну клиента MDI, и, наконец, к окну-рамке MDI (которое являет- ся порождающим окном для всех дочерних окон MDI). Следовательно, меню окна-рамки можно использовать для управления работой в теку- щем активном дочернем окне MDI. Затем шанс отреагировать получают окно клиента и окно-рамка.



Окна MDI в ObjectWindows


-----------------------------------------------------------------

ObjectWindows определяет типы для представления рамок MDI и клиентов MDI. Это соответственно TMDIWindow и TMDIClient. TMDIWindow является производным от TWindow, но TMDIClient на са- мом деле представляет собой управляющий элемент и является произ- водным от TControl. В приложении MDI ObjectWindows, окно-рамки владеет своим окном клиента MDI и хранит его в поле ClientWnd. Окно-рамка также содержит каждое из дочерних окон MDI в связанном списке ChildList. Дочерние окна MDI являются экземплярами типа объекта, производного от написанного вами TWindow.

Методы TMDIWindow занимаются в основном конструированием и управлением дочерними окнами MDI, окном клиента MDI и обработкой выбора в меню. Главная работа TMDIClient происходит скрытно от пользователя и состоит в управлении дочерними окнами MDI. При разработке приложений MDI вы в общем случае будете создавать но- вые производные типы для своих рамок и дочерних окон соответс- твенно от TMDIWindow и TWindow.



Построение приложения MDI


-----------------------------------------------------------------

Построение приложения MDI в ObjectWindows представляет собой относительно простую задачу:

* Построение основного окна MDI.

* Установка меню дочернего окна.

* Предоставление основному окну возможности создания дочер- них MDI.

Окно MDI обрабатывает для вас все специфические функции, а ваши функции, специфические для приложения, могут перейти в до- черние окна.



Построение рамки MDI


-----------------------------------------------------------------

Окно-рамка MDI всегда является основным окном приложения, поэтому оно конструируется в методе InitMainWindow его объекта приложения. Однако, существует два аспекта рамки MDI, которые от- личают его от других основных окон:

* Рамка MDI всегда является основным окном, поэтому оно ни- когда не имеет порождающего окна. Таким образом, TMDIWindow.Init нет необходимости воспринимать в качестве параметра указатель порождающего окна.

* Окно-рамка MDI всегда должно иметь меню, так что вторым параметром Init является описатель меню. Для основных окон, отличных от MDI и производных от TWindows, вы опре- деляете Init для установки Attr.Menu в допустимый описа- тель меню. TMDIWindow.Init устанавливает для вас AttrMenu.

Типичный метод InitMainWindow для приложения MDI может выг- лядеть следующим образом:

procedure TMDIApplication.InitMainWindow; begin MainWindow := New(PMyFrame, Init('Заголовок рамки', LoadMenu(HInstance, 'MenuName'));

Если предположить, что TMyFrame - это потомок TMDIWindow, при этом будет создаваться окно-рамка MDI с заголовком "Заголовок рамки" и строкой меню, заданной ресурсом "MenuName".



Пример приложения MDI


-----------------------------------------------------------------

Программа MDITest создает приложение MDI, показанное на Рис. 14.1. Полный текст файла MDITEST.PAS содержится на ваших дистрибутивных дискетах.



Создание дочерних окон MDI


-----------------------------------------------------------------

TMDIWindow определяет автоматический метод реакции CreateChild, который вызывается при выборе из меню варианта, ре- зультатом которого будет команда с идентификатором Create_Child. Обычно этот вариант меню называется New или Create. Как это опре- делено в TMDIWindow, CreateChild конструирует и создает дочернее окно MDI типа TWindow вызовом TMDIWindow.InitChild. Для задания корректного типа дочернего окна (производного от TWindow), пере- определим InitChild для вашего типа окна-рамки MDI:

function MyMDIWindow.InitChild: PWindowsObject; begin InitChild:=New(PMyChild, Init(@Self, 'Новое дочернее окно')); end;



Создание меню дочерних окон


-----------------------------------------------------------------

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

Окно-рамка должно знать, каким элементом меню верхнего уров- ня является меню его дочернего окна. Объект TMDIWindow хранит це- лое значение позиции в поле объекта ChildMenuPos. TMDIWindow.Init первоначально устанавливает ChildMenuPos в ноль, указывая край- ний левый элемент меню верхнего уровня. Однако, для установки по- зиции ChildMenuPos вы можете переопределить Init для своего про- изводного от TMDIWindow типа:

constructor TMyMDIWindow.Init(ATitle: PChar; AMenu: HMenu); begin inherited Init(ATitle, AMenu); ChildMenuPos := 1; end;

TMDIWindow.Init также вызывает InitClientWindow для констру- ирования объекта TMDIClient, который будет служит его окном кли- ента MDI. TMDIWindow.SetupWindow создает окно клиента MDI.



Управление дочерним окном MDI


-----------------------------------------------------------------

Тип окна MDI в ObjectWindows содержит методы манипулирования дочерними окнами MDI приложения MDI. Хотя большая часть скрытой работы делается в TMDIClient, доступ к данным и функциям происхо- дит через метод TMDIWindow.

TMDIWindow определяет методы реакции на сообщения Windows, которые автоматически реагируют на выбор команды стандартного ме- ню MDI: Title, Cascade, Arrange Icon и Close All. Эти методы ожи- дают основанных на командах сообщений с заранее определенными константами идентификаторов меню. Обязательно используйте эти идентификаторы при построении ресурса меню дочернего окна:

Стандартные методы, команды и действия MDI Таблица 14.1 +----------------+------------------------+---------------------+ | Действие | Константа ID меню | Метод TMDIWindow | +----------------+------------------------+---------------------| | Tile | cm_TileChildren | CM_TileChildren | | Cascade | cm_CascadeChildren | CM_CascadeChildren | | Arrange Icons| cm_ArrangeChildIcons | CM_ArrangeChildIcons| | Close All | cm_CloseChildren | CM_CloseChildren | +----------------+------------------------+---------------------+

Методы реакции TMDIWindows, подобные CMTileChildren, вызыва- ют другие методы TMDIWindows, такие как CMChildren. Эти методы вызывают методы TMDIClient с тем же именем, например, TMDIClient^.TileChildren. Для переопределения такого автоматичес- кого поведения нужно переопределить TMDIWindow.TileChildren или другой метод TMDIWindow. Для дочерних окон MDI не подходит реаги- рование на основанные на командах сообщения, генерируемые меню дочернего окна.



Другие соглашения по печати


-----------------------------------------------------------------

Объекты распечатки содержат также несколько других методов, которые вы можете при необходимости переопределить. Методы BeginPrintint и EndPrinting вызываются, соответственно, перед пе- чатью и после печати любого документа. Если вам требуется специ- альная установка, вы можете выполнить ее в BeginPrinting и отме- нить в EndPrinting.

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

Может потребоваться также переопределение метода GetSelection. GetSelection указывает в своем возвращаемом булевс- ком значении, имеет ли документ выделенную часть. Если это так, диалоговое окно печати предоставляет вам возможность распечатать только эту выделенную часть. Позицию выделения указывают два па- раметра-переменных Start и Stop. Например, TEditPrintout интерп- ретирует Start и Stop как позиции символов, но может представлять также строки текста, страницы и т.д.



Объекты печати


Один из наиболее трудных аспектов программирования в Windows - это вывод на принтер. ObjectWindows путем использования ряда модулей и инкапсуляции поведения устройства печати и самой распе- чатки делает печать более простой.

Данная глава описывает основы процесса печати, после чего описываются следующие задачи:

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

* Создание распечатки.

- Печать документа.

- Печать содержимого окна.

* Передача распечатки на принтер.

* Выбор другого принтера.

* Настройка конфигурации принтера.



Назначение конкретного принтера


-----------------------------------------------------------------

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

SetDevice воспринимает в качестве параметров три строки: имя устройства, имя драйвера и имя порта.



Печать документа


-----------------------------------------------------------------

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

ObjectWindows предусматривает абстрактный объект распечатки TPrintout, из которого вы можете создать производные объекты рас- печатки. Вам нужно переопределить в TPrintout только несколько методов.

Ваши объекты распечатки должны делать следующее:

* Устанавливать параметры принтера.

* Подсчитывать страницы.

* Отображать каждую страницу в контексте устройства.

* Указывать, есть ли еще страницы.

Остальная часть этой главы ссылается на пример программы PrnTest, записанной на ваших дистрибутивных дискетах под именем PRNTEST.PAS. PrnTest считывает текстовый файл в набор строк, а затем по команде печатает документ. Объект PrnTest описывается следующим образом:

type PTextPrint = ^TTextPrint; TTextPrint = object(TPrintout); TextHeight, LinesPerPage, FirstOnPage, LastOnPage: Integer; TheLines; PCollection; constructor Init(ATitle: PChar; TheText: PPCharCollection); function GetDialogInfo(var Pages: Intger): Boolean; virtual; function HasNextPage(Page: Word): Boolean; virtual; procedure SetPrintParams(ADC: HDC; ASize: TPoint); virtual; procedure PrintPage(Page: Word; var Rect: TRect; Flags: Word); virtual; end;



Печать каждой страницы


-----------------------------------------------------------------

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

procedure TTextWindow.Paint(PaintDC: HDC; var PaintInfo: TPaintStruct); var Line: Integer; TextMetrics: TTextMetric; TheText: PChar;

function TextVisible(ALine: Integer): Boolean; begin with Scroller^ do TextVisible := IsVisible(0, (ALine div YUnit) + YPos, 1, Attr.W div YUnit); end;

begin GetTextMetrics(PaintDC, TextMetrics); Scroller^.SetUnits(TextMetrics.tmAveCharWidth, TextMetrics.tmHeight); Line := 0; while (Line < FileLines^.Count) and TextVisible(Line) do begin TheText := PChar(FileLines^.At(Line)); if TheText <> nil then TextOut(PaintDC, 0, Line * Scroller^.YUnit, TheText, StrLen(TheText)); Inc(Line); end; end;

procedure TTextPrint.PrintPage(Page: Word; var Rect: TRect; Flags: Word); var Line: Integer; TheText: PChar; begin FirstOnPage := (Page - 1) * LinesPerPage; LastOnPage := (Page * LinesPerPage) - 1; if LastOnPage >= TheLines^.Count then LastOnPage := TheLines^.Count - 1; for Line := FirstOnPage to LastOnPage do begin TheText := Theines^.At(Line); if TheText <> nil then TextOut(DC, 0, (Line - FirstOnPage) * TextHeight, TheText, StrLen(TheText)); end; end;

При написании методов PrintPage следует иметь в виду следую- щее:

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

* Возможности устройств. Хотя большинство видеоустройств поддерживают все операции GDI, некоторые принтеры этого не делают. Например, многие устройства печати (такие как гра- фопостроители) совсем не воспринимают графических изобра- жений. При выполнении сложных задач вывода в программе следует вызывать функцию API Windows GetDeviceCaps, кото- рая возвращает важную информацию о данном устройстве выво- да.



Печать содержимое окна


-----------------------------------------------------------------

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

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

Создание объекта распечатки окна требует только одного шага. Все, что требуется сделать - это построить объект печати окна, передавая ему строку заголовка и указатель на окно, которое тре- буется напечатать:

PImage := New(PWindowPrintout, Init('Заголовок', PSomeWindow));

Часто возникает необходимость в том, чтобы окно само созда- вало свою распечатку, возможно в ответ на команды меню:

procedure TSomeWindow.CMPrint(var Msg: TMessage); var P: PPrintout; begin P := New(PWindowPrintout, Init('Дамп экрана', @Self)); { передать образ на экран } Dispose(P, One); end;

TWindowPrintout не предусматривает разбивки на страницы. При печати документа вам нужно печатать каждую страницу отдельно, но так как окна не имеют страниц, вам нужно напечатать только один образ. Окно уже знает, как создать этот образ - оно имеет метод Paint. TWindowsPrintout печатается путем вызова метода Paint окна объекта с контекстом устройства печати вместо контекста дисплея.



Печать в ObjectWindows


-----------------------------------------------------------------

Модуль ObjectWindows OPrinter предусматривает для упрощения печати два объекта - TPrinter и TPrintout. TPrinter инкапсулирует доступ к устройствам печати. Он предоставляет возможность конфи- гурирования принтера, выводя диалог, в котором пользователь может выбрать нужный принтер, а также установить параметры печати, та- кие как графическое разрешение или ориентация (горизонтальная и вертикальная) печати.

TPtintout инкапсулирует задачу печати документа. К принтеру этот объект имеет такое же отношение, как TWindow - к экрану. Ри- сование на экране выполняется методом Paint объекта TWindow, а печать на принтере - методом PrintPage объекта TPrintout. Чтобы напечатать что-то на принтере, приложение должно передать методу Print объекта TPrinter экземпляр TPrintout.



Почему печать представляет трудности?


-----------------------------------------------------------------

С одной стороны печать в Windows достаточно проста. Вы може- те использовать для генерации распечатки те же функции GDI, что используются для вывода образов на экран. Для вывода текста ис- пользуется функция TextOut, а для вычерчивания прямоугольника - Rectangle.

С другой стороны, процесс осложняется, так как Windows тре- бует непосредственного "общения" с драйверами принтера через вы- зовы Escape или получения адреса DeviceMode или ExtDeviceMode. Это еще более осложняется требованием Windows, чтобы приложение считывало имя драйвера устройства из файла WIN.INI. Кроме того, устройства печати обладают большими возможностями проверки допус- тимости и возможностями разрешения, чем видеоустройства.

ObjectWindows не может преодолеть все препятствия на этом пути, но делает процесс печати более простым и легким для понима- ния.



Подсчет страниц


-----------------------------------------------------------------

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

Функция GetDialogInfo воспринимает единственный параметр-пе- ременную Pages, которую она должна устанавливать в число страниц в документе или в 0, если она не может подсчитать страницы. Возв- ращаемое значение равно True, если вы хотите вывести диалоговый блок, и False для подавления его вывода.

По умолчанию GetDialogInfo устанавливает Pages в 0 и возвра- щает True, что означает, что она не знает, сколько может полу- читься страниц, и что перед печатью будет выводиться диалоговый блок. GetDialogInfo обычно переопределяется для установки Pages в число страниц в документе и возвращает True.

Например, PrnTest подсчитывает, сколько строк текста выбран- ного шрифта может поместиться в области печати в SetPrintParams, а затем использует это число для подсчета количества страниц, ко- торые нужно напечатать в GetDialogInfo:

procedure TTextPrint.SetPrintParams(ADC: HDC; ASize: TPoint); var TextMetrics: TTextMetric; begin inherited SetPrintParams(ADC, ASize); { установить DC и размер Size } GetTextMetrics(DC, TextMetrics); { получить информацию о размере текста } TextHeigh := TextMetrics.tmHeight; { вычислить высоту строки } LinesPerPages := Size.Y div TextHeight; { и число строк на странице } end;

function TTextPtint.GetDialogInfo(var Pages: Integer): Boolean); begin Pages:= TheLines^.Count div LinesPerPage + 1; GetDialogInfo := True { вывод перед печатью диалогового блоки } end;



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


-----------------------------------------------------------------

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

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

constructor TSomeWindow.Init(AParent: PWindowsObject; ATitle: PChar); begin Inherited Init(AParent, ATitle); . . . Printer := New(PPrinter, Init); end;

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

Хотя вы можете сомневаться насчет переопределения конструк- тора TPrinter для использования принтера, отличного от заданного в системе по умолчанию, рекомендуемой процедурой является исполь- зование конструктора по умолчанию, а затем смена связанного с объектом устройства. См. раздел "Выбор другого принтера".



Создание распечатки


-----------------------------------------------------------------

Единственной "хитрой" частью процесса печати в ObjectWindows являются создание распечатки. Этот процесс аналогичен написанию метода Paint для объекта окна: вы используете графические функции Windows для генерации в контексте устройства нужного графического образа. Контекст устройства оконного объекта обрабатывает ваши взаимодействия с устройством экрана; аналогичным образом контекст устройства распечатки изолирует вас от устройства печати.

Примечание: Графические функции Windows поясняются в Главе 17.

Чтобы создать объект распечатки, постройте новый тип, произ- водный от TPtintout, который переопределяет PrintPage. В очень простых случаях это все, что требуется сделать. Если документ имеет размер более одной страницы, то вам нужно также переопреде- лить HasNextPage для возврата True. Текущий номер страницы пере- дается в качестве параметра PrintPage.

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

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



Указание оставшихся страниц


-----------------------------------------------------------------

У объектов распечатки имеется также последняя обязанность - указать объекту принтера, имеются ли после данной страницы еще печатаемые страницы. Метод HasNextPage воспринимает в качестве параметра номер строки и возвращает значение Boolean, указываю- щее, существуют ли еще страницы. По умолчанию HasNextPage всегда возвращает значение False. Чтобы напечатать несколько страниц, ваши объекты распечатки должны переопределять HasNextPage для возврата True, если документ имеет больше страниц для печати, и False, если переданным параметром является последняя страница.

Например, PrnTest сравнивает номер последней напечатанной строки с последней строкой в файле и определяет, нужно ли печа- тать еще страницы:

function TTextPrint.HasNextPage(Page: Word): Boolean; begin HasNextPage := LastOnPage < TheLines^.Count - 1; end;

Убедитесь, что HasNextPage возвращает в некоторой точке зна- чение False. Если HasNextPage всегда возвращает True, то процесс печати попадет в бесконечный цикл.



Выбор другого принтера


-----------------------------------------------------------------

Когда у вас в приложении есть объект принтера, вы можете связать его с любым установленным в Windows устройстве печати. По умолчанию TPrinter использует заданный по умолчанию принтер Windows (как это определено в разделе устройств файла WIN.INI).

Существует два способа задания альтернативного принтера: не- посредственно в программе и через диалоговое окно пользователя.



Выбор принтера пользователем


-----------------------------------------------------------------

Наиболее общим способом назначения другого принтера является вывод диалогового окна, предоставляющего пользователю возможность выбора из списка установленных устройств печати. TPtinter делает это автоматически при вызове его метода Setup. Как показано на Рис. 15.1, Setup использует для этого диалогового окна объект TPrinterSetupDlg.

+---------------------------------------------------------------+ |#=#XXXXXXXXXXXXXXXXXXXSelectPrinterXXXXXXXXXXXXXXXXXXXXXXXXXXXX| +---------------------------------------------------------------| | | | Printer and port: | | +--------------------------------------------+-+ | | |PostScript Printer on LPT1: |v| | | +--------------------------------------------+-+ | | +-----------+ +-----------+ +-----------+ | | |####OK#####| |##Setup####| |##Cancel###| | | +-----------+ +-----------+ +-----------+ | | | +---------------------------------------------------------------+

Рис. 15.1 Диалоговое окно задания принтера.

Настройка конфигурации принтера

Одна из командных кнопок в диалоге выбора принтера позволяет пользователям изменить конфигурацию конкретного принтера. Кнопка Setup выводит диалоговый блок конфигурации, определенной в драй- вере принтера. Ваше приложение не может управлять внешним видом или функциями диалогового блока конфигурации драйвера.



Вывод распечатки на принтер


-----------------------------------------------------------------

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

Printer^.Print(PParentWindow, PPrintoutObject);

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

Предположим, у вас есть приложение, основное окно которого является экземпляром TWidGetWindow. В меню этого окна вы можете выбрать команду меню для печати содержимого окна, генерирующую команду cm_Print. Метод реакции на сообщения может иметь следую- щий вид:

procedure TWidgetWindow.CMPrint(var Msg: TMessage); var P: PPrintout; begin P := New(PWindowPrint, Init('Widgets', @Self)); Printer^.Print(@Self, P); Dispose(P, Done); end;



Задание параметров печати


-----------------------------------------------------------------

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

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

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



Что такое сообщение?


-----------------------------------------------------------------

Если вы не используете программирование, управляемое событи- ями, Windows может выглядеть достаточно странной операционной средой. Возможно, вам придется писать программы, которые основную часть своего времени просто ждут ввода от пользователя (например, в операторе Readln).

Программирование, управляемое событиями, обходит эту ситуа- цию, возлагая обработку ввода от пользователя на центральную подпрограмму, которую вам даже не нужно вызывать. В этом случае Microsoft Windows сама взаимодействует с пользователем и опраши- вает список взаимодействий для каждого работающего приложения. Эти информационные пакеты называются сообщениями и представляют собой просто структуры записей типа TMsg:

type TMsg = record hwnd: HWnd; message: Word; wParam: Word; lParam: Longint; time: Longint; pt: TPoint; end;

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

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


-----------------------------------------------------------------

Теперь, когда вы знаете, как написать метод реакции на сооб- щение, можно рассмотреть, какая информация содержится в сообще- нии. Запись TMessage, передаваемая методу реакции на сообщение, выглядит следующим образом:

type TMessage = record Receiver: HWnd; Message: Word; case Integer of 0: ( WParam: Word; LParam: Longint; Result: Longint); 1: ( WParamLo: Byte; WParamHi: Byte; LParamLo: Word; LParamHi: Word; ResultLo: Word; ResultHi: Word); end;

Поля Receiver и Message для объектов ObjectWindows не осо- бенно полезны, поскольку описатель Receiver обычно представляет собой тоже самое, что и поле HWindow оконного объекта, а Message уже отсортировано в цикле сообщения ObjectWindows.

Однако другие три поля очень важны. WParam и LParam - это 16- и 32-битовые параметры, передаваемые в сообщениях от Windows. Result содержит код результата, который может потребоваться пере- дать обратно. Заметим, что TMessage - вариантная запись, так что вы можете обращаться к старшему и младшему байту слов параметров.



Диапазоны сообщений


-----------------------------------------------------------------

Сообщение определяется полем message записи сообщения (16-битовое значение). Windows резервирует для своих собственных стандартных сообщений 0$03FF, а остальные сообщения до $7FFF резервируются для сообщений, определенных пользователем. Диапа- зоны остальных сообщений в ObjectWindows подразделяются на диа- пазоны команд и уведомлений (как показано в следующей таблице):

Диапазоны сообщений Таблица 16.1 +-------------------------------------+----------------+ | Диапазон | Значения | +-------------------------------------+----------------| | Сообщения, зарезервированные | $0000-$7FFF | | для Windows. | | +-------------------------------------+----------------| | Сообщения, определяемые | $0400-$8FFF | | пользователем. | | +-------------------------------------+----------------| | Уведомляющие сообщения | $8000-$8FFF | | управляющих элементов. | | +-------------------------------------+----------------| | Зарезервированные в Windows | $8F00-$8FFF | | уведомляющие сообщения | | | управляющих элементов. | | +-------------------------------------+----------------| | Уведомляющие сообщения | $9000-$9FFF | | порождающего объекта. | | +-------------------------------------+----------------| | Зарезервированные в Windows | $9F00-$9FFF | | уведомляющие сообщения | | | порождающего объекта. | | +-------------------------------------+----------------| | Командные сообщения. | $A000-$FFFF | +-------------------------------------+----------------| | Команды, зарезервированные | $FF00-$FFFF | | в ObjectWindows. | | +-------------------------------------+----------------+

+------------------------+ cm_Internal ($FF00) +------------------------| | | | | | Команды | | | | | cm_First ($A000) +------------------------| nf_Internal ($9F00) +------------------------| | | | Уведомления | | управляющих элементов | | | nf_First ($9000) +------------------------| id_Internal ($8F00) +------------------------| | | | Уведомления | | порождающих объектов | | | id_First ($8000) +------------------------| + | | | | | | | Сообщения, | | | определенные | + wm_Count | пользователем | | ($8000) | | | | | | wm_User ($A000) +------------------------| + | Сообщения Windows | wm_First($0000) +------------------------+

Рис. 16.1 Диапазоны сообщений и команд.



Динамические виртуальные методы


-----------------------------------------------------------------

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

Например, сообщение, генерируемое при нажатии в окне левой кнопки "мыши", содержит в своем поле message wm_LButtonDown ($0201). Когда цикл сообщения ObjectWindows считывает для одного из своих окон такое сообщение, то выполняется поиск в таблице виртуальных методов данного оконного объекта и определяется дина- мический метод, описанный для данного значения. Если такой метод найден, то он вызывается, и ему в качестве параметра передается распакованная запись сообщения типа TMessage. Если оконный объект не описывает метод с данным индексом динамического метода, то цикл сообщения вызывает используемую по умолчанию оконную проце- дуру.



Дополнение поведения по умолчанию


-----------------------------------------------------------------

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



Сообщения Windows


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

Данная глава охватывает ряд тем, связанных с передачей, по- лучением и обработкой сообщений, включая следующие вопросы:

* что такое сообщение?

* как выполняется диспетчеризация сообщений?

* обработка сообщений Windows;

* определение ваших собственных сообщений;

* передача и адресация сообщений;

* диапазоны сообщений.



Именующие сообщения


-----------------------------------------------------------------

Наиболее важным полем сообщений является поле message, кото- рое содержит одну из констант сообщений Windows, начинающихся с wm_. Каждое сообщение Windows уникальным образом идентифицируется 16-битовым числом с соответствующим мнемоническим идентификато- ром. Например, сообщение, являющееся результатом нажатия клавиши, содержит в поле сообщения wm_KeyDown ($0100). На сообщения обычно ссылаются по их мнемоническим именам.



Командные сообщения


-----------------------------------------------------------------

ObjectWindows обрабатывает команды меню и оперативных клавиш путем отдельной диспетчеризации командных сообщений в основном аналогично другим сообщениям Windows. Реально обработка выполня- ется внутри метода WMCommand ваших оконных объектов, наследуемого из TWindowsObject. Но вместо обработки самих команд WMCommand вы- полняет диспетчеризацию командных сообщений на основе генерируе- мого командой идентификатора меню или оперативной клавиши.

Например, если вы определяете элемент меню с идентификатором cm_DoSomething, в ваших объектах следует на основе этого иденти- фикатора определить методы реакции:

type TSomeWindow = object(TWindow) . . . procedure CMDoSomething(var Msg: TMessage); virtual cm_First + cm_DoSomething; end;

procedure TSomeWindow.CMDoSomething(var Msg: TMessage); begin { реакция на команду } end;

Аналогично wm_First, cm_First - это константа ObjectWindows, определяющая начало диапазона сообщений. Ваши командные константы должны лежать в диапазоне 024319.

Обработка команд по умолчанию

Чтобы вызвать используемую по умолчанию реакцию на команду, для нее обычно вызывается наследуемый метод реакции. Если в объ- екте-предке не определяется метод реакции на конкретную команду, по умолчанию обработка выполняется с помощью DefCommandProc. DefCommandProc работает во многом аналогично методу DefWndProc для сообщений Windows, но обрабатывает команды.



Командные, уведомляющие и управляющие идентификаторы


-----------------------------------------------------------------

Сообщение - это просто запись данных, идентифицируемая конк- ретным значением в поле message, а ObjectWindows экономит вам массу времени и усилий путем сортировки этих сообщений и диспет- черизации их методам реакции на сообщения в ваших объектах. Одна- ко, сообщение Windows wm_Command имеет много вариантов, которые нужно отсортировывать с помощью большого оператора case. ObjectWindows также выполняет это для вас.

Например, сообщение wm_Command посылается при выборе элемен- та меню или нажатии оперативной клавиши. В обычном приложении сообщение Windows передавалось бы вашей функции окна, где в боль- шом операторе case нужно было бы отсортировать различные сообще- ния, вызывая другие подпрограммы при обнаружении wm_Command. Эта подпрограмма должна в свою очередь определить, какая была переда- на команда (с помощью другого большого оператора case).



Написание методов реакции на сообщение


-----------------------------------------------------------------

Чтобы описать методы реакции на сообщение, нужно задать в оконном объекте процедуру, названную по имени константы сообще- ния. Например, чтобы ответить на сообщение wm_LButtonDown, вам нужно описать методы следующим образом:

type TMyWindow = object(TWindow) . . . procedure WMLButtonDown(var Msg: TMessage); virtual wm_First + wm_LButtonDown; . . . end;

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

Поскольку для методов реакции ObjectWindows использует нес- колько диапазонов, а все индексы методы отсчитываются с 0, в ка- честве смещения используется константа wm_First. Добавив для каж- дого метода это смещение, вы создадите уникальный индекс динами- ческого метода. Подробнее о диапазонах сообщений рассказывается в разделе "Диапазоны сообщений" данной главы.



Объектно-ориентированная обработка сообщения


-----------------------------------------------------------------

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



Обычная диспетчеризация сообщений


-----------------------------------------------------------------

Обычные приложения Windows (то есть не использующие ObjectWindows) имеют цикл сообщения, в котором выполняется выбор- ка и диспетчеризация сообщения. По существу, в цикле сообщения вызывается связанная с окном функция, заданная описателем окна в поле hwnd записи сообщения.

Каждое окно имеет функцию окна, заданную при его создании. Когда Windows находит сообщение для конкретного окна, она переда- ет сообщение функции данного окна. Функция окна отсортировывает сообщения на основе типа сообщения, а затем вызывает подпрограмму реакции на конкретное сообщение.

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

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



Определение ваших собственных сообщений


-----------------------------------------------------------------

Windows резервирует для собственного использования 1024 со- общения. В этот диапазон попадают все стандартные сообщения. На- чало диапазона сообщений определяется константой wm_User. Чтобы определить сообщение, используемое окнами вашей программы, опре- делите идентификатор сообщения, попадающий в диапазон wm_Userwm_User+31744.

Чтобы избежать конфликта со стандартными сообщениями, вам следует определить идентификаторы своих сообщений как константы на основе wn_User. Например, в приложении, где требуется три оп- ределенных пользователем сообщения, вы можете описать их следую- щим образом:

const wm_MyFirstMessage = wm_User; wm_MySecondMessage = wm_User + 1; wm_MyThirdMessage = wm_User + 2;

Реакция на ваши сообщения аналогично реакции на любое другое сообщение:

TCustomWindow = object(TWindow) . . . procedure WMMyFirstMessage(var Msg: TMessage); virtual wm_First + wm_MyFirstMessage

Согласно общему правилу, определенные пользователем сообще- ния следует использовать для внутреннего обмена сообщения. Если вы посылаете определенное пользователем сообщение другому прило- жению, нужно убедиться, что это другое приложение определяет со- общение также, как это делается в коде вашего приложения. Внешние коммуникации лучше обрабатываются с помощью динамического обмена данными (DDE).



Откуда поступают сообщения


-----------------------------------------------------------------

Генерировать сообщения позволяют несколько различных событий:

* взаимодействия с пользователем, такие как нажатия клавиш, щелчок кнопкой "мыши" или ее буксировка; * вызовы функций Windows, которым нужно информировать об из- менениях другие окна; * ваша программа явно посылает сообщение; * другое приложение посылает сообщение через DDE (динамичес- кий обмен данными); * сообщение генерируется самой Windows (например, сообщение об останове системы).

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



Отмена поведения по умолчанию


-----------------------------------------------------------------

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

procedure TNonEdit.WMChar(var Msg: TMessage); begin end;



Отправление сообщения


-----------------------------------------------------------------

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



Передача и отправление сообщений


-----------------------------------------------------------------

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

Имейте в виду, что SendMessage, особенно при вызове в методе реакции на сообщение, может вызвать бесконечный цикл или клинч, приводящие к сбою программ. Вам следует не только избегать оче- видных циклов, таких как методы реакции на сообщения, генерирую- щих то же сообщение, которое его вызывает, но избегать также пе- редачи сообщений с побочными эффектами. Например, метод Paint объекта, который вызывается в ответ на сообщения wm_Paint, оче- видно должен явным образом посылать самому себе другое сообщение wm_Paint. Но он должен также избегать других действий, дающих в результате другое сообщение wm_Paint, посылаемое, пока метод еще активен, таких как изменение размеров окна, запрещение окна или создание/уничтожение перекрывающихся окон.



Передача сообщений


-----------------------------------------------------------------

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

ObjectWindows уже обеспечивает для вас способы передачи мно- гих сообщений, которые в противном случае пришлось бы передавать вручную. Например, общим случаем для генерации сообщений является работа с управляющим элементами. Чтобы добавить строку в блок списка, Windows определяет такие сообщения как lb_AddString, а чтобы отменить выбор кнопки с зависимой фиксацией или выбрать ее - bm_SetCheck. ObjectWindows определяет методы для объектов уп- равляющих элементов (TListBox.AddString и TCheckBox.SetCheck), посылающие для вас эти сообщения, так что вам даже не нужно ду- мать об их использовании.

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



Передача сообщения


-----------------------------------------------------------------

Для передачи сообщения требуется следующее: описатель ок- на-получателя, номер сообщения и параметры Word и Longint. В при- ложении ObjectWindows описателем получателя является обычно поле HWindow интерфейсного объекта. Идентификатор сообщения - это просто константа, идентифицирующая конкретное сообщение, которое вы хотите передать (такая как wm_More или em_SetTabStops). Пара- метры в зависимости от сообщения могут быть различными.

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

Windows с помощью SendMessage обеспечивает ограниченное средство циркулярной рассылки сообщений. Если вы в качестве опи- сателя окна, в которое нужно передать сообщение, зададите $FFFF, Windows посылает сообщения всем всплывающим и перекрывающимся ок- нам в системе (не только в вашем приложении). Таким образом, вы не должны использовать циркулярную рассылку сообщений, опреде- ляемых пользователем, так как не может быть уверены, что другое приложение не определило данное сообщение каким-то другим спосо- бом.



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


-----------------------------------------------------------------

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

Если управляющий элемент имеет связанный с ним объект, вы можете просто использовать для получения идентификатора управляю- щего элемента поле HWindow объекта. Если диалоговый блок имеет связанный с ним объект, вы можете вызвать метод SendDlgItemMsg объекта диалогового блока, для которого задаются идентификатор управляющего элемента, идентификатор сообщения и два параметра сообщения. В большинстве приложений ObjectWindows для вас досту- пен любой их этих подходов.

Если по каким-то причинам у вас нет ни объекта диалогового блока, ни доступного объекта управляющего элемента, вы можете послать сообщение управляющему элементу в диалоговом окне с по- мощью функции API Windows SendDlgItemMessage, которая воспринима- ет в качестве параметров описатель диалогового блока, идентифика- тор управляющего элемента, идентификатор сообщения и два парамет- ра сообщения.



Поле Result


-----------------------------------------------------------------

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

Например, когда пользователь пытается восстановить окно из состояния пиктограммы, ему посылается сообщение wm_QueryOpen. По умолчанию wm_QueryOpen возвращает булевское значение True (не ноль). Если вы хотите иметь окно, которое всегда выводится в виде пиктограммы, то вы можете ответить на сообщение wm_QueryOpen и установить Result в 0. Это означает, что окно не может быть отк- рыто:

procedure TIconWindow.WMQueryOpen(var Msg: TMessage); begin Msg.Result := 0; end;



Поля параметров


-----------------------------------------------------------------

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

WParam

Параметр WParam типа Word обычно содержит описатель, иден- тификатор (например, идентификатор управляющего элемента) или бу- левское значение. Например, параметр WParam сообщения wm_SetCursor содержит описатель окна, в котором находится курсор. Уведомляющие сообщения управляющего элемента, такие как bn_Clicked, содержат в WParam идентификатор соответствующего уп- равляющего элемента. wm_Enable использует WParam для булевского значения, указывающего, разрешено или запрещено соответствующее окно.

LParam

Параметр LParam типа Longint обычно содержит значение-указа- тель двух переменных размером в слово, таких как координаты x и y. Например, параметр LParam сообщения wm_SetText указывает на строку с завершающим нулем, содержащую устанавливаемый текст. Со- общения "мыши", такие как wm_LButtonDown, используют LParam для записи координат события "мыши". Благодаря вариантным частям за- писи сообщения, LParamLo содержит x-координату, а LParamHi - y-координату.



Способ, предлагаемый ObjectWindows


-----------------------------------------------------------------

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

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



Уведомление порождающего объекта


-----------------------------------------------------------------

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

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

type TMyWindow = object(TWindow) . . . procedure IDMyControl(var Msg: TMessage); virtual id_First + id_MyControl; end;

procedure TMyWindow.IDMyControl(var Msg: TMessage); begin { реакция на сообщение } end;

В качестве смещения в диапазоне сообщений, используемых для реакции на уведомления управляющих элементов, ObjectWindows опре- деляет константу id_First.

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



Уведомления управляющих элементов


-----------------------------------------------------------------

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

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

type TBeepButton = object(TButton) procedure BNClicked(var Msg: TMessage); virtual nf_First + bn_Clicked; end;

procedure TBeepButton.BNClicked(var Msg: TMessage); begin MessageBeep(0); end;

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



Уведомления управляющих элементов и порождающих объектов


-----------------------------------------------------------------

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

Например, с учетом приведенного выше объекта TBeepButton вы можете также уведомлять порождающее окно с помощью добавления вы- зова DefNotificationProc:

procedure TBeepButton.BNClicked(var Msg: TMessage); begin MessageBeep(0); DefNotificationProc(Msg); end;

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



Уведомляющие сообщения


-----------------------------------------------------------------

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

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



Вызов наследуемых методов


-----------------------------------------------------------------

Предположим, например, что вы создали новый оконный объект и хотите, чтобы он в дополнение к другим обычно выполняемым дейс- твиям он давал звуковой сигнал при щелчке в окне левой кнопкой "мыши". Все, что вам нужно сделать - это вызов в вашем новом ме- тоде наследуемого метода TWindow.WMLButtonDown:

procedure TBeepWindow.WMLButtonDown(var Msg: TMessage); begin inherited WMLButtonDown(Msg); MessageBeep(0); end;

В данном случае неважно, размещаете ли вы вызов наследуемого метода WMLButtonDown перед или после вызова MessageBeep. Вам нуж- но решить, должен ли ваш метод вызывать наследуемые действия пе- ред специальной обработкой или после нее (на основе того, нужны ли вам параметры сообщения, или требуется их изменить).

Следует иметь в виду, что вы можете изменять параметры Msg перед вызовом наследуемого метода. Однако делать это следует ак- куратно, так как передача параметров вне диапазона может привес- ти к тому, что ваша программа вызовет сбой Windows, особенно если эти параметры содержат описатели или указатели. Если вы будете аккуратны, изменение Msg может оказаться полезным, но нужно учи- тывать, что это может быть также опасным.



Вызов процедур, используемых по умолчанию


-----------------------------------------------------------------

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

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

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

procedure TBeepEdit.WMChar(var Msg: TMessage); begin MessageBeep(0); DefWndProc(Msg); end;

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



Замена поведения по умолчанию


-----------------------------------------------------------------

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

procedure TBeepEdit.WMChar(var Msg: TMessage); begin MessageBeep(0); end;

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



Чем отличаются контексты устройства?


-----------------------------------------------------------------

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

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



Что содержится в контексте устройства?


-----------------------------------------------------------------

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

Перо, например, это просто удобный способ интерпретации ха- рактеристик линий на устройстве. Графическая линия между двумя точками имеет три характеристики: ширину, цвет и стиль (например, пунктирная линия и линия из точек), которые совместно можно расс- матривать как "перо". Эти логические группы изобразительных ха- рактеристик значительно упрощают графику в Windows.

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



Цвет


-----------------------------------------------------------------

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



Функции изображения текста


-----------------------------------------------------------------

Функция рисования текста использует для рисования заданный текущий шрифт контекста дисплея. Функция TextOut рисует текст в заданной точке. TextOut выравнивает текст в зависимости от теку- щих значений флагов форматирования текста. По умолчанию происхо- дит выравнивание слева. Текущий метод выравнивания можно посмот- реть с помощью функции GetTextAlign и установить с помощью функ- ции SetTextAlign.

Функция TextOut - это самая часто используемая функция рисо- вания текста. Используя установленные по умолчанию флаги формати- рования текста, данный метод Paint рисует выравненный слева мас- сив символов, левый верхний угол которого имеет координаты (10,15).

procedure TMyWindow.Paint(PaintDC: HDC; var PaintINfo: TPaintStruct); var MyTextString: array[020] of Char; begin StrCopy(MyTextString, 'Hello, World'); TextOut(PaintDC, 10, 15, MyTextString, StrLen(MyTextString)); end;

+-------------------------------- | | (10, 15) | * Hello Word | |

Рис. 17.3 Результат выполнения функции TextOut.



Функции рисования линий


-----------------------------------------------------------------

Функции рисования линии используют для рисования заданное текущее перо контекста дисплея. Большинство линий рисуется с ис- пользованием функций MoveTo и LineTo. Эти функции воздействуют на атрибут контекста дисплея - текущую позицию. Если использовать аналогию с карандашом и листом бумаги, то текущая позиция это точка, где карандаш касается бумаги.

Функции MoveTo и LineTo

Функция MoveTo перемещает текущую позицию в заданные коорди- наты. Функция LineTo рисует линию из текущей позиции к точке с заданными координатами. Заданные координаты затем становятся те- кущей позицией. Следующий метод Paint рисует линию от (100,150) до (10,15).

procedure TMyWindow.Paint(PaintDC: HDC; var PaintINfo: TPaintStruct); begin MoveTo(PaintDC, 100, 150); LineTo(PaintDC, 10, 15); end;

+-------------------------------- | | (10, 15) | * | \ | \ | \ | \ | * (100, 150)

Рис. 17.4. Результат выполнения функции LineTo.

Функция PolyLine

Функция Polyline рисует последовательность линий, соединяю- щих заданные точки. По действию она аналогична выполнению после- довательности функций MoveTo и LineTo, однако, Polyline выполняет эту операцию намного быстрее и никак не воздействует на текущую позицию пера. Следующий метод Paint рисует прямой угол.

procedure TMyWindow.Paint(PaintDC: HDC; var PaintInfo: TPaintStruct); var Points: array[02] of TPoint; begin Points[0].X:=10; Points[0].Y:=15; Points[1].X:=10; Points[1].Y:=150; Points[2].X:=100; Points[2].Y:=150; Polyline(PaintDC, @Points, 3); end;

+-------------------------------- | | (10, 15) | * | | | | | | | | | +-----* (100, 150)

Рис. 17.5. Результат выполнения функции Polyline.

Функция Arc

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

Следующий метод Paint рисует верхнюю четверть окружности с началом в (40,25) и окончанием в (10,25), используя ограничиваю- щий прямоугольник (10,10), (40,40), начальную точку (0,0) и ко- нечную точку (50,0). Действие производится даже в том случае, ес- ли заданная начальная и конечная точка не лежат на дуге.

procedure TMyWindow.Paint(PaintDC: HDC; var PaintInfo: TPaintStruct); begin Arc(PaintDC, 10, 10, 40, 40, 50, 0, 0, 0); end;



Интерфейс с графическими устройствами


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

Для предоставления приложениям графических функциональных возможностей Windows имеет набор функций, называемый интерфейсом с графическим устройством - GDI. GDI можно представить себе как графическую машину, которую используют приложения Windows для отображения и манипулирования графикой. Функции GDI предоставляют вашему приложению возможности рисования, которые не зависят от используемого дисплея. Например, вы можете использовать одни и те же функции для организации вывода на дисплей EGA, на дисплей VGA и даже на принтер PostScript.

Аппаратная независимость реализуется через использование драйверов устройств, которые переводят функции GDI в команды, воспринимаемые используемым устройством вывода. Это означает, что вам не нужно беспокоиться о том, как конкретное устройство рабо- тает с графическим образом. Вы сообщаете драйверу, что нужно что-то сделать, и он работает с устройством как нужно.



Графические функции GDI


-----------------------------------------------------------------

Данный раздел описывает различные вызовы API, которые вы мо- жете использовать для рисования изображений в окне.



Инструментальные средства рисования


-----------------------------------------------------------------

Контекст дисплея управляет отображением графики на экране. Для иного способа отображения графики можно изменить инструмен- тальные средства, с помощью которых создается изображение. Атри- буты инструментальных средств задают проявление изображений с по- мощью функций GDI, например, LineTo, Rectange и TextOut. Перья задают внешний вид линий, кисти задают внешний вид закрашенных областей и шрифт задает внешний вид изображаемого текста.

Для задания атрибутов инструмента программа Windows выбирает логический инструмент в контекст дисплея. Логический инструмент создается вашей программой путем заполнения полей определенной записи, TLogPen, TLogBrush или TLogFont. Текущий инструмент - это инструмент, определенный в Windows, представляющий самый общие варианты атрибута, например, непрерывное черное перо, серая кисть или системный шрифт.



Использование изобразительных инструментальных средств


-----------------------------------------------------------------

Контекст дисплея позволяет вам рисовать в окне и, кроме это- го, он содержит инструментальные средства рисования: перья, кис- ти, шрифты и палитры, которые вы используете для рисования текста и изображений. При рисовании линии в контексте дисплея линия вы- водится со следующими атрибутами текущего пера: цвет, стиль (неп- рерывная, пунктирная и т.п.) и толщина. При закрашивании области она выводится со следующими атрибутами текущей кисти: образец и цвет. При рисовании текста в контексте дисплея он выведется с ат- рибутами текущего шрифта: шрифт (Modern, Roman, Swiss и т.п.), размер, стиль (наклонный, жирный и т.п.). Палитра содержит набор текущих доступных цветов.

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

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

var NewPen, OldPen: HPen; TheDC: HDC; begin { задать ширину пера 10 } NewPen := CreatePen(ps_Solid, 10, RGB(0, 0, 0)); TheDC := GetDC(AWindow^.HWindow); OldPen := SelectObject(TheDC, NewPen); { выполнить черчение } SelectObject(TheDC, OldPen); ReleaseDC(AWindow^.HWindow, TheDC); DeleteObject(NewPen); end;

Как показано в данном примере, новый инструмент рисования должен быть создан, а затем удален. Подобно контексту дисплея, элементы хранятся в памяти Windows. Если их не удалить, это при- водит к потерям памяти и возможности возникновения сбоя. Как и для контекста дисплея, вы должны хранить описатели инструменталь- ных средств рисования в переменных типа HPen, HBrush, HFont и HPalette.


Функция Windows DeleteObject удаляет инструментальные средс- тва рисования из памяти Windows. Ни в коем случае не удаляйте инструментальные средства рисования, которые выбраны в данный мо- мент в контекст дисплея!

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

Есть два способа создания новых инструментальных средств ри- сования. Самый простой способ состоит в использовании существую- щего альтернативного инструмента, называемого основным или опор- ным. Список основных инструментальных средств приведен в Таблице 17.1.

Для установки основного инструментального средства в объекте контекста дисплея используются методы SetStockPen, SetStockBrush, SetStockFont и SetStockPalette. Например:

ThePen:=GetStockObject(Black_Pen);

Не удаляйте основные инструменты из памяти Windows, посколь- ку вы будете настраивать их. Иногда возникает ситуация, когда нет основного инструмента, который имел бы нужный вам атрибут. Напри- мер, все основные перья воспроизводят тонкие линии, а вам требу- ется толстая. В этом случае имеется два способа создания настро- енных инструментальных средств рисования. Один способ состоит в вызове функций Windows CreatePen, CreateFont, CreateSolidBrush или CreateDIBPatternBrush. Эти функции используют параметры, ко- торые описывают нужный инструмент, и возвращают описатель инстру- ментального средства, который используется в вызовах SelectObject.



Другой способ создания настроенных инструментальных средств состоит в построении описания атрибутов логического инструмента. Логический инструмент реализуется структурами данных Windows TLogPen, TLogBrush, TLogFont и TLogPalette. Например, TLogPen имеет поля для хранения толщины цвета и стиля. После создания записи данных логического инструмента, она передается в качестве параметра в CreatePenInderect, CreateBrushInderect, CreateFontInderect или CreatePalette. Эти функции возвращают опи- сатели инструментального средства которые могут быть использованы в вызовах SelectObject. В данном примере устанавливается синее перо для изображения в контексте дисплея окна:

procedure SampleWindow.ChangePenToBlue; var ALogPen: TLogPen; ThePen: HPen; begin ALogPen.lopnColor:=RGB(0, 0, 255); ALogPen.lopnStyle:=ps_Solid; ALogPen.lopnWidth.X:=0; ALogPen.lopnWidth.Y:=0; ThePen:=CreatePenInderect(@ALogPen); SelectObject(TheDC, ThePen); end;


Использование палитр


-----------------------------------------------------------------

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

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

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



Изображение фигур


-----------------------------------------------------------------

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

Функция Rectangle

Функция Rectangle рисует прямоугольник от его левого верхне- го угла к правому нижнему. Например, следующий оператор метода Paint рисует прямоугольник от (10,15) до (100,150).

Rectangle(PaintDC, 10, 15, 100, 150);

+-------------------------------- | | (10, 15) | *-----* | |#####| | |#####| | |#####| | |#####| | *-----* (100, 150)

Рис. 17.6. Результат выполнения функции Rectangle.

Функция RoundRect

Функция RoundRect рисует прямоугольник со скругленными угла- ми. Скругления углов определены как четверти эллипса. Например, следующий оператор метода Paint рисует прямоугольник от (10,15) до (100,150), углы которого будут скруглены четвертями эллипса шириной 9 и высотой 11.

RoundRect(PaintDC, 10, 15, 100, 150, 9, 11);

Функция Ellipse

Функция Ellipse рисует эллипс, задаваемый ограничивающим его прямоугольником. Следующий пример рисует эллипс в прямоугольнике от (10,15) до (110,70).

Ellipse(PaintDC, 10, 50, 100, 150);

Функции Pie и Chord

Функции Pie и Chord рисуют секторы эллипса. Они рисуют дугу, подобно функции Arc. Однако, результатом Pie и Chord будут облас- ти. Функция Pie соединяет центр эллипса с его граничными точками. Следующая функция Pie рисует верхнюю четверть круга, заключенного в прямоугольник от (10,10) до (40,40).

Pie(PaintDC, 10, 10, 40, 40, 50, 0, 0, 0);

Функция Chord соединяет две граничные точки дуги.

Chord(PaintDC, 10, 10, 40, 40, 50, 0, 0, 0);

Функция Polygon

Функция Polygon рисует непрерывную последовательность сег- ментов линий, аналогично функции Polyline, но в конце работы за- мыкает область, рисуя линию от последней заданной точки к первой заданной точке. И, наконец, он заполняет полученный многоугольник текущей кистью, используя установленный режим закрашивания много- угольника. Следующий метод Paint рисует и закрашивает прямоуголь- ный треугольник.

procedure TMyWindow.Paint(PaintDC: HDC; var PaintInfo: TPaintStruct); var Points: array[02] of TPoint; begin Points[0].X:=10; Points[0].Y:=15; Points[1].X:=10; Points[1].Y:=150; Points[2].X:=100; Points[2].Y:=150; Polygon(PaintDC, @Points, 3); end;

+-------------------------------- | | (10, 15) | * | |\ | |#\ | |##\ | |###\ | *-----* (100, 150) (10, 150)

Рис. 17.7. Результат выполнения функции Polygon.



Изображение окон


-----------------------------------------------------------------

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

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

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

После завершения работы метода Paint контекст дисплея рисо- вания автоматически освобождается.



Изобразительные средства


-----------------------------------------------------------------

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

Перья

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

Кисти

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

Шрифты

Инструментальное средство шрифта используется при изображе- ния в контексте дисплея текста. Оно задаете высоту шрифта, его ширину, семейство и имя гарнитуры.

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



Логические инструментальные средства


-----------------------------------------------------------------

Записи логических инструментов, TLogPen, TLogBrush и TLogFont, содержат поля для хранения каждого атрибута инструмен- та. Например, TLogPen.lopnColor содержит значение цвета ручки. Каждый тип записи определяет свой собственный набор атрибутов, соответствующий типу инструмента.



Логические кисти


-----------------------------------------------------------------

Вы можете создавать логические кисти с помощью функций Windows CreateHatchBrush, CreatePatternBrush, CreateDIBPatternBrush или CreateBrushInderect. Например:

TheBrush := CreateHatchBrush(hs_Vertical, RGB(0, 255, 0)); TheBrush := CreateBrushInderect(@ALogBrush);

Определение записи TLogBrush имеет следующий вид:

TLogBrush = record lbStyle: Word; lbColor: Longint; lbHatch: Integer; end;

Поле стиля, lbStyle, содержит константы, задающие стиль кис- ти:

* bs_DIBPattern указывает, что образец кисти задан аппарат- но-независимым побитовым отображением.

* bs_Hatched задает один из заранее определенных образцов штриховки (см. lbHatch).

* bs_Hollow - это пустая кисть.

* bs_Pattern использует левый верхний угол 8 на 8 элементов побитового отображения, которое находится в этот момент в памяти.

* bs_Solid - это непрерывная кисть.

Поле lbColor содержит значение цвета, аналогично записи TLogPen. Это поле игнорируется кистями со стилями bs_Hollow и bs_Pattern.

Поле lbHatch содержит целую константу, задающую образец штриховки для кисти со стилем bs_Hatched. Если стиль bs_DIBPattern, то lbHatch содержит описатель побитового отображе- ния.

----------------------------------------------------------------- Константа Результат -----------------------------------------------------------------

////////////////////////////////////// HS_BDIAGONAL ////////////////////////////////////// //////////////////////////////////////

++++++++++++++++++++++++++++++++++++++ HS_CROSS ++++++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++++++++++++++++++

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx HS_DIAGCROSS xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ HS_FDIAGONAL \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

-------------------------------------- HS_HORIZONTAL -------------------------------------- --------------------------------------

HS_VERTICAL

-----------------------------------------------------------------

Рис. 17.2 Стили штриховки для кисти.



Логические перья


-----------------------------------------------------------------

Вы можете создавать логические перья с помощью функций Windows CreatePen или CreatePenInderect. Например:

ThePen := CreatePen(ps_Dot, 3, RGB(0, 0, 210)); ThePen := CreatePenInderect(@ALogPen);

Определение записи TLogPen имеет следующий вид:

TLogPen = record lopnStyle: Word; lopnWidth: TPoint; lopnColor: Longint; end;

Поле стиля, lopnStyle, содержит константу, задающую стиль линии.

----------------------------------------------------------------- Константа Результат ----------------------------------------------------------------- PS_SOLID ------------------------------------- PS_DASH ------------------------------------- PS_DOT . PS_DASHDOT .-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. PS_DASHDOTDOT ------------- PS_NULL -----------------------------------------------------------------

Рис.17.1 Стили линий для пера.

Поле толщины, lopnWidth, содержит точку, координата x кото- рой задает толщину линии в координатах устройства. На экране VGA, если задано значение 0, то будет рисоваться линия толщиной в один элемент изображения. Значение координаты y игнорируется. Поле цвета, lopnColor, содержит значение Longint, байты которого зада- ют величины интенсивности основных цветов (красного, зеленого и синего), смешение которых и дает нужный цвет. Значение lopnColor должно иметь вид $00bbggrr, где bb - значение синего цвета, gg - значение зеленого цвета, а rr - значение красного цвета. Доступ- ный диапазон интенсивности для каждого первичного цвета от 0 до 255, или от 0 до FF в шестнадцатиричном исчислении. Следующая таблица показывает некоторые примеры значений цвета:

Примеры значений цвета Таблица 17.2 +--------------+--------------+ | Значение | Цвет | +--------------+--------------| | $00000000 | черный | | $00FFFFFF | белый | | $000000FF | красный | | $0000FF00 | зеленый | | $00FF0000 | синий | | $00808080 | серый | +--------------+--------------+

В качестве альтернативы для воспроизведения цвета можно ис- пользовать функцию RGB. RGB(0,0,0) возвратит черный цвет, RGB(255,0,0) возвратит красный и т.д.



Логические шрифты


-----------------------------------------------------------------

Вы можете создавать логические шрифты с помощью функций Windows CreateFont или CreateFontInderect.

Определение записи TLogBrush следующее:

TLogFont = record lfHight: Integer; lfWidht: Integer; lfEscapement: Integer; lfOrientation: Integer; lfWeight: Integer; lfItalic: Byte; lfUnderline: Byte; lfStrikeOut: Byte; lfCharSet: Byte; lfOutPrecision: Byte; lfClipPrecision: Byte; lfQuality: Byte; lfPitchAndFamily: Byte; lfFaceName: array[0lf_FaceSize - 1] of Byte; end;

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

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

Поле lfWidht задает нужную ширину букв в единицах устройс- тва. Если задан ноль, то коэффициент относительного удлинения сохраняется.

Для поворачиваемого текста lfEscapement задает значение в десятых долях градуса, на которое поворачивается текст против ча- совой стрелки. lfOrientation делает аналогичный поворот каждого символа.

Параметр lfWeight задает нужный вес символов. В качестве значений можно использовать константы fw_Light, fw_Normal, fw_Bold и fw_DontCare.

Для трех атрибутов шрифта - lfItalic, lfUnderline и lfStrikeOut - нужно задать ненулевые значения.

В поле lfCharSet требуется задать конкретный набор символов, ANSI_CharSet, OEM_CharSet или Symbol_CharSet. Набор символов ANSI содержится в "Руководстве пользователя по Microsoft Windows", в Приложении B. OEM_CharSet является системно-зависимым.

Поле lfOutPrecision задает, как точно создаваемый Windows шрифт должен соответствовать запросам на размеры и позиционирова- ние. Значение поля по умолчанию - Out_Default_Precis. Поле lfClipPrecision задает способ рассмотрения частично видимых сим- волов. Значение поля по умолчанию Clip_Default_Precis.


Поле lfQuality показывает как точно предоставляемый Windows шрифт соответствует запрошенным атрибутам шрифта. Может быть ус- тановлено значение Default_Quality, Draft_Quality или Proof_Quality. Для значения Proof_Quality жирные, подчеркнутые, наклонные шрифты и шрифты с надпечаткой синтезируются, даже если их нет. Поле lfPitchAndFamily задает шаг и семейство шрифта. Оно может быть результатом логической операции or между константой шага и константой семейства.

Константы шага и семейства шрифта Таблица 17.3 +---------------------+----------------------------+ | Константы шага | Константы семейства | +---------------------+----------------------------| | Default_Pitch | ff_Modern | | Fixed_Pitch | ff_Roman | | Variable_Pitch | ff_Script | | | ff_Swiss | | | ff_Decorative | | | ff_DontCare | +---------------------+----------------------------+

И, наконец, lfFaceName - это строка, которая задает запро- шенный вид букв. Если задано значение 0, то вид букв будет стро- иться на основании значений других полей TLogFont. Приведем нес- колько примеров исходного кода, определяющего записи TLogFont:

procedure MyWindow.MakeFont; var MyLogFont: TLogFont; begin with MyLogFont do begin lfHight := 30; lfWidht := 0; lfEscapement := 0; lfOrientation := 0; lfWeight := fw_Bold; lfItalic := 0; lfUnderline := 0; lfStrikeOut := 0; lfCharSet := ANSI_CharSet; lfOutPrecision := Out_Default_Precis; lfClipPrecision := Clip_Default_Precis; lfQuality := Default_Quality; lfPitchAndFamily := Variable_Pitch or ff_Swiss; StrCopy(@FaceName, 'Helv'); end; TheFont := CreateFontInderect(@MyLogFont); end;

procedure MyWindow.MakeFont; var MyLogFont: TLogFont; begin with MyLogFont do begin lfHight := 10; lfWidht := 0; lfEscapement := 0; lfOrientation := 0; lfWeight := fw_Normal; lfItalic := Ord(True); lfUnderline := Ord(True); lfStrikeOut := 0; lfCharSet := ANSI_CharSet; lfOutPrecision := Out_Default_Precis; lfClipPrecision := Clip_Default_Precis; lfQuality := Default_Quality; lfPitchAndFamily := Fixed_Pitch or ff_DontCare; StrCopy(@FaceName, 'Courier'); end; TheFont := CreateFontInderect(@MyLogFont); end;

procedure MyWindow.MakeFont; var MyLogFont: TLogFont; begin with MyLogFont do begin lfHight:=30; lfWidht:=0; lfEscapement:=0; lfOrientation:=0; lfWeight:=fw_Normal; lfItalic:=0; lfUnderline:=0; lfStrikeOut:=0; lfCharSet:=Symbol_CharSet; lfOutPrecision:=Out_Default_Precis; lfClipPrecision:=Clip_Default_Precis; lfQuality:=Proof_Quality; lfPitchAndFamily:=Fixed_Pitch or ff_Roman; StrCopy(@FaceName, 'Rmn'); end; TheFont:=CreateFontInderect(@MyLogFont); end;


Модификация палитры


-----------------------------------------------------------------

Есть два способа изменения элементов логической палитры. Функция SetPaletteEntries берет те же самые аргументы, что и GetPaletteEntries и меняет заданные элементы на те, на которые указывает третий аргумент. Обратите внимание на то, что произве- денные изменения не отражаются в системной палитре до вызова RealizePalette, и их не видно до перерисовки области клиента. Функция AnimatePalette воспринимает те же аргументы, что и SetPaletteEntries, но используется для быстрых изменений палитры приложения, и они немедленно становятся видимыми. При вызове AnimatePalette элементы палитры с полем peFlags установленным в константу pc_Reserved будут заменены на соответствующие новые элементы, и это найдет немедленное отражение в системной палитре. На другие элементы это никак не повлияет.

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

GetObject(ThePalette, SizeOf(NumEntries), @NumEntries); if NumEntries >= 10 then begin GetPaletteEntries(ThePalette, 0, 10, @PaletteEntries); for i:=0 to 9 do begin PaletteEntries[i].peRed:=PaletteEntries[i].peRed+40; PaletteEntries[i].peGreen:=PaletteEntries[i].peGreen-40; PaletteEntries[i].peBlue:=PaletteEntries[i].peBlue-40; end; AnimatePalette(ThePalette, 0, 10, @PaletteEntries); end;

Вместо AnimatePalette мы могли бы использовать:

SetPaletteEntries(ThePalette, 0, 10, @PaletteEntries); RealizePalette(ThePalette);

и затем перерисовать окно, чтобы увидеть изменения цветов.



Обрезание областей


-----------------------------------------------------------------

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



Основные инструментальные средства


-----------------------------------------------------------------

Основные инструментальные средства создаются функцией GDI GetStockObject. Например:

var TheBrush: HBrush begin TheBrush:=GetStockObject(LtGray_Brush); . . . end;

где LtGray_Brush - это целая константа, определенная в модуле WinTypes в ObjectWindows. Приведем список всех имеющихся констант основного инструментального средства:

Основные инструменты рисования Таблица 17.1 +-------------------+------------------+------------------------+ | Кисти | Перья | Шрифты | +-------------------+------------------+------------------------| | White_Brush | White_Pen | OEM_Fixed_Font | | LtGray_Brush | Black_Pen | ANSI_Fixed_Font | | Gray_Brush | Null_Pen | ANSI_Var_Font | | DkGray_Brush | | System_Font | | Black_Brush | | Device_Default_Font | | Null_Brush | | System_Fixed_Font | | Hoolow_Brush | | | +-------------------+------------------+------------------------+

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



Отображение графики в окнах


-----------------------------------------------------------------

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

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



Побитовая графика


-----------------------------------------------------------------

Действительная поверхность контекста дисплея называется по- битовым отображением (битовым массивом). Побитовые отображения представляют конфигурацию памяти конкретного устройства. Следова- тельно, они зависят от вида адресуемого устройства. Это создает проблему, поскольку охраненные для одного устройства побитовые отображения будут несовместимы с другим устройством. GDI имеет ряд средств для разрешения этой проблемы, включая аппаратно-неза- висимые побитовые отображения. Имеются следующие функции GDI, ко- торые создают побитовые отображения: CreateCompatibleDC, CreateCompatibleBitmap и CreateDIBitmap. Имеются следующие функ- ции GDI по манипулированию побитовыми отображениями: BitBlt, StretchBlt, StretchDIBits и SetDIBitsToDevice.



Работа с контекстом дисплея


-----------------------------------------------------------------

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

type TMyWindows = object(TWindow) TheDC: HDC; . . . end;

Чтобы получить для окна контекст дисплея, вызовите функцию Windows GetDC:

TheDC := GetDC(HWindow);

Затем вы можете выполнить операцию изображения в контексте дисплея. Вы можете использовать описатель контекста дисплея в графических функциях Windows:

LineTo(TheDC, Msg.LParamLo, Msg.LParamHi);

Как только вы закончите работу с контекстом дисплея, освобо- дите его с помощью вызова функции ReleaseDC:

ReleaseDC(HWindow, TheDC);

Не вызывайте GetDC в строке дважды, не включив между вызова- ми вызов ReleaseDC. В итоге это приведет к сбою системы при рабо- те программы, так как она исчерпает все доступные контексты дисп- лея.



Реакция на изменения палитры


-----------------------------------------------------------------

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

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

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



Режимы отображения


-----------------------------------------------------------------

Очень трудно выбрать устройство рисования, когда заранее не- известно, какое устройство будет использоваться для отображения. Большинство приложений игнорируют эту проблему и предполагают, что вполне удовлетворительно будет работать единица рисования по умолчанию (один элемент изображения). Однако, некоторые приложе- ния требуют, чтобы отображение точно воспроизводило размеры нуж- ного образа. Для таких приложений GDI допускает различные режимы отображения, некоторые из которых не зависят от аппаратуры. Каж- дый из методов распределения имеет свою единицу размерности и систему координатной ориентации. Режим распределения по умолчанию устанавливает начало координат в левом верхнем углу контекста дисплея с положительным направлением оси X вправо и положительным направлением оси Y вниз. Каждый контекст дисплея имеет атрибуты распределения для интерпретации задаваемых вами координат.

Иногда нужно транслировать логические координаты, используе- мые вами для рисования, в физические координаты побитового отоб- ражения. Для большинства приложений начало координат для экрана - это его левый верхний угол, но для окна началом координат будет левый верхний угол области клиента. Некоторые окна прокручивают свою поверхность клиента так, что начало координат не будет даже находиться в области клиента. Некоторые функции GDI работают только в конкретной системе координат, поэтому преобразование ко- ординат просто необходимо. В GDI имеется ряд функций для подобно- го пересчета координат: ScreenToClient, ClientToScreen, DPToLP и LPToDP.



Рисование с палитрами


-----------------------------------------------------------------

После реализации палитры вашего приложения, оно может осу- ществлять рисование с использованием его цветов. Цвета палитры можно задавать прямо или косвенно. Для прямого задания цвета ис- пользуется индекс палитры, TColorRef. Индекс палитры TColorRef есть значение типа Longint, где старший байт установлен в 1, а индекс элемента логической палитры содержится в двух младших бай- тах. Например, $01000009 задает девятый элемент логической палит- ры. Это значение можно использовать везде, где ожидается аргумент TColorRef. Например:

ALogPen.lopnColor := $01000009;

Если ваше дисплейное устройство допускает использование пол- ного 24-битового цвета без системной палитры, то использование индекса палитры неоправданно ограничивает вас цветами вашей логи- ческой палитры. Чтобы избежать этого ограничения, вы можете за- дать цвет палитры косвенно, используя относительное значение па- литры TColorRef. Относительное значение TColorRef почти совпадает с абсолютным значением RGB TColorRef, но байт старшего разряда установлен в 2. Три младших байта содержат значение цвета RGB. Например, $020000FF задают значение чистого красного цвета. Если устройство поддерживает системную палитру, то Windows подберет максимально соответствующий цвет RGB логической палитры. Если устройство не поддерживает системную палитру, то TColorRed ис- пользуется так, как если бы он задавал явное значение RGB.



Рисование в окнах


-----------------------------------------------------------------

Для рисования любого текста или изображения в объекте окна сначала нужно получить контекст дисплея. После рисования контекст дисплея нужно освободить. (В одном сеансе Windows доступны только пять элементов контекста дисплея.) Вы можете использовать описа- тель контекста дисплея в качестве аргумента любой графической функции Windows.

Вызов графических функций окна

Одно из правил GDI состоит в том, что для работы функций не- обходимо в качестве аргумента задавать контекст дисплея. Обычно вы будете вызывать эти функции из методов типа окна. Например, TextOut - это функция рисования текста на контексте дисплея в за- данном месте:

TheDC := GetDC(HWindow); TextOut(TheDC, 50, 50, 'Sample Text', 11); ReleaseDC(HWindow, TheDC);



Стратегия графики


-----------------------------------------------------------------

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

Вы можете выбрать один из двух возможных вариантов. Первый подход состоит в выделении нескольких графических методов и при динамическом рисовании вызывать их из метода Paint. Другой под- ход, показанный в примере Главы 3, состоит в хранении данных, от- носящихся к графическому контексту окна, в полях объекта этого окна. Эти данные могут включать, например, координаты, формулы и побитовые распределения. Затем метод Paint повторно вызывает гра- фические функции, которые нужны для преобразования этих данных в изображения.

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



Управление контекстом дисплея


-----------------------------------------------------------------

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



Установка палитры


-----------------------------------------------------------------

Логические палитры являются инструментами рисования, такими же как перья и кисти, описанные в разделе "Инструментальные средства изображения". Для создания логической палитры использу- ется функция CreatePalette, которая берет указатель на запись данных LogPalette, создает новую палитру и возвращает ее описа- тель, который передается в SelectPalette для выбора палитры в контекст дисплея. Запись TLogPalette содержит поля номера версии Windows (в настоящее время $0300), число элементов палитры и мас- сив элементов палитры. Каждый элемент палитры - это запись типа TPaletteEntry. Тип TPaletteEntry имеет три байтовых поля для спе- цификации цвета (peRed, peGreen и peBlue) и одно поле для флагов (peFlags).

GetStockObject(Default_Palette) создает палитру по умолча- нию, состоящую из 20 цветов, которые всегда присутствуют в палит- ре системы.

После выбора палитры в контекст дисплея с помощью SelectPalette, он должен до использования "реализовать" ее. Это делается с помощью функции Windows RealizePalette:

ThePalette := CreatePalette(@ALogPalette); SelectPalette(TheDC, ThePalette, 0); RealizePalette(TheDC);

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



Запись на устройство вывода


-----------------------------------------------------------------

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

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

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

Нужно помнить о том, что контекст устройства представляет собой только часть устройства, на котором вы реально рисуете. Хо- тя вы можете рассматривать вывод в терминах всего окна (рамки, меню, области клиента) или печатаемой страницы, контекст устройс- тва охватывает только ту часть, где вы рисуете - область клиента окна или печатаемую часть страницы.

Контекст устройства - это элемент, управляемый Windows (ана- логично оконному элементу, только контекст устройства не имеет соответствующего объекта ObjectWindows).



Запрос палитры


-----------------------------------------------------------------

Windows определяет функцию, которая позволяет вам получать информацию относительно палитры. GetPaletteEntries воспринимает индекс, диапазон и указатель на TPaletteEntry и заполняет буфер заданными элементами палитры.