Глава 2. Заполнение окна


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

Шаг 2: Отображение текста в окне


Step 1Basic App
Step 2Text
Step 3Lines
Step 4Menu
Step 5About Box
Step 6Pens
Step 7Painting
Step 8Streams
Step 9Printing
Step 10Palette
Step 11BWCC
Step 12Custom ctrls

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

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

Вывод в контексте дисплея


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

    Примечание: Подробно о контексте дисплея рассказывается в Главе 17 "Интерфейс с графическими устройствами".

Что такое контекст дисплея?


    Контекст дисплея имеет три основных функции отображения:

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

    Поскольку, чтобы рисовать в окне при буксировке "мыши", вам необходим контекст дисплея, создайте в объекте основного окна новое поле с именем DragDC, которое будет содержать описатель контекста дисплея. DragDC имеет тип HDC, который эквивалентен типу Word.

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


Получение контекста дисплея


    Чтобы отобразить что-то в окне, вы должны сначала получить контекст дисплея. Это можно сделать, вызвав в одном из методов типа непосредственно перед отображением на экране функцию Windows GetDC:

     
     DragDC := GetDC(HWindow);

Использование контекста дисплея


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

     TextOut(DragDC, 20, 20, 'Пример текста', 11);
     LineTo(DragDC, 30, 45);

Освобождение контекста дисплея


    После отображения текста или графики вы должны освободить контекст дисплея (как только закончите отображение).

     ReleaseDC(HWindow, DragDC);
    Если вы не освободите весь полученный контекст дисплея, то скоро исчерпаете его, что приводит обычно к зависанию компьютера. Если ваша программа не может что-либо отобразить, проверьте освобождение контекстов дисплея.

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

    Примечание: GDI и вопросы использования памяти освещаются в Главе 17 "Интерфейс с графическим устройством".

Координаты Windows


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

    При отображении вас касаются только координаты в контексте дисплея. Windows обеспечивает, чтобы контекст дисплея попадал в область клиента окна.

    Примечание: Область клиента - это часть окна внутри рамки.

    На этом шаге Step отобразит текст, показывающий координаты той точки в окне, где вы щелкнули кнопкой "мыши". Например, '(20,30)' - это точка, отстоящая на 20 элементов изображения вправо и на 30 элементов изображения вниз от верхнего левого угла поверхности отображения. Вы можете отображать прямо в той точке, где щелкнули "мышью". Это показано на Рис. 2.1.


Рис. 2.1 Отображение текста в точке нажатия кнопки "мыши".

Параметры сообщений


    Щелчок левой кнопкой "мыши" генерирует сообщение wm_LButtonDown, который вы перехватываете с помощью метода реакции на сообщение WMLButtonDown.

    Параметр Msg метода реакции на сообщение несет информацию о породившем сообщение событии (такую как координаты точки, где пользователь щелкнул кнопкой "мыши"). Msg - это запись TMessage, поля которой содержат параметр lParam типа Longint и параметр wParam типа Word. Идентификаторы lParam и wParam соответствуют полям в структуре сообщения Windows TMsg.

    TMessage определяют также вариантные поля, содержащие подполя lParam и wParam. Например, Msg.lParamLo содержит младшее слово lParam, а Msg.lParamHi - старшее слово. Чаще всего используются поля wParam, lParamLo и lParamHi.

    В случае WMLButtonDown Msg.lParamLo содержит x-координату точки нажатия кнопки "мыши", а Msg.lParamHi - y-координату этой точки. Таким образом, чтобы переписать WMLButtonDown для отображения координат точки нажатия кнопки, нужно преобразовать Msg.lParamLo и Msg.lParamHi в строки и, чтобы они приняли вид '(25,21)', конкатенировать их с запятой. В примере для форматирования строки используется функция Windows WVSPrintF.

    Примечание: Слияние параметров зависит от сообщения. Подробности о каждом сообщении и его параметре вы можете узнать, воспользовавшись оперативным справочником Help.

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

     
     procedure TStepWindow.WMLButtonDown(var Msg: TMessage);

     var S: array[0..9] of Char;
     begin
       WVSPrint(S, '(%d,%d)', Msg.LParam);
       DragDC := GetDC(HWindow);
       TextOut(DragDc, Msg.LParamLo, Msg.LParamHi, S, StrLen(S));
       ReleaseDC(HWindow, DragDC);
     end;
    Примечание: Windows ожидает получения строк с завершающим нулем (конечным нулевым байтом). Подробнее эти строки описываются в Главе 18 "Руководства по языку".

Очистка окна


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

    Чтобы очистить окно в ответ на щелчок правой кнопкой "мыши", переопределите метод WMRButtonDown и вызовите в нем процедуру InvalidateRect, которая приводит к повторному отображению всего окна. Так как ваша программа пока не знает, как повторно вывести изображение, она просто очистит область клиента:

     Procedure TStepWindow.WMRButtonDown(var Msg: TMessage);
     begin
        InvelidateRect(HWindow, nil, Trut);
     end;
    Текущий исходный код вы можете найти в файле STEP02.PAS.

Шаг 3: Изображение линий в окне


Step 1Basic App
Step 2Text
Step 3Lines
Step 4Menu
Step 5About Box
Step 6Pens
Step 7Painting
Step 8Streams
Step 9Printing
Step 10Palette
Step 11BWCC
Step 12Custom ctrls

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

    На шаге 3 мы добавим следующее поведение:

    Чтобы выполнить эти шаги, изучим сначала схему буксировки Windows, а затем реализуем простую графическую программу.

Буксировка линии


    Мы уже видели, что щелчок левой кнопкой "мыши" дает в результате сообщение wm_LButtonDown и вызывает метод WMLButtonDown. В шаге 1 ваша программа отвечала на щелчки левой кнопкой "мыши", выводя окна сообщений. Вы могли также видеть, что щелчок правой кнопкой "мыши" давал в результате сообщение wm_RButtonDown и вызывал метод WMRButtonDown. На нажатие правой кнопки "мыши" программа отвечала очисткой окна.

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

Сообщения wm_MouseMove


    Сделать это можно путем реакции еще на несколько сообщений. Когда пользователь буксирует "мышь" в новую точку окна, Windows посылает сообщение wm_MouseMove, а когда пользователь отпускает левую кнопку "мыши" - сообщение wm_LButtonUp. Обычно окно получает одно сообщение wm_LButtonDown, за которым следует последовательность сообщений wm_MouseMove (по одному на каждую промежуточную точку буксировки) и одно сообщение wm_LButtonUp.

    Типичная графическая программа Windows реагирует на сообщение wm_LButtonDown инициализацией процесса рисования (получая, кроме всего прочего, контекст дисплея). На сообщение wm_LButtonUp она реагирует завершением процесса рисования (освобождая контекст дисплея).

Реакция на сообщения буксировки


    Нужно помнить о том, что после wm_LButtonDown всегда следует сообщение wm_LButtonUp (с промежуточными сообщениями wm_MouseMove или без них). Таким образом, каждый раз, когда вы получаете контекст дисплея, вы можете позднее освободить его.

    Для правильного функционирования программы Windows очень важным является освобождение каждого получаемого вами контекста дисплея. Однако вы можете добавить еще одно более надежное средство. Определите в TStepWindow новое булевское поле - тип основного окна с именем ButtonDown и обеспечьте его инициализацию в TStepWindow.Unit значением False. Затем вы можете проверять перед получением и освобождением контекста дисплея значение ButtonDown.

    Приведем три метода обработки буксировки "мыши":

     
     procedure TStepWindow.WMLButtonDown(var Msg: TMessage);
     begin
        InvalidateRect(HWindow, nil, True);
        if not ButtonDown then
        begin
           ButtonDown := True;
           SetCapture(HWindow);
           DragDC := GetDC(HWindow);
           MoveTo(DragDC, Msg.lParamLo, Msg.lParamHi);
        end;
     end;

     procedure TStepWindow.WMMouseMove(var Msg: TMessage);
     begin
        if ButtonDown then
              LineTo(DragDC, Msg.lParamLo, MsglParamHi);
     end;

     procedure TStepWindow.WMLButtonUp(var Msg: TMessage);
     begin
       if ButtonDown then
       begin
          ButtonDown := False;
          ReleaseCapture;
          ReleaseDC(HWindow, DragDC);
       end;
     end;

Изображение точек и линий


    В API Windows имеются графические функции MoveTo и LineTo, которые, соответственно, перемещают текущую позицию рисования и рисуют линию до текущей позиции. Для правильной работы функций требуется указание описателя контекста дисплея DragDC. Нужно помнить о том, что вы рисуете не непосредственно в окне, а в его контексте дисплея.

Перехват "мыши"


    Передачу Windows соответствующих сообщений wm_MouseMove обеспечивают функции SetCapture и ReleaseCapture. Например, если вы буксируете "мышь" за пределы окна, Windows все равно будет посылать сообщения основному, а не смежному с ним окну, в которое она попала. Перехват "мыши" обеспечивает также поступление в ваше окно сообщения от "мыши", так что оно будет знать о прекращении рисования даже если "мышь" перемещается в другом окне.

    Нужно изменить определение объекта для TStepWindow с заголовками метода для WMMouseMove и WMLButtonUp:

     
     procedure WMLButtonUp(var Msg: TMessage); virtual wm_First +
                 wm_LButtonUp;
     procedure WMLMouseMove(var Msg: TMessage); virtual wm_First +
                 wm_LMouseMove;
    Пример полученного исходного кода вы найдете в файле STEP03A.PAS.

Изменение размера пера


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

    Для реализации механизма выбора пользователем размера пера мы используем диалоговое окно (типа TInputDialog). Это окно вида:


Рис. 2.2 Задание новой толщины линии с помощью диалогового окна ввода.

Отслеживание размера пера


    Чтобы изменить толщину изображаемых линий, вам нужно получить сначала несколько более глубокое представление о графике Windows и о контексте в частности.

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

    Для получения графики и текста в окне Windows использует несколько изобразительных средств: перья, кисти и шрифты. Эти изобразительные средства представляют собой элементы, хранимые в памяти Windows и не отличающиеся от видимых элементов экрана, таких как окна и управляющие элементы. Ваша программа может обращаться к изобразительным средствам с помощью описателей (как это имеет место в случае окон). Так как ObjectWindows не использует для представления изобразительных средств объекты, вашим программам не нужно создавать их и удалять из памяти Windows при завершении работы с ними.

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

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

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

Получение пера нового размера


    Сначала нужно обеспечить способ выбора нового размера пера. В простейшем случае это можно сделать с помощью диалогового окна ввода модуля OStdDlgs. Добавьте модуль OStdDlgs в оператор uses программы. Чтобы использовать совместимые с Windows функции работы со строками, укажите также модуль Strings. Начало программного файла должно выглядеть таким образом:

     program Steps;

     uses Strings, WinTypes, WinProcs, OWindow, OStdDlgs;
       .
       .
       .
Выполнение диалогового окна ввода
    Диалоговое окно ввода - это простое диалоговое окно, которое выводит подсказку и возвращает одну введенную строку текста. Вы можете использовать его без модификации TInputDialog или других методов.

    Щелчок правой кнопкой "мыши" дает удобный способ вывода параметра для изменения толщины пера. Давайте переопределим метод WMRButtonDown для вывода нового диалогового окна ввода.

    Так как диалоговое окно ввода появляется только на короткое время, а вся обработка выполняется одним методом, вам нет необходимости определять его как поле TStepWindows. Оно может существовать в виде локальной переменной метода WMRButtonDown. Все построение и отмену объекта диалогового окна вы можете выполнять в рамках метода WMRButtonDowm.

    Когда Init построит объект диалогового окна ввода, вы можете выполнить его как режимное диалоговое окно, вызвав ExecDialog. ExecDialog проверяет успешность выполнения конструктора Init и создает объект диалогового окна, соответствующий элементу экрана, выполняя затем диалоговое окно. Обработка для ExecDialog завершается только после того как пользователь закрыл диалог, щелкнув "мышью" на командной кнопке OK (Подтверждение) или Cancel (Отмена).

    Если пользователь щелкнул "мышью" на командной кнопке OK, InputText заполняется полученным от пользователя текстом, вызывая метод GetText из TInputDialog. Так как вы запрашиваете номер толщины, возвращаемый текст нужно преобразовать в число и передать его в вызове SetPenSize. Таким образом, каждый раз, когда пользователь выбирает новую толщину линии, старое перо удаляется и создается новое.

     
     procedure TStepWindow.WMLButtonDown(var Msg: TMessage);
     var
        InputText: array[0..9] of Char;
        NewSize, ErrorPos: Integer;
     begin
        if not ButtonDown then
        begin
           Str(PenSize, InputText);
           if Application^.ExecDialog(New(PInputDialog,
               Init(@Self, 'Толщина линии',
               'Введите новую толщину:', InputText,
               SizeOf(InputText))) = id_Ok then
        begin
           Val(InputText, NewSize, ErrorPos);
           if ErrorPos = 0 then SetPenSize(NewSize);
        end;
     end;
     end.
Добавление полей объекта
    Далее добавим в TStepWindow новое поле для хранения описателя пера, которое вы будете использовать для рисования графики. В данной программе в каждый момент времени вы можете рисовать и выводить на экран линии только одной толщины. Соответствующее этой толщине перо хранится в новом поле TStepWindow с именем ThePen. Вы напишете также метод SetPenSize, создающий новое перо и удаляющий старое. Теперь описание объекта TStepWindow должно принять следующий вид:

     type
       PStepWindow = ^TStepWindow;
       TStepWindow = object(TWindow)
         DragDC: HDC;
         ButtonDown, HasChanged: Boolean;
         ThePen: HPen;
         PenSize: Integer;
         constructor Init(AParent: PWindowsObject;
                           ATitle: PChar);
         destructor Done; virtual;
         function CanClopse: Boolean: virtual;
         procedure WMLButtonDown(var Msg: TMessage); virtual
                     wm_First + wm_LButtonDown;
         procedure WMLButtonUp(var Msg: TMessage); virtual
                     wm_First + wm_LButtonUp;
         procedure WMMouseMove(var Msg: TMessage); virtual
                     wm_First + wm_LMouseMove;
         procedure WMRButtonDown(var Msg: TMessage); virtual
                     wm_First + wm_RButtonDown;
         procedure SetPenSize(NewSize: Integer); virtual;
     end;
Инициализация полей
    Чтобы инициализировать новые поля, вам нужно модифицировать конструктор Init для установки пера и переопределить деструктор Done для его отмены. Не забудьте вызвать в новых методах наследуемые методы:

     constructor TStepWindow.Init(AParent: PWindowsObject;
                   ATitle: PChar);
     begin
        Inherited Init(AParent, ATitle);
        ButtonDown := False;
        HasChanged := False;
        PenSize := 1;
        ThePen := CreatePen(ps_Solid, Pensize, 0);
     end;

     destructor TStepWindow.Done;
     begin
       DeleteObject(ThePen);
       inherited Done;
     end;
Изображение линий
    Теперь изменим метод WMLButtonDown для выбора текущего пера (ThePen) во вновь полученном контексте дисплея. Аналогично MoveTo и MessageBox, SelectObject является функцией API Windows.

     procedure TStepWindow.WMLButtonDown(var Msg: TMessage);
     begin
        if not ButtonDown then
        begin
            ButtonDown := True;
            SetCapture"(HWindow);
            DragDC := GetDC(HWindow);
            SelectObject(DragDC, ThePen);
            MoveTo(DragDC, Msg.lParamLo, Msg.lParamHi);
        end;
     end;
    Указанные методы выбирают в контексте дисплея уже созданное перо. Однако для создания пера нужно написать следующий вызываемый WMRButtonDown метод SetPenSize:

     
     procedure TStepWindow.SetPenSize(NewSize: Integer);
     begin
        DeleteObject(ThePen);
        ThePen := Create(ps_Solid, NewSize, 0);
        PenSize := NewSize;
     end;
    Вызов функции Windows CreatePen - это один из способов создания пера Windows заданной толщины. Описатель пера записывается в ThePen. Очень важным шагом является удаление старого пера. Отсутствие такого шага приведет к неверному использованию памяти Windows.

    На шаге 5 и 6 вы создадите собственное диалоговое окно и объект пера и используете их для более эффективного графического отображения.