ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП |
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы. |
Часть 19 Глава 16. Отладка объектно-ориентированной прикладной программы, использующей ObjectWindows Пример объектно-ориентированной программы данной главы был написан с помощью класса ObjectWindows, который существенно облегчает программирование в Windows. К программам Windows данной главы применимы также все методы, описанные в Главе 15 "Отладка станд артной программы Турбо Паскаля". Примерами программ служат программа TDODEMO и TDODEMOB (B обозначает версию программы с ошибками). Программа TDODEMOB содержит несколько ошибок, которые вы выявите при работе с данной главой. Перед тем, как продолжить изучение, полезно запустить из Windows програму TDODEMO и немного поэкспериментировать с ней, чтобы получить представление, как она работает. Вы можете использовать для запуска TDODEMO.EXE команду FileіRun (ФайліВыполнение) мене джера программ или добавить ее к программной группе в качестве пиктограммы. О программе TDODEMOB - это программа ObjectWindows, позволяющая рисовать на экране различными цветами с помощью "мыши". Когда вы нажимаете левую кнопку "мыши" и перемещаете "мышь", то программа рисует на экране линию. Нажав правую кнопку "мыши", вы можете очистить о кно. Программа TDODEMO имеет строку меню, которая позволяет вам выбрать один из 4 цветов: Red (красный), Green (зеленый), Blue (голубой) или Black (черный). Вы можете рисовать, нажимая кнопку "мыши", перемещая "мышь" и освобождая кнопку "мыши". Программа легко выполняет эту задачу с помощью библиотеки ObjectWindows и динамических виртуальных методов. Динамический виртуальный метод - это метод с присвоенным е му числовым идентификатором. Поскольку Турбо Паскаль для Windows определяет имена сообщений Windows, как числовые константы, вы можете использовать номера сообщений Windows в качестве идентификатора динамического метода. При этом ObjectWindows может вызывать данный метод, когда окно , для которого объявлен метод, получает совпадающее с идентификатором метода сообщение. Если нет метода, идентификатор которого совпадает с идентификатором метода, ObjectWindows вызывает используемую по умолчанию процедуру окна. Например, чтобы создать метод, который отвечает на сообщения WM_MOUSEMOVE, вы можете определить метод в объекте окна, который выглядит следующим образом: procedure WMMouseMove(var Msg: TMessage); virtual WM_MOUSE; Как вы можете видеть, идентификатор WM_MOUSEMOVE можно присоединить к процедуре с помощью оператора virtual <идентификатор>, который непосредственно следует за описанием процедуры. Параметры процедуры окна Windows wParam и lParam содержит тип TMessage. Эти параметры часто содержат дополнительную информацию о сообщении, например, где позиционируется "мышь". В следующих нескольких разделах поясняется, как работает программа TDODEMOB. В нее преднамеренно внесены ошибки, которые вы сможете обнаружить позднее. Полезно также запустить Турбо Паскаль для Windows и открыть файл TDODEMOB.PAS, после чего вы сможете с ледовать по коду программы. Определение типа ScribbleWindow Тип ScribbleWindow определяется следующим образом: type ScribbleWindow = object(TWindow) HandleDC: HDC; ButtonDown: Boolean; { флаг левой кнопки } constructor Init(aParent: PWindowsObject; aTitle: PChar); procedure WMLButton(var Msg: TMessage); virtual WM_LBUTTONDOWM; procedure WMMouseMove(var: Msg: TMessage); virtual WM_LBUTTONUP; procedure WMMouseMove(var: Msg: TMessage); virtual WM_MOUSEMOVE; procedure WMMouseMove(var: Msg: TMessage); virtual WM_RBUTTONDOWM; end; Тип ScribbleWindow определяет объект окна, который отвечает на следующий ввод пользователя: - перемещения "мыши"; - нажатие и освобождение левой кнопки "мыши"; - нажатие правой кнопки "мыши". Имеются два экземпляра переменных HandleDC и ButtonDown, которые содержат класс устройства и состояние кнопки "мыши" соответственно. Init Конструктор Init вызывает стандартный конструктор TWindow.Init, что позволяет окну вести себя как любой другой объект TWindow, а также инициализировать переменную ButtonDown значением False. WMLButtonDown Когда пользователь нажимает в окне Scribble кнопку "мыши" и собирается рисовать, окно получает сообщение WM_LBUTTONDOWN, которое приводит к тому, что ObjectWindows вызывает метод WMLButtonDown (так как он имеет идентификатор WM_LBUTTONDOWN). Метод WMLBut tonDown перемещает перо в текущую позицию "мыши" и устанавливает переменную ButtonDown, чтобы указать, что кнопка нажата. В Windows имеются также дополнительные вызовы данной процедуры, о чем будет рассказано ниже. WMMouseMove Когда пользователь начинает перемещать курсор по окну, окно получает сообщение WM_MOUSEMOVE, которое приводит к тому, что ObjectWindows вызывает метод WMMouseMove. Если пользователь нажал левую кнопку "мыши", то программа рисует при каждом перемещении "м ыши" линию. Если пользователь не нажимает кнопку "мыши", то ничего не происходит. WMLButtonUp Когда пользователь заканчивает рисование и освобождает кнопку "мыши", окно получает сообщение WM_LBUTTONUP, которое в свою очередь приводит к тому, что ObjectWindows вызывает метод WMLButtonUp. Программа присваивает переменной ButtonDown значение False и освобождает класс усройства, связанный с данным окном. WMRButtonDown Когда пользователь нажимает правую кнопку "мыши" и очищает экран, ObjectWindows вызывает метод WMRButtonDown, который, в свою очередь, вызывает процедуру окна UpdateWindow. Вызов данной процедуры предназначен для очистки окна. Добавление цвета с помощью CSribbleWindow Теперь программа имеет объект, позволяющий пользователю рисовать с помощью "мыши". Чтобы добавить цвет, можно было бы включить в тип ScribbleWindow другие методы, но более полезно задействовать используемое в объектно-ориентированном программировании сре дство наследования и создать вместо этого из ScribbleWindow новый объект. Новый объект называется CScribbleWindow и описывается следующим образом: CScribbleWindow = object(ScribbleWindow) thePen: HPen; constructor Init(aParent: PWindowsObject: aTitle: PChar); destructor Done: wirtual; procedure SelectRedPen(var Msg: TMessage); virtual cm_First + RedMenu; procedure SelectGreenPen(var Msg: TMessage); virtual cm_First + GreenMenu; procedure SelectBluePen(var Msg: TMessage); virtual cm_First + BlueMenu; procedure SelectBlackPen(var Msg: TMessage); virtual cm_First + BlackMenu; procedure WMLButtonDowd(var Msg: TMessage); virtual WMLButtonDown; procedure GetWindowClass(var AWndClass: TWndClass); virtual; function GetCalssName: PChar; virtual; end; Переменная thePen Новый объект содержит экземпляр переменной thePen, в которую записывается текущий цвет пера. Используя эту переменную, методы CSribbleWindow устанавливают цвет пера и затем выбирают его в класс устройства. Конструктор Init Конструктор Init вызывает сначала конструктор своего предка. Затем с помощью установки поля окна Attr.Menu в соответствии с описателем меню, возвращаемый вызовом LoadMenu, он присоединяет к окну меню. LoadMenu загружает меню из файла ресурсов. Наконец, к онструктор инициализирует перо черным цветом. Деструктор Done Деструктор Done вызывает деструктор своего предка и отменяет содзданное перо. Подпрограммы цвета пера Имеются также четыре метода, устанавливающие цвет пера, удаляя текущее перо и создавая новое перо с нужным цветом. Эти методы отличаются только устанавливаемыми цветами. Метод WMLButtonDown Все, что осталось сделать, это выбрать в соответствии с контекстом устройства перо. После этого можно рисовать. Поскольку рисование происходит после нажатия пользователем левой кнопки "мыши", необходимо заново переописать динамический виртуальный метод W MLButtonDown, чтобы он вызывался вместо исходного метода ScribbleWindow. Новая версия WML.ButtonDown вызывает сначала метод WMLButtonDown родительского объекта, а затем в соответствии с контекстом текущего устройства выбирает переменую thePen. Создание прикладной программы Чтобы создать прикладную программу, которая использует окно ColorScribble, необходимо создать объект на основе объекта TApplication ObjectWindow. Назначение этого объекта (объекта CSribbleApplication) состоит в переопределении метода InitMainWindow, благ одаря чему прикладная программа сможет создавать основное окно со свойствами окна CSribbleWindows Теперь вы знаете как работает программа и можете начать ее отлаживать. Отладка программы Если вы еще этого не сделали, запустите Турбо Паскаль для Windows, загрузите программу TDODEMOB.PAS, затем скомпилируйте и запустите ее. Когда вы нажимате кнопку "мыши" и начинаете перемещать "мышь", вы увидите, что на экране ничего не рисуется. Очевидно , где-то ошибка. Выявление первой ошибки Поскольку линии рисуются только методом WMMouseMove, имеет смысл начать поиски отсюда. Возможен один из трех вариантов: 1. WMMouswMove не вызывается при перемещении "мыши". 2. Возникают проблемы при вызове функции Windows LineTo. 3. Переменная ButtonDown не установлена в значение True. Загрузите программу в TDW. Проще всего это сделать из Турбо Паскаля, загрузив TDODEMOB.PAS в окно Edit (Редактирование), что вы уже делали, и выбрав команду RunіDebugger (ВыполнениеіОтладчик). Исключение альтернатив Первое, что нужно проверить, это вызывается ли метод WMMouseMove. Переместите курсор на ScribbleWindow.WMMouseMove и установите точку останова на операторе begin. Для выполнения программы нажмите F9. (Если программа продолжает выходить в TDW, вам, возмож но, потребуется нажать клавишу F9 несколько раз.) После вывода окна и начала работы переместите "мышь" по окну, не нажимая левую кнопку. TDW останавливает выполнение программы и возвращает вас в точку останова, которую вы установили с помощью WMMouseMove. (Чтобы из Windows пришло сообщение WM_MOUSEMOVE и это сработало, вам, возможно, придется нажать клавишу.) Метод WMMouseMove вызывается, следовательно с первым возможным случаем мы разобрались. Ошибка состоит в том, что либо не устанавливается переменная ButtonDown, либо имеются проблемы при вызове Lin eTo. Чтобы проверить эти возможности, удалите точку останова на операторе begin метода WMMouseMove, переместите курсор на две строки вниз (на вызов функции LineTo) и утановите точку останова там. Далее переместите курсор в ScribbleWindow.WMLButtonUp и установ ите другую точку останова на операторе begin этой подпрограммы. Когда вы снова запустите программу и переместите "мышь" с нажатой левой кнопкой вниз, выполнение остановится на вызове функции LineTo. Это даст вам понять, что переменная ButtonDown устанавливается правильно, и проблема состоит в вызове функции LineTo. В противном случае программа возвратится в WMLButtonUp, и что-то не так с ButtonDown. Возобновите выполнение программы, нажав клавишу F9. Если курсор нажодится в окне, нажмите левую кнопку "мыши" и переместите "мышь". Поскольку выполнение останавливается на LineTo, переменная ButtonDown должна содержать корректное значение. Проблема должн а заключаться в вызове функции LineTo. Отладка LineTo Функция LineTo воспринимает три параметра: контекста вывода (HandleDC), позицию x (Msg.LParamLo) и позицию y (Msg.LParaHi). Windows посылает в поле lParam сообщения сообщение WM_MOUSEMOVE в соответствии с текущей позицией "мыши". Переменная TMessage Msg воспринимает данный параметр в своих собственных полях lParam, LParamTo, значении x, LParamHi и значении y. (TMessage - это тип, предусмотренный в ObjectWindows.) Контекст вывода, чтобы он был допустимым, должен иметь значение > 0, а значения x и y должн ы быть меньше, чем размер окна в элементах изображения. Переместите курсор на параметр HandleDC функции LineTo и нажмите для проверки значения клавиши Ctrl-I. Как вы можете видеть, значение равно 0, что указывает на неправильность контекста (он ни с чем не связан). Поскольку вы не можете выполнять запись в не связанный контекст, вероятно в этом и состоит проблема. Когда вы закончите проверку данной переменной, нажмите клавишу Esc. За задание и установку окна для рисования несет ответственность метод ScribbleWindow.WMLButton, который должен инициализировать контекст вывода для данного окна. Если вы посмотрите на исходный код метода WMLButtonDown, то увидите, что HandleDC никогда не связывается с контекстом вывода. В приведенноми ниже исходном коде показан метод WMLButtonDown с добавленным оператором инициализации контекста. procedure ScribbleWindow.WMLButtonDown(var Msg: TMessage); begin if not ButtonDown then begin ButtonDown := Trut; { отметить кнопку "мыши", так что при перемещении "мыши" с нажатой кнопкой будет рисоваться линия } HandleDC := GetDC(HWindow), { создать контекст вывода Msg.LParamHi); для рисования при нажатой кнопке "мыши" } end; end; Проверка исправлений Удалите точку останова на вызове функции LineTo и на ScribbleWindow.WMLButtonUp. Далее запустите ColorScribble и выйдите из него, затем выйдите в TDW. Когда вы вернетесь обратно в Турбо Паскаль, переместитесь на ScribbleWindow.WMLButtonDown и добавьте оп ератор инициализации контекста. Примечание: Если вы выйдите в TDW перед выполнением и выходом из прикладной задачи, Windows будет где-либо прикладную задачу, и вы из нее не выйдите. Перед компиляцией программы обедитесь, что в него записывается отладочная информация: 1. Выберите команду OptionsіCompiler (ПараметрыіКомпилятор) и проверьте параметр Debug Information (Информация для отладки). 2. Выберите команду OptionsіLinker (ПараметрыіКомпоновщик) и проверьте параметр Debug Info in EXE (Отладочная информация в файле EXE). 3. Для сохранения установленных параметров выберите команду OptionsіSave (ПараметрыіСохранение). Теперь вы можете перекомпилировать программу (выберите команду CompileіCompile (КомпиляцияіКомпиляция) или нажмите клавиши Alt-F9). Примечание: При запуске TDW Турбо Паскаль может запросить у вас сохранения всех модулей. Чтобы сохранить их, вы можете ответить Yes (Да). Теперь, когда вы нажимаете кнопку "мыши" и перемещаете "мышь", появляется черная линия. Попробуйте рисовать различными цветами, выбирая из меню цвет пера. Красный, зеленый и голубой будут работать прекрасно, но когда вы попытаетесь изменить цвет обратно на черный, то цвет пера не изменится. Похоже, вы нашли другую ошибку. Поиск ошибки назначения цвета пера Наиболее веротным местом этой ошибки является метод SCribbleWindow, который создает цвет пера SelectBlackPen. Выйдите из ColorScribble, затем снова запустите TDW и установите точку останова на CScribbleWindow.SelecrBlackPen. Затем запустите программу и в ыберите PenіBlack. TDW должен остановить выполнение на точке останова. Поскольку этого не происходит, здесь что-то неверно. Вероятно SelectBlackPen никогда не вызывается. Поскольку данная подпрограмма работает на основе таблицы динамических виртуальных методов, возможно что-то не так с ее идентификатором. Установка точки останова для окна Когда пользователь выбирает элемент меню, Windows посылает сообщение WM_COMAND тому окну, которому данное меню принадлежит. Параметр сообщения wParam содержит идентфикатор выбираемого элемента меню. Когда окно ObjectWindows получает сообщение WM_COMMAND, оно просматривает индексы динамических методов объекта окна в поиске значения cm_First+menu_id. SelectBlackPen имеет индекс cm_First+BlackMenu, где BlackMenu имеет значение 104. Чтобы определить, каким является параметр wParam командного сообщения PenіBlack, вам нужно сообщить TDW, что он должен останавливать выполнение при получении сообщения WM_COMMAND. Тогда вы можете запустить программу, сделать выбор в меню и проверить пара метр wParam, чтобы посмотреть, не совпадает ли он с константой BlackMenu. Прерывание программы с помощью клавиш Ctrl-Alt-SysRq Примечание: Если клавиши Ctrl-Alt-SysRq в вашей системе не работают, смотрите следующий раздел. Перед тем, как вы зададите прерывание программы при получении сообщения WM_COMAND, нужно прервать программу и передать управление в TDW. Это можно сделать, нажав клавиши Ctrl-Alt-SysRq. TDW возвращает вас в окно CPU (ЦП), указывая, что при выходе из прог раммы выполнялся код ядра Windows. Поскольку вы не находитесь в окне модуля программы TDODEMOB, нужно использовать для задания точки останова по сообщению обходной метод. Нажмите для удаления окна CPU. клавиши Alt-F3, затем для вывода окна Evaluate/Modify (Вычисление/Модификация) нажмите клавиши Ctrl-F4. Введите в качестве вычисляемого выражения имя описателя окна CSApp.MainWindow^.hWindow, после чего нажмите клавишу Enter. В окне результата появится числовое значение - значение описателя окна. Запишите это значение, затем для закрытия о кна Evaluate/Modify нажмите клавишу Esc. Задание точки останова Далее выберите команду ViewіWindows Messages (ОбзоріСообщения Windows), по которой выводится окно Windows Messages (Сообщения Windows). В левой верхней области показано окно, которое вы в данный момент отслеживаете. В правой верхней области показаны интересующие вас типы сообщений. Нижняя область содержит уже принятые сообщения. В данный момент все три области пусты, поскольку вы пока никаких окон не добавили. Примечание: Программа, написанная с помощью ObjectWindows, не содержит легко доступной процедуры окна, поэтому вместо нее лучше использовать описатель окна. Чтобы добавить окно, обычно следует переместить курсор в левую верхнюю область, затем начать ввод имени описателя окна. Поскольку меню присоединено к основному окну, вы хотите регистрировать приходящие в это окно сообщения. (Это единственное окно програм мы, что упрощает дело.) Описателем окна для основного окна является CSApp.MainWindow^.hWindow. Однако, поскольку модуль TDODEMOB не активен, вы должны вместо этого набрать значение описателя окна, который вы извлекли ранее, затем нажать клавиши Alt-H, чтобы сообщить TDW, что это значение представляет собой описатель, а не процедуру. Вы видите, что селективный переключатель Identify By (Идентификация) перемещается на Handle (Описатель). Наконец, нажмите для выбора OK клавишу Enter. В область добавится значение описателя. После того, как окно в программе установлено, вы должны выбирать только описатель окна. Описатель устанавливается после инициализации объекта окна. В данной программе описатель устанавливается после выполнения метода CScribbleWindow.SetupWindow. Следующим шагом является задание сообщения WM_COMMAND, на котором программа должна прерываться. Переместите курсор в правую верхнюю область, затем для вывода локального меню нажмите Alt-F10. Выберите команду Add (Добавление), наберите WM_COMMAND, нажмите Alt-B, активизируйте "кнопку" Break (Прерывание), затем нажмите клавишу Enter. В правой верхней области теперь выводится Break on mesage WM_COMMAND (Прерывание по сообщению WM_COMMAND). Если не работают клавиши Ctrl-Alt-SysRq Клавиши Ctrl-Alt-SysRq работают не на всех системах. Если в вашей системе они не работают, вам нужно завершить ColorScribble, затем для перезагрузки TDODEMOB использовать клавиши Ctrl-F2. Когда выводится окно модуля, следующим шагом будет установка в код е точки останова, благодаря чему вы можете запустить программу, инициализировать описатель окна, затем выйти обратно в TDW и установить точку останова по сообщению. Чтобы установить точку останова в окне Module, нажмите для перехода к началу файла клавиши Ctrl-PgUp. Вы устанавливаете точку останова на методе ScribbleWindow WMLButtonDown, поэтому нажмите для вывода диалогового окна Search (Поиск) клавиши Ctrl-S, набе рите ScribbleWindow, затем нажмите клавишу Enter. Когда курсор установится на имени метода, для установки точки останова на операторе begin нажмите клавишу F2. Следующим шагом будет запуск программы с помощью клавиши F9. Когда выведется окно ColorScribbleWindow, для прерывания программы и возврата в TDW нажмите левую кнопку "мыши". Удалите точку останова, нажав клавишу F2. Теперь вы готовы к тому, чтобы установить точку останова по сообщению. Вернитесь к предыдущему разделу и прочтите "Установка точки останова". Поскольку окно Module активно, и установлено CSApp.MainWindow^.hWindow, вы можете вместо численного значения зад ать имя описателя. В противном случае процесс будет тем же. Проверка wParam Вы можете возвбновить выполнение программы, нажав клавишу F9. Выберите в меню команду PenіBlack (Черный цвет пера). После того, как вы выберите черное перо, TDW останавливает выполнение и выводит на экран окно CPU, показывая, что в момент прерывания программа не выполняла ваш код. Закройте окно CPU, нажав клавиши A lt-F3. Если это необходимо, снова выведите окно Windows Messages (Сообщения Windows). Увеличьте окно до полного размера, благодаря чему вы сможете видеть в нижней области все сообщение. Вы можете видеть, что окно получает сообщение WM_COMMAND со значением в wPa ram 204 ($CC). Причиной того, что не вызывался виртуальный метод, было то, что приладная программа искала идентификатор cm_First+204, а его действительным значением было cm_First+104. Если вы измените BlackMenu на 204, то выбор черного пера будет работат ь корректно. Когда вы сделаете это изменение, описание констант в начале программы должно быть следующим: const PenWidth = 1; { толщина линии Scribble } MenuID = 100 { идентификатор меню файла ресурсов } IconID = 100 { идентификатор пиктограммы файла ресурсов } RedMenu = 101 { значение меню PenіRed } GreenMenu = 102 { значение меню PenіGreen } BlueMenu = 103 { значение меню PenіBlue } BlackMenu = 204 { значение меню PenіBlack } Проверка исправления Запустите ColorScribble и выйдите оттуда, затем выйдите в TDW. Когда вы вернетесь в Турбо Паскаль, измените определение константы BlackMenu, затем перекомпилируйте программу и запустите ее. Теперь когда вы рисуете в окне, то можете заметить другую проблему. Если при рисовании вы смещаете "мышь" за пределы окна, то при возврате в окно в другом месте вы увидите, что программу рисует линию, соединяющую то место, где вы вышли из окна, с тем мес том, где вы в него вернулись. Все, что сдесь должна делать программа - это прекращение рисования при выходе из окна и возобновление рисование при возврате в него. Итак, вы обнаружили еще одну ошибку. Поиск ошибки рисования за пределом экрана Поиск этой ошибки неплохо начать с сообщение Windows, которые получает данное окно. Выйдите из программы ColorScribb[e и загрузите в TDW программу TDODEMOB.PAS. Перед заданием каких-либо сообщений вам нужно инициализировать основное окно, после чего опис атель окна будет допустимым. Чтобы сделать это, установите точку останова на CSribbleWindow.WMLButtonDown, затем нажмите для запуска программы клавишу F9. Далее для возврата в окно Module TDW нажмите левую кнопку "мыши". Когда вы вернетесь в окно Module, удалите только что установленную точку останова. Регистрация сообщений Windows С помощью команды ViewіWindows Messages (ОбзоріСообщения Windows) выведите окно Windows Messages (Сообщения Windows) и наберите в левой области окна Windows Messages CSApp.MainWindow^.hWindow, чтобы задать описатель окна. Для выбора "кнопки" Handle (Опис атель) нажмите клавиши Alt-H, затем нажмите клавишу Enter. Затем переместите курсор в правую верхнюю область и нажмите для вывода диалогового окна Set Massage Filter (Задание фильтра сообщений) клавиши Ctrl-A. После этого установите прервывание по сообщению WM_LBUTTONUP, после чего TDW будет получать управление, когда вы закончите рисование. Вы хотите получать просматривать все приходящие сообщения, но установка WM_LBUTTONUP стирает установку Log All Messages (Регистрация всех сообщений). Все, что нужно сделать, это вывод диалогового окна Set Message Filter и в ыбор класса Log All Messages. Обнаружение ошибки Возобновите выполнение программы TDODEMOB, нажав клавишу F9. Начните рисовать, затем выведите "мышь" за пределы области пользователя и вернитесь в нее в другом месте. Чтобы уменьшить число сообщений, просто переместитесь вовне и снова вернитесь назад, за тем освободите левую кнопку "мыши", чтобы управление вернулось в TDW. Перед тем, как взлянуть на окно Windows Messages (Сообщения Windows), увеличьте его до полного размера (клавишей F5), благодаря чему вы сможете видеть больше сообщений. Когда вы посмотрите на нижнюю область окна Windows Messages, то увидите множество соо бщений WM_NCHITEST ит WM_SETCURSOR, среди которых будет сообщение WM_LBUTTONDOWN, WM_MOUSEMOVE, после них будет несколько сообщений WM_NCMOUSEMOVE, далее еще сообщения WM_MOUSEMOVE и, наконец, заключительное сообщение WM_LBUTTONUP. Похоже, что когда курс ор не находится в области пользователя, то сообщения WM_MOUSEMOVE не поступают, а поступают только сообщения WM_NCMOUSEMOVE. Теперь яcно, что представляет собой ошибка. Программа рисует от точки последнего сообщения WM_MOUSEMOVE до точки текущего сообщения WM_MOUSEMOVE. Когда "мышь" выходит из области пользователя, программа не получает сообщений WM_MOUSEMOVE. Таким образом, к огда "мышь" возвращается в область пользователя, последней точкой будет та точка, где она покинула экран, и программа ошибочно рисует линию от точки выхода за пределы экрана до текущей точки. Исправление ошибки Одним из возвожных решений могло бы быть определение того меета, где "мышь" выходит за область пользователя, благодаря чему программа сможет игнорировать последнюю позицию "мыши" и снова начать рисование, когда "мышь" возвращается в область ппользователя . Но это потребовало бы сложной логики определения позиции, где "мышь" покидает пределы экрана и где она возвращается в область пользователя. К счастью, есть более простой способ. Функция Windows SetCapture делает как раз то, что требуется. Данная функция указывает Windows, что до вызова ReleaseCapture нужно посылать все сообщения Windows, связанные с "мышью", в указанное окно, что приводит к получению окном вместо сообщений WM_NC MOUSEBUTTON ("вне области пользователя") сообщений WM_MOUSEMOVE. Если поместите в ScribbleWindow.WMButtonDown вызов SetCapture, а в WMLButtonUp - ReleaseCapture, WMMouseMove будет на самом деле рисовать вне окна, но Windows будет отсекать этот вывод программы. Эти изменения отражены в следующем исходном коде: procedure ScribbleWindow.WMLButtonDown(var Msg: TMessage); begin if not ButtonDown then begin ButtonDown :=True; { пометить кнопку "мыши" } { как нажатую, благодаря чему } { при перемещении "мыши" будет } { рисоваться линия } SetCapture(HWindow); { сообщить Windows, что в окно } { нужно посылать все сообщения } { от "мыши"; отменять этот перехват } { будет вызов WMLButtonUp } HandleDC :=GetDC(HWindow); { создать контекст изображения } { для вывода } MoveTo(HandleDC, Msg.lparamLo, { переместить точку рисования } Msg.LParamHi); { в ту точку, где была нажата } { кнопка "мыши" } end; end; procedure Scribblewindow.WMLButtonUp(var Msg: TMessage); begin if Buttondown then begin ReleaseCapture; { сообщить Windows, что нужно } { прекратить посылать все сообщения } { от "мыши" в это окно; это позволит } { получать данное сообщение другим } { прикладным задачам } ReleaseDC(HWindow,handleDC);{ освободить контекст вывода, } { созданный методом WMLButtonDown } ButtonDown :=False; { отметить кнопку "мыши", как } { не нажатую } end; end; Проверка исправлений Запустите программу ColorScribble и выйдите из нее, затем выйдите в TDW. Когда вы вернетесь в Турбо Паскаль, внесите изменения в две подпрограммы, затем перекомпилируйте программу и запустите ее. Теперь, когда вы рисуте в окне, все будет работать прекрас но, но когда вы попытаетесь стереть экран, используя правую кнопку "мыши", то ничего не произойдет. Вы нашли еще одну ошибку. Поиск ошибки стирания экрана Выйдите из ColorScrible и загрузите в TDW программу TDODEMOB. Чтобы выполнить ее до процедуры WMRButtonDown, в которой возможно содержится ошибка, нажмите клавиши Alt-F9 и наберите WMRButtonDown. Порисуйте немного в окне, затем нажмите правую кнопку "мыш и". TDW останавливает программу в начале WMRButtonDown. Используя клавишу F7, войдите в WRMButtonDown и остановитесь на вызове UpdateWindow. Единственным параметром является HWindow. Вы можете предположить, что параметр HWindow установлен правильно, поскольку другие методы успешно используются. Поскольку ниче го очевидно неверного здесь нет, вы можете проверить следующее: принимается ли действительно окном сообщение WM_PAINT, которое должно посылаться в окно при вызове UpdateWindow? Теперь вы наверное знаете, как установить точку останова по сообщению WM_PAINT. Если нет, то прочтите выше описание установки точки останова по сообщению WM_COMMAND. Установив точку останова по сообщению, для выполнение программы нажмите F9. Поскольку она не прерывается и не возвращает управления, сообщение WM_PAINT в окно не посылается. По каким-то причинам вызов UpdateWindow не работает так, как ожидается. Анализ причины ошибки Эта ошибка требует некоторого понимания того, как Windows работает с функцией UpdateWindow. Когда программа вызывает данную функцию, Windows проверяет, является ли какая-либо часть окна недопустимой, и требуется ли отображать ее заново. Если это так, то Windows посылает окну сообщение WM_PAINT. Если же нет, но незачем попусту тратить системные ресурсы на ненужное сообщение, поэтому Windows ничего не делает. Но откуда Windows знает, что окно требуется обновить? Прикладная программа уведомляет Windows, что по крайней мере часть окна недопустима, с помощью вызова InvalidateRect или InvalidateRgn. Эти две функции помещают в окно обновленную область и уведомляют Windows, что окно следует обновить с помощью сообщени я WM_PAINT. Однако, Windows присваивает сообщению WM_PAINT, которое оно посылает в ответ на эти вызовы функций, низкий приоритет, поэтому, если вы хотите, чтобы окно обновлялось немедленно, следует воздерживаться от вызова UpdateWindow. Исправление ошибки Добавление в WMRButtonDown вызова InvalidateRect устранит проблему. Функция InvalidateRect воспринимает три параметра: идентифицирующий окно описатель окна, указатель на прямоугольную область, отмечающий требующий обновления прямоугольник, и параметр тип а Boolean, который определяет, следует ли стирать прямоугольную область. В качестве параметра, задающего указатель на прямоугольную область, вы можете передать значение nil, указав Windows, что к обновляемому прямоугольнику следует добавить все окно. В с ледующем исходном коде показано, как выглядит WMRButtonDown с несколькими добавленными вызовами функций: procedure ScribbleWindow.WMRButtonDown(var Msg: TMessages); begin InvalidateRect(HWindow, nil, True); UpdateWindow(HWindow); end: Проверка исправления Запустите программу ColorScribble и выйдите из нее, затем выйдите в TDW. Когда вы вернетесь в Турбо Паскаль, внесите изменения в WMRButtonDown, затем перекомпилируйте программу и запустите ее. Теперь когда вы рисуете в окне и затем нажимаете правую кнопк у "мыши" для стирания изображение, все окно стирается. Вы нашли все ошибки, и программа теперь прекрасно работает. |