ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы.



 

Часть 4

Глава 3. Заполнение окна
Интерфейсом пользователя с программой управляют меню, блоки диалога и всплывающие окна. Однако, программа все еще не делает ничего интересного. В данной части руководства вы возьмете MyProgram, которая представляет собой лишь заготовку программы, и преоб
разуете ее в полезное диалоговое графическое приложение. Сначала вы нарисуете текст в головном окне MyProgram. Затем MyProgram будет преобразована в полное графическое приложение, которое позволит вам рисовать линии в головном окне. Далее программа будет
 усовершенствована для перерисовки изображения, изменения толщины линий и, наконец, сохранения созданного графического изображения в файле для последующей повторной загрузки.
Что такое контекст дисплея ?
Можно рассматривать контекст дисплея в качестве элемента, который представляет поверхность для рисования в окне. Контекст дисплея служит в Windows для рисования в окне произвольного текста или графики. Windows управляет контекстом дисплея в своем собстве
нном пространстве памяти, но приложение также отслеживает его, храня регулятор к контексту дисплея. Как и регулятор окна, регулятор контекста дисплея представляет собой число, которое идентифицирует корректный контекст дисплея Windows.
Поскольку контекст дисплея предназначен для рисования в окне, вы создадите новое поле объекта для вашего объекта головного окна, с именем DragDC, для хранения регулятора контекста дисплея. DragDC будет иметь тип HDC, специальный эквивалент стиля типа Win
dows для стандартного типа Word в Turbo Pascal.
Для рисования в окне нужно сначала получить контекст дисплея. Это делается вызовом функции Windows GetDC из одного из типов методов окна, непосредственно перед рисованием объекта на экране: 
     DragDC:=GetDC(HWindow);
Теперь DragDC можно использовать в качестве параметра в вызовах графических функций Windows, которые требуют регулятор контекста дисплея. Приведем несколько примеров: 
     TextOut(DragDC, 20, 20, 'Sample Text', 11);
     LineTo(DragDC, 30,45);
После рисования текста или графики вы обязательно должны освободить контекст дисплея. Очень важно освободить полученный вами контекст дисплея сразу же после осуществления рисования.
     ReleaseDC(HWindow, DragDC);
Если не освободить все полученные контексты дисплея, то скоро произойдет превышение разрешенного их числа (для внутренней среды Windows имеется ограничение - пять), и ваше приложение аварийно завершится. Обычно компьютер при этом зависает. Если ваше прил
ожение сбивается на рисовании третьего или четвертого объекта, то первое, что нужно проверить, это неосвобожденность контекста дисплея.
Контекст дисплея обслуживает ряд важных функций рисования. Во-первых, он предотвращает рисование текста или графики вне поверхности окна. Во-вторых, он управляет выбором и удалением инструментов рисования: ручек, кистей и шрифтов. Образец выбора другой р
учки приводится в графическом примере в данном разделе ниже. Но сначала давайте нарисуем текст.
Шаг 3: Рисование текста в окне
Для рисования текста в головном окне MyProgram нужно следовать модели рисования с контекстом дисплея из предшествующего раздела, сначала - контекст дисплея, затем - его освобождение. Чтобы было интереснее, будем рисовать текст в ответ на нажатие левой кн
опки на мыши, рассмотренном на Шаге 2. На этот раз вместо появления блока сообщения реакцией будет рисование текста, который будет показывать координаты точки окна, в которой произошло нажатие.
Например, '(20,30)' это точка на 20 пикселей правее левого верхнего угла поверхности рисования окна и на 30 пикселей ниже. Рисование будет производится справа от точки нажатия. Этот пример, кроме того, позволит вам ознакомится с системой координат Window
s. Рассмотрите Рис.3.1.
Рис.3.1. MyProgram рисует текст в указанных пользователем точках
Помните о том, что событие нажатия левой кнопки на мыши генерирует сообщение wm_LButtonDown, которому соответствует метод реакции WMLButtonDown.
Записи сообщений
На Шаге 2 вы видели, что аргумент Msg метода реакции на сообщение несет полезную информацию, например, точку в которой произошло нажатие кнопки на мыши. Msg это запись TMessage с полями для хранения параметра типа Longint (длинное целое), lParam, и парам
етра Word, wParam. Идентификаторы lParam и wParam те же самые, что и соответствующие поля собственной структуры сообщения Windows, TMsg.
TMessage, кроме того, определяет поля для хранения подполей lParam и wParam. Например, Msg.lParamLo хранит слово низшего порядка lParam, а lParamHi хранит слово высшего порядка. В большинстве случаев вы будете использовать поля wParam, lParamLo и lParamH
i.
Для случая WMLButtonDown, Msg.lParamLo хранит координату x выбранной точки, а Msg.lParamHi хранит координату y. Т.о. для модификации WMLButtonDown для рисования текста, показывающего координаты выбранной точки, следует преобразовать Msg.lParamLo и Msg.lP
aramHi в строки и объединить их с двумя скобками и запятой, с целью получения результата в виде '(25,34)'. Для форматирования строки в данном примере используется функция Windows WVSPrintF. Функция WVSPrintF рассматривается в Главе 2 "Справочного руковод
ства по Windows".
После получения итоговой строки ее можно нарисовать в выбранной точке, передав в вызов функции Windows TextOut саму строку, S, координаты, Msg.lParamLo и Msg.lParamHi, и длину строки, StrLen(S). Нужно быть уверенным в том, что контекст дисплея получен, д
о рисования и не забыть освободить его после. 
Обратите внимание на то, что Windows предполагает заканчивание строки пустым символом (нулевым байтом). Детальное описание использования таких строк содержится в Главе 13 "Руководства программиста".
procedure TMyWindow.WMLButtonDown(var Msg: TMessage);
var
 DC: HDC;
 S: array[0..9] of Char;
begin
 WVSPrintF(S, '(%d,%d)', Msg.LParam);
 DC:=GetDC(HWindow);
 TextOut(DC, Msg.LparamLo, Msg.LParamHi, StrLen(S));
 ReleaseDC(HWindow, DC)
end;
Вызовы Str и StrCopy осуществляют преобразование типа. Str преобразует координаты типа Word в тип string. StrCopy (процедура из модуля String) стиль строк Pascal в строки, заканчивающиеся пустым символом.
Очистка экрана
К приложению рисования текста можно добавить дополнительную функцию очистки экрана. Обратите внимание на то, что каждый раз при изменении размеров окна, его прикрытии и последующем проявлении нарисованный текст удаляется. Однако, вы можете захотеть форси
ровать очистку экрана в качестве результата выбора варианта меню или каких либо иных действий пользователя, например, нажатия кнопки на мыши.
Будем очищать окно в ответ на нажатие правой кнопки на мыши. Для реализации этого переопределим wmRButtonDown так, чтобы вызывалась процедура Windows, InvalidateRect, которая вызывает перерисовку всего окна. Но наше окно еще не знает, как ей перерисовыва
ть себя, поэтому просто произойдет очистка области пользователя:
procedure TMyWindow.WMRButtonDown(var Msg: TMessage);
begin
 InvalidateRect(HWindow, nil, True);
end;
Данный исходный код можно найти в файле STEP3.PAS.
Шаг 4: Рисование линий в окне
После того, как мы рассмотрели модель рисования (получение контекста дисплея. рисование, освобождение контекста дисплея), можно можно использовать ее в более сложном диалоговом графическом приложении. В следующих нескольких шагах мы построим простую прог
рамму рисования в головном окне.
Вы должны проделать следующие этапы:
1. Реакцией на нажатие левой кнопки на мыши и "протягивания" ее    должно стать рисование линии.
2. Реакцией на нажатие правой кнопки на мыши должно быть проявление диалога, позволяющего пользователю сменить толщину линии.
3. Осуществить автоматическую перерисовку содержимого окна, путем       сохранения точек и перерисовку их в ответ на сообщение paint.
Для реализации этих этапов сначала изучим модель протягивания Windows, а затем реализуем простую графическую программу рисования.
Модель протягивания
Было бы полезно вначале рассмотреть модель событий мыши в Windows.    Вы уже видели, что нажатие левой кнопки на мыши приводит к возникновению сообщения wm_LButtonDown и вызову метода WMLButtonDown. Ранее в данном руководстве мы реагировали на нажатие ле
вой кнопки на мыши проявлением блока сообщения и рисованием текста на экране. Вы также видели, что нажатие правой кнопки на мыши приводит к возникновению сообщения wm_RButtonDown и вызову метода WMRButtonDown. Реакцией на нажатие правой кнопки на мыши бы
ло стирание экрана. Подобные реакции покрывают лишь ситуации единичных нажатий на кнопки. Многие диалоговые программы Windows требуют нажать кнопку на мыши и не отпуская ее протянуть указатель по экрану для рисования линий или прямоугольников, или для пе
ремещения рисунка по экрану в конкретное местоположение. Для нашей графической программы рисования нужно обработать события протягивания и отреагировать на них рисованием линий.
Вы сделаете в качестве реакции на несколько дополнительных сообщений. Когда пользователь протягивает мышь в другую точку окна, принимается сообщение wm_MouseMove, а когда он отпустит левую кнопку на мыши поступит сообщение wm_LButtonUp. В обычном случае 
в окно поступает одно сообщение wm_LButtonDown, за которым идет серия сообщений wm_MouseMove (по одному на каждую протянутую точку), за которыми следует одно сообщение wm_LButtonUp. Типичная графическая программа Windows будет реагировать на wm_LButtonDo
wn инициализацией процесса рисования (получая наряду с другими вещами контекст дисплея). Реакцией на wm_MouseMove будет рисование или сдвиг изображения, а процесс рисования будет закончен как реакция на поступление сообщения wm_LButtonUp (с освобождением
 контекста дисплея).
В следующей таблице суммированы самые общие сообщения событий, связанных с мышью.
Таблица 3.1. Общие сообщения событий, связанных с мышью
----------------------------------------------------------------
Сообщение           Событие
----------------------------------------------------------------
wm_LButtonDown      Пользователь нажал левую кнопку на мыши.
wm_RButtonDown      Пользователь нажал правую кнопку на мыши.
wm_MouseMove        Пользователь протягивает мышь.
wm_LButtonUp        Пользователь отпустил левую кнопку на мыши.
wm_RButtonUp        Пользователь отпустил правую кнопку на мыши.
wm_LButtonDblClk    Пользователь дважды нажал левую кнопку на мыши.
wm_RButtonDblClk    Пользователь дважды нажал правую кнопку на
                    мыши.
----------------------------------------------------------------
Реакция на сообщения о протягивании
В следующей таблице приведены действия, которыми вы должны прореагировать на сообщения о протягивании для создания программы рисования линий.
     Таблица 3.2. Используемые на Шаге 4 сообщения
----------------------------------------------------------------
Сообщение           Реакция
----------------------------------------------------------------
wm_LButtonDown      Очистить экран. Получить контекст дисплея и
                    сохранить его в DragDC. Расположить инструмент
                    рисования в выбранной точке.
wm_MouseMove        Нарисовать линию от предыдущей точки в текущую
                    точку.
wm_LButtonUp        Освободить DragDC.
----------------------------------------------------------------
Как это уже было сказано ранее, за wm_LButtonDown всегда следует wm_LButtonUp, а между ними могут находиться, а могут и нет, сообщения wm_MouseMove. В конце концов, нажатая кнопка когда нибудь должна же быть отпущена ! Далее, каждый раз при получении кон
текста дисплея вы позднее должны освободить его. В данном случае вы получаете единственный контекст дисплея для всего процесса рисования, между временем нажатия пользователем левой кнопки на мыши и ее отпусканием.
Получение и высвобождение контекста дисплея является критическим фактором надлежащего функционирования вашей графической программы. Однако, можно предусмотреть дополнительные меры безопасности. Определим новое булево поле объекта для TMyWindow, типа голо
вного окна, с именем ButtonDown. WMLButtonDown будет устанавливать его значение True, а WMLButtonUp значение False. Теперь можно проверить значение ButtonDown до получения или освобождения контекста дисплея.
Рассмотрим три метода протягивания мыши:
procedure TMyWindow.WMLButtonDown(var Msg: TMessage);
begin
 InvalidateRect(HWindow, nil, True);
 if not ButtonDown then
 begin
  ButtonDown:=True;
  SetCapture(HWindow);
  DragDC:=GetDC(HWindow);
  MoveTo(DragDC, Mgs.lParamLo, Msg.lParamHi);
 end;
end;
procedure TMyWindow.WMMouseMove(var Msg: TMessage);
begin
 if not ButtonDown then
  LineTo(DragDC, Mgs.lParamLo, Msg.lParamHi);
end;
procedure TMyWindow.WMLButtonUp(var Msg: TMessage);
begin
 if ButtonDown then
 begin
  ButtonDown:=False;
  ReleaseCapture;
  ReleaseDC(HWindow, DragDC);
 end;
end;
MoveTo и LineTo являются графическими функциями API Windows, которые сдвигают текущую позицию рисования и рисуют линию в текущую позицию, соответственно. Для корректной работы им требуется регулятор контекста дисплея, DragDC. Помните о том, что вы рисует
е не непосредственно в окне, а в его контексте дисплея.
SetCapture и ReleaseCapture являются функциями Windows, которые гарантирую корректную посылку сообщений wm_MouseMove. Например, если мышь протягивается вне окна, сообщение будет wm_MouseMove будет послано головному окну, а не текущему.
Следует внести следующие изменения в определение объекта TMyWindow с заголовками метода для WMMouseMove и WMLButtonUp (им будет соответствовать код файла STEP4.PAS):
procedure WMLButtonUp(var Msg: TMessage); virtual wm_First+
     wm_LButtonUp;
procedure WMMouseMove(var Msg: TMessage); virtual wm_First+
     wm_MouseMove;
Шаг 5: Изменение толщины линии
В данный момент вы можете рисовать только тонкие линии. Но программы рисования обычно позволяют изменять толщину рисуемых линий. На самом деле, когда вы это делаете, вы изменяете не саму толщину линий, а толщину инструмента, который используется для рисо
вания линий. Ручки и кисти, шрифты и палитры являются инструментальными средствами рисования, включенными в контекст дисплея. На данном этапе мы научимся пользоваться новыми инструментами контекста дисплея, которые дадут MyProgram возможность устанавлива
ть новую толщину линий.
Для обеспечения механизма изменения пользователем толщины линий вы будете использовать диалог ввода (тип TInputDialog). Рис.3.2 показывает использование диалога ввода.

Рис.3.2. Изменение толщины линии
Трассировка толщины линии
Для изменения толщины рисуемых линий сначала нужно более глубоко некоторые особенности графики Windows и особенно контекста дисплея.
Ранее мы уже рассматривали средства рисования, которые использовались для воспроизведения графики и текста в окне: ручки, кисти и шрифты. Эти инструментальные средства рисования являются элементами окна, описания которых хранятся в памяти Windows, в отли
чие от элементов интерфейса. Приложение осуществляет доступ к инструментам рисования через регуляторы, как и к окнам. Поскольку инструментальные средства рисования не представлены объектами ObjectWindows, ваши программы несут ответственность за их создан
ие и удаление из памяти Windows, когда вы уже использовали.
Средство рисования можно рассматривать в качестве кисти художника, а контекст дисплея в качестве холста. После того, как он создал свои инструменты рисования (кисть), он получает контекст дисплея (холст) и выбирает надлежащий инструмент рисования. Аналог
ично, программа Windows должна выбрать инструменты рисования в контекст дисплея. Т.о. как же вы можете рисовать текст и линии в ваших окнах без выбора какого-либо инструмента рисования ? Все контексты дисплея имеют по умолчанию набор инструментов: тонкая
 черная ручка, толстая черная кисть и системный шрифт. На данном шаге мы выберем другую, более толстую ручку для рисования в окне.
Первый этап состоит в обеспечении диалога ввода для MyProgarm. Добавим StdDlg к оператору uses. Для использования совместимых с Windows функций манипулирования строк, используем string. Начало файла вашей программы должно теперь выглядеть следующим образ
ом:
     program MyProgram;
     uses String, WinTypes, WinProcs, WObjects, StdDlgs;
     ...
Далее, нужно добавить поле объекта к TMyWindow для хранения регулятора ручки, которая будет использоваться для рисования графики. В данной программе ограничимся рисованием и отображением линий только одной заданной толщины в каждый момент времени. Соотве
тствующая данной толщине ручка будет хранится в новом поле TMyWindow  с именем ThePen. Также напишем метод SetPenSize для создания новой ручки и удаления старой. Объявление объекта TMyWindow должно теперь выглядеть следующим образом:
type
 PMyWindow=^TMyWindow;
 TMyWindow=object(TWindow)
  DragDC: HDC;
  ButtonDown: Boolean;
  ThePen: HPen;
  PenSize: Integer;
 constructor Init(Aparent: PWindowObject; ATitle: PChar);
 destructor Done; virtual;
 function CanClose: 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_MouseMove;
 procedure WMRButtonDown(var Msg: TMessage); virtual wm_First+
     wm_RButtonDown;
 procedure SetPenSize(NewSize: Integer); virtual;
end;
Для инициализации новых полей данных нужно модифицировать конструктор Init для установки ручки и переписать деструктор Done для отмены ручки. Не забыть вызвать в новом методе унаследованные методы:
constructor TMyWindow.Init(AParent: PWindowsObject; ATitle: PChar);
begin
 TWindow.Init(AParent, ATitle);
 ButtonDown:=False;
 PenSize:=1;
 ThePen:=CreatePen(ps_Solid, PenSize, 0);
end;
destructor TMyWindow.Done;
begin
 DeleteObject(ThePen);
 TWindow.Done;
end;
Теперь изменим метод WMLButtonDown для выбора текущего пера (ThePen) во вновь полученный контекст дисплея. Если ThePen равен нулю, то специальная ручка не создавалась и нужно использовать ручку по умолчанию (тонкую). Аналогично MoveTo и MessageBox, Selct
Object является функцией Windows API.
procedure TMyWindow.WMLButtonDown(var Msg: TMessage);
begin
 InvalidateRect(HWindow, nil, True);
 if not ButtonDown then
 begin
  ButtonDown:=True;
  SetCapture(HWindow);
  DragDC:=GetDC(HWindow);
  SelectObject(DragDC, ThePen)
  MoveTo(DragDC, Mgs.lParamLo, Msg.lParamHi);
 end;
end;
Вышеизложенный метод выбирает уже созданную ручку в контекст дисплея. Однако, для создания ручки нужно написать следующий метод SetPenSize:
procedure TMyWindow.SetPenSize(NewSize: Integer);
begin
 DeleteObject(ThePen);
 ThePen:=CreatePen(ps_Solid, NewSize, 0);
 PenSize:=NewSize;
end;
Вызов функции Windows CreatePen является способом создания инструментальной ручки Windows заданной толщины. Регулятор для ручки хранится в ThoPen. Удаление предыдущей ручки является очень важным моментом. Без этого действия верхняя память Windows будет о
чень медленно использоваться и нет способа поправить ситуацию.
Проведение диалога ввода
Нажатие правой клавиши на мыши является очень удобным способом изменения толщины линии. Давайте переопределим метод WMRButtonDown для вызова диалога ввода, одного из диалогов ObjectWindows. Диалог ввода является простым блоком диалога, и осуществляет вво
д одной строки текста. Для его использования не нужно изменять TInputDialg или любой из его методов.
Поскольку диалог ввода появляется лишь на короткое время и вся его обработка может быть проведена лишь одним методом, нет необходимости в определении поля объекта в TMyWindow. Он может существовать как локальная переменная метода WMRButtonDown. Вы сконст
руируете и выведете объект диалога ввода в методе WMRButtonDown.
Поскольку объект диалога ввода был сконструирован с Init, его можно запускать как модальный диалог вызовом метода Execute. Execute аналогичен Create для объекта окна в том смысле, что он создает соответствующий элемент интерфейса объекта. Однако, обработ
ка Execute заканчивается только после того, как пользователь закончил диалог выбором OK или Cancel. Если пользователь выбрал OK, то InputText заполняется вводом пользователя вызовом метода GetText из TInputDialog. Поскольку запрашивается номер толщины, в
веденный текст нужно преобразовать в число и передать его в вызов SetPenSize. Т.о. каждый раз, когда пользователь выбирает новую толщину линии, удаляется старая ручка и создается новая.
Procedure TMyWindow.WMRButtonDown(var Msg: TMessage);
var
 InputText: array[0..5] of Char;
 NewSize, ErrorPos: Integer;
begin
 if not ButtonDown then
 begin
  Str(PenSize, InputText);
  if Applicatin^.ExecDialog(New(PInputDialog,
   Init(@Self, 'Line Thickness', 'Input a new thickness:',
    InputText, SizeOf(InputText))))=id_OK then
  begin
   Val(InputText, NewSize, ErrorPos);
   if ErrorPos=0 then SetPenSize(NewSize);
  end;
 end;
end;
В качестве последнего этапа нужно убедиться в удалении итоговой ручки из памяти Windows до завершения работы приложения. Для этого перепишем деструктор Done для удаления ручки.
destructor TMyWindow.Done;
begin
 DeleteObject(ThePen);
 TWindow.Done;
end;
Шаг 6: Автоматически регенерируемые изображения
Вы, вероятно, будете удивлены, узнав, что что графика и текст, которые вы нарисовали в окне, используя функции Windows TextOut и LineTo исчезают при изменении размеров окна или его перекрывании. Куда же пропадает изображение ?
Лучше было бы спросить, "Где оно было сначала ?". Вы еще не записывали текст или линии в какой-нибудь тип переменной. Поскольку графические данные поступают в Windows  через функции Windows, их нельзя получить назад при необходимости перерисовки. Для тог
о, чтобы иметь окно с возможностью перерисовки изображения, нужно записать графические данные в некоторый иной тип структуры. Для решения этой задачи хорошо подходит объект. Объекты могут хранить простые и достаточно сложные графические изображения, и мо
гут легко обрабатываться в качестве полей объекта головного окна.
Модель рисования
Когда пользователь вашего приложения изменяет размеры вашего окна или перекрывает его, его требуется изменять или другими словами перерисовывать. ObjectWindows автоматически вызывает метод Paint типа вашего окна для перерисовки в нем изображения. Наследу
емый из TWindow Paint ничего не делает. Для Paint пишут код рисования содержимого окна. В действительности, Paint вызывается при первом появлении окна. Paint отвечает за изменение текущего содержания дисплея.
Есть одно главное отличие между рисованием графики по методу Paint и другими методами, как, например, в виде реакции на действия мышью. Используемый для рисования контекст дисплея передается в параметр PaintDC поэтому вашей программе нет необходимости ег
о получать и освобождать. Однако, вам придется в PainDC повторно выбирать инструмент рисования.
Для перерисовки изображения вашего окна вам придется повторить действия, которые привели к его созданию с помощью DragDC, но уже с использованием PaintDC. Визуальный эффект будет такой, как будто изображение рисуется пользователем впервые, аналогично вос
произведению аудио-записи концерта. Оно живое или это работает ObjectWindows. Но сначала нужно сохранить графику в качестве объекта, чтобы его можно было перерисовать в методе Paint.
Сохранение изображений в качестве объектов
Давайте хранить нарисованную линию как набор точек в поле объекта TMyWindows с именем Points. Для этого набора будет использоваться тип TCollection, из модуля WObjects. В каждом случае Poins будет содержать текущее изображение в виде набора точек. В случ
ае необходимости перерисовки окна, хранящиеся в Points данные будут использоваться для повторного рисования линии. 
Следующим вопросом, который ждет ответа, будет "Что такое линия ?". На Шаге 4 вы видели, что нарисованная линия есть ни что иное, как набор точек, передаваемых из Windows программе через сообщение wm_MouseMove. Для представления точек нужен собственный т
ип объекта. Определим DPoint для хранения в полях объекта координат x и y. 
Объекты TCollection имеют способность динамического роста при добавлении пользователем дополнительных элементов. Они прекрасно подходят для хранения произвольного числа линий и точек. В действительности одна линия может содержать тысячи точек. 
Приведем определение объекта DPoint:
type
 PDPoint=^TDPoint;
 Doint=object(TObject)
  X, Y: Integer;
  constructor Init(AX, AY, Integer);
end;
constructor TDPoint.Init(AX, AY: Iteger);
begin
 X:=AX;
 Y:=AY;
end;
DPoint не задает какого-либо нового поведения, он нужен в качестве объекта для хранения в дальнейшем потоков данных. Нужно сконструировать Points в TMyWindow.Init и разрушить в TMyWindow.Done. Для этого нужно переписать унаследованные от TWindows Init и 
Done.
constructor TMyWindow.Init(AParent: PWindowsObject; ATitle: PChar);
begin
 TWindow.Init(AParent, ATitle);
 ButtonDown:=False;
 ThePen:=CreatePen(ps_Solid, 1, 0)
 PenSize:=1;
 Points:=New(PColection, Init(50, 50)); 
end;
destructor TMyWindow.Done;
begin
 TWindow.Done;
 DeleteObject(ThePen);
 Dispose(Points, Done);
end;
Головное окно MyProgram содержит набор точек в поле Points. По мере рисования пользователем линий нужно преобразовать их в объекты и добавить в Points. Затем, если требуется перерисовка окна, нужно вернуться к Points и перерисовать каждую из его точек. 
Для хранения линий в качестве объектов нужно внести соответствующие изменения в WMLButtonDown и WMMouseMove.
procedure TMyWindow.WMLButtonDown(var Msg: TMessage);
var
 APoint: PDPoint;
begin
 Points^.DeleteAll;
 InvalidateRect(HWindow, nil, True);
 if not ButtonDown then
 begin
  ButtonDown:=True;
  SetCapture(HWindow);
  DragDC:=GetDC(HWindow);
  if ThePen <> 0 then SelectObject(DragDC, ThePen)
  MoveTo(DragDC, Mgs.lParamLo, Msg.lParamHi);
  Points^.Insert(New(PDPoint, Init(Msg.lParamLo, Msg.LParamHi)));
 end;
end;
procedure TMyWindow.WMMouseMove(var Msg: TMessage);
var APoint: PDPoint;
begin
 if ButtonDown then
 begin
  LineTo(DragDC, Mgs.lParamLo, Msg.lParamHi);
  Points^.Insert(New(PDPoint, Init(Msg.lParamLo, Msg.LParamHi)));
 end;
end;
Не требуется внесения каких-либо изменений в MWLButtonUp.
Перерисовка сохраненных изображений
Теперь TMyWindow хранит свою текущую линию и нужно научить программу перерисовывать ее по команде. Это будет команда Paint. Давайте напишем метод Paint для TMyWindow, который повторяет действия WMLButtonDown, WMMouseMove и WMLButtonUp. Итеративно обрабат
ывая все точки набора, Point будет рассматривать точки в той последовательности, в которой они были введены пользователем. Приведем текст метода Paint:
procedure TMyWindow.Pain(PaintDC: HDC; var PaintInfo: TPaintStruct);
var First: Boolean;
procedure DrawLine(P: PDPoint); far;
begin
 if First then MoveTo(PaintDC, P^.X, P^.Y)
 else LineTo(PaintDC, P^.X, P^.Y);
 First:=False;
end;
begin
 SelectObject(PaintDC, ThePen);
 First:=True;
 Points^.ForEach(@DrawLine);
end;


Яндекс цитирования