Текст книги "Программирование в Delphi. Трюки и эффекты"
Автор книги: Александр Чиртик
Жанр: Программирование, Компьютеры
сообщить о неприемлемом содержимом
Текущая страница: 6 (всего у книги 24 страниц)
Существует ряд задач, для выполнения которых полезно иметь возможность получать сообщения от мыши даже тогда, когда указатель находится за пределами формы. Обычно рабочая область программы ограничена размерами окна приложения. Это значит, что действия, выполняемые пользователем за пределами рабочей области, попросту не воспринимаются приложением. Попробуйте сделать следующее: откройте редактор Paint, сделайте размер его окна меньше размера холста, затем, зажав кнопку мыши, нарисуйте линию так, чтобы в ходе рисования указатель вышел за пределы окна редактора. Если теперь развернуть окно редактора на весь экран, то можно увидеть, что рисунок содержит часть линии, которую вы рисовали, перемещая указатель за пределами окна редактора. Так происходит, потому что редактор Paint «захватывает» указатель, как только пользователь начинает рисовать линию, и «освобождает» указатель, когда пользователь отпускает кнопку мыши.
Захват указателя полезен и в других случаях, потому стоит рассмотреть способ его реализации (а сделать это действительно просто). В листинге 3.5 приведены обработчики нажатия и отпускания кнопки мыши, которые реализуют захват указателя на время от нажатия до отпускания кнопки.
Листинг 3.5. Захват и освобождение указателя мыши
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
//Захватываем указатель мыши
SetCapture(Handle);
end;
procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
//Отменяем захват указателя
ReleaseCapture();
end;
Вся хитрость состоит в использовании API-функций захвата SetCapture и ReleaseCapture. При вызове первой функции происходит регистрация окна, которое захватывает указатель мыши: это окно будет получать сообщения от мыши даже тогда, когда указатель переместится за его пределы. Функция возвращает дескриптор окна, которое захватило указатель ранее, либо 0, если такого окна нет. Соответственно, функция ReleaseCapture используется для освобождения указателя.
Примечание
При использовании SetCapture окно получает сообщения, когда указатель находится не над окном, только в том случае, если кнопка мыши нажата либо указатель находится над одним из окон, созданных тем же потоком (независимо от того, нажата ли кнопка мыши).
Можно также упомянуть об API-функции GetCapture. Эта функция не принимает аргументов и возвращает дескриптор окна, захватившего указатель ранее. С помощью этой функции можно, например, удостовериться, что захватом указателя мыши не нарушается работа другого приложения (что маловероятно).
Ограничение области перемещения указателяС помощью несложных манипуляций можно ограничить перемещение указателя мыши определенной областью экрана (прямоугольником). Для этого используется API-функция ClipCursor. Она принимает в качестве параметра структуру TRect с координатами прямоугольника, в пределах которого может перемещаться указатель. Если ограничивающая область движения курсора заданна успешно, то функция возвращает ненулевое значение.
С функцией ClipCursor тесно связана функция GetClipCursor, позволяющая получить координаты прямоугольника, которым ограничено перемещение указателя в данный момент.
Способ использования функций ClipCursor и GetClipCursor показан в листинге 3.6.
Листинг 3.6. Ограничение области перемещения указателя
var
lastRect: TRect;
cursorClipped: Boolean = False;
procedure SetCursorRect(newRect: TRect);
begin
if not cursorClipped then
begin
//Сохраняем старую область перемещения указателя
GetClipCursor(lastRect);
//Устанавливаем ограничение на перемещения указателя
cursorClipped:= ClipCursor(Addr(newRect)) <> False;
end;
end;
procedure RestoreCursorRect();
begin
if cursorClipped then
begin
//Восстанавливаем область перемещения указателя
cursorClipped:= ClipCursor(Addr(lastRect)) = False;
end;
end;
В данном примере реализованы две функции. Первая (SetCursorRect) ограничивает перемещение указателя мыши заданной областью экрана (параметр newRect). Перед ограничением на перемещения указателя в процедуре SetCursorRect происходит сохранение области перемещения, установленной ранее, чтобы действие процедуры можно было отменить. Для отмены ограничения перемещения указателя служит вторая функция – RestoreCursorRect.
Изменение назначений кнопок мышиПримечание
Вообще, задание ограничения на перемещение указателя мыши не считается хорошим тоном. Потому для использования такой возможности в реальном приложении должны быть действительно веские причины.
Как известно, операционная система Windows предоставляет возможность работать с компьютером широкому кругу людей. Большинство производителей манипуляторов типа «мышь» предусматривают возможность простой адаптации манипулятора под правшу или левшу. К тому же, мышь адаптировать к таким различиям намного проще, нежели выпускать манипуляторы различных типов: конструкцию изменять не надо, достаточно программно поменять функции кнопок мыши.
Способ программного изменения функций левой и правой кнопок мыши продемонстрирован в листинге 3.7.
Листинг 3.7. Изменение назначений кнопок мыши
procedure TForm1.Button1Click(Sender: TObject);
begin
//Меняем местами функции левой и правой кнопок мыши
SwapMouseButton(True);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
//Восстанавливаем функции кнопок мыши
SwapMouseButton(False);
end;
В коде листинга 3.7 не учтен тот факт, что инверсия кнопок мыши может быть изначально установлена при запуске программы (например, если за компьютером работает левша). Чтобы точно знать, была ли ранее применена инверсия к кнопкам мыши, можно использовать значение, возвращаемое функцией SwapMouseButton. Если это значение отлично от нуля, то функции кнопок мыши ранее были инвертированы.
Подсчет расстояния, пройденного указателем мышиДалее будет рассмотрена небольшая программа, которая носит скорее познавательный, нежели практический характер. Она подсчитывает количество метров (в буквальном смысле), пройденное указателем мыши за время ее работы. Внешний вид формы приложения показан на рис. 3.1.
Рис. 3.1. Программа для измерения пробега указателя мыши
Использование этой программы крайне просто: сразу после запуска она начинает измерять пройденное указателем мыши расстояние в пикселах. Нижняя группа элементов управления формы нужна для правильного вывода пройденного расстояния в метрах. При нажатии кнопки Изменить масштаб становятся активными два текстовых поля (предназначенных для ввода ширины и высоты прямоугольника). Чтобы программа правильно преобразовывала пройденное расстояние, нужно линейкой измерить ширину белого прямоугольника и ввести полученное значение (в миллиметрах) в текстовое поле. При повторном нажатии кнопки Изменить масштаб введенные значения принимаются, и с этого момента показания пройденного расстояния начинают переводиться в метры с учетом текущего разрешения и размера монитора.
Теперь перейдем к рассмотрению способа реализации этого приложения. В табл. 3.1 приведены сведения о настройке элементов управления, не являющихся рамками или статическими надписями.
Таблица 3.1. Параметры элементов управления формы, показанной на рис. 3.1
В коде листинга 3.8 объявляются переменные (члены класса TForm1) и методы, добавленные вручную.
Листинг 3.8. Форма для измерения пробега указателя
type
TForm1 = class(TForm)
...
private
isUp dating: Boolean; //Если равен False, то показания в txtDistance
//не обновляются
lastPos: TPoint; //Координаты указателя во время прошлого замера
distance: Real; //Пройденное расстояние в пикселах
procedure StartUpdating();
procedure StopUpdating();
procedure ShowDistance();
end;
Суммарное расстояние в пикселах, пройденное указателем, сохраняется в переменной distance. Ниже представлен способ перевода этого расстояния в метры (листинг 3.9).
Листинг 3.9. Перевод расстояния в метры с учетом масштаба
procedure TForm1.ShowDistance();
var
scale: Real;
distanceMetters: Real;
begin
//Пересчитываем текущий пробег в метры и показываем его
//в текстовом поле
//..определяем масштаб для перевода измерений в метры
scale:= 0.001 * StrToInt(txtWidth.Text) / Shape1.Width;
//..подсчитываем расстояние с учетом масштаба
distanceMetters:= scale * distance;
//..округляем до трех знаков (мм) и показываем
distanceMetters:= Int(distanceMetters * 1000) * 0.001;
txtDistance.Text:= FloatToStr(distanceMetters);
end;
Главная процедура приложения – обработчик для таймера Timer1. Таймер срабатывает с максимальной для него частотой (около 18 раз в секунду). Текст обработчика Timer1Timer приведен в листинге 3.10.
Листинг 3.10. Подсчет разницы между положениями указателя мыши
procedure TForm1.Timer1Timer(Sender: TObject);
var
curPos: TPoint;
delta: Real;
begin
if (curPos.X <> lastPos.X) or (curPos.Y <> lastPos.Y) then
begin
GetCursorPos(curPos);
//Вычисляем разницу между текущим и прошлым положением мыши
delta:= Sqrt(Sqr(curPos.X – lastPos.X) + Sqr(curPos.Y – lastPos.Y));
distance:= distance + delta;
//Не забываем сохранить новые координаты указателя
lastPos:= curPos;
if isUpdating then
begin
//Обновим показания в текстовом поле
ShowDistance();
end;
end;
end;
Из данного листинга видно, что обновление показаний происходит при истинном (True) значении переменной isUpdating. Значение этой переменной устанавливается в False во время изменения масштаба, чтобы во время ввода значений в текстовые поля не выводились неправильные цифры (листинг 3.11).
Листинг 3.11. Активизация и деактивизация режима изменения масштаба
procedure TForm1.cmbScaleClick(Sender: TObject);
begin
if cmbScale.Caption = 'Изменить масштаб' then
begin
//Начинаем изменение масштаба
StopUpdating();
cmbScale.Caption:= 'Принять масштаб';
txtWidth.Enabled:= True;
end
else
begin
//Заканчиваем изменение масштаба
txtWidth.Enabled:= False;
cmbScale.Caption:= 'Изменить масштаб';
StartUpdating();
end;
end;
Процедуры StartUpdating и StopUpdating скрывают действия, которые необходимо произвести для остановки и возобновления отображения пройденного в текстовом поле указателем мыши расстояния. В данном примере они выглядят достаточно просто (листинг 3.12).
Листинг 3.12. Включение и выключение обновления результатов измерения
procedure TForm1.StartUpdating();
begin
//Включаем обновление показаний в текстовом поле
isUpdating:= True;
end;
procedure TForm1.StopUpdating();
begin
//Отключаем обновление показаний в текстовом поле
isUpdating:= False;
end;
В завершение остается реализовать код инициализации координат указателя мыши при запуске программы и обработчик события Click для кнопки cmbClear (листинг 3.13).
Листинг 3.13. Инициализация при запуске и код сброса счетчика
procedure TForm1.FormCreate(Sender: TObject);
begin
//Инициализируем координаты мыши
GetCursorPos(lastPos);
StartUpdating();
end;
procedure TForm1.cmbClearClick(Sender: TObject);
begin
//Сбрасываем счетчик пройденного расстояния
distance:= 0;
GetCursorPos(lastPos); //Начинаем отсчет с текущей позиции указателя
ShowDistance();
end;
Вот, собственно, и все, что нужно для работы рассматриваемой программы. Остается лишь уточнить, что способ установки масштаба, используемый в программе, предназначен для таких разрешений мониторов, при которых нет искажений по горизонтали или вертикали. Чаще всего это такие разрешения, при которых размеры изображения по горизонтали и вертикали подчиняются пропорции 4:3 (640 х 480, 800 х 600 и т. д.). При этом такими же пропорциями должен обладать и экран монитора.
Подсвечивание элементов управленияВ реальных приложениях часто возникает необходимость изменять внешний вид элементов интерфейса программы в ответ на определенные действия пользователя. Поэтому стоит рассмотреть несложный, но достаточно полезный пример, позволяющий сделать более «живым» интерфейс приложения: изменение внешнего вида элементов управления при наведении на них указателя мыши.
В листинге 3.14 продемонстрирован способ создания статической надписи, похожей на гиперссылку (для большего эффекта для такой надписи можно установить свойство Cursor равным crHandPoint на этапе проектирования формы).
Листинг 3.14. Подчеркивание и изменение цвета надписи
procedure TForm1.lblUnderlineMouseEnter(Sender: TObject);
begin
lblUnderline.Font.Style:= [fsUnderline];
lblUnderline.Font.Color:= RGB(0, 0, 255);
end;
procedure TForm1.lblUnderlineMouseLeave(Sender: TObject);
begin
lblUnderline.Font.Style:= [];
lblUnderline.Font.Color:= RGB(0, 0, 0);
end;
Для надписи, чтобы получилась довольно правдоподобная гиперссылка, осталось добавить только обработчик события Click, правда, выполнять она сможет любое действие, а не только переход по ссылке (достаточно лишь определить обработчик).
Для стандартной кнопки начертание шрифта также можно изменить (листинг 3.15).
Листинг 3.15. Изменение начертания шрифта
procedureTForm1. cmbItalicBoldMouseMove (Sender: TObject;
Shift: TShiftState; X, Y: Integer);
begin
cmbItalicBold.Font.Style:= [fsItalic, fsBold];
end;
procedure TForm1.lblItalicMouseEnter(Sender: TObject);
begin
lblItalic.Font.Style:= [fsItalic];
end
В листинге 3.15 используется обработчик MouseMove для кнопки потому, что обработчики событий MouseEnter и MouseLeave для нее (по крайней мере, с вкладки Standard) не предусмотрены.
Клавиатура
Клавиатура является основным средством ввода информации в компьютер, поэтому не будем обходить стороной и рассмотрим некоторые не так часто используемые или не такие очевидные возможности работы с ней. В этом разделе вы научитесь определять тип клавиатуры, а также будет разработана программа опроса клавиатуры. Также будет рассказано, как программным способом имитировать нажатия клавиш, и разработана программа «Бегущие огни на клавиатуре».
Получение информации о клавиатуреНачнем с небольшого примера, позволяющего определить некоторую информацию о клавиатуре (листинг 3.16). Данный пример основан на использовании API-функции GetKeyboardType.
Листинг 3.16. Получение информации о клавиатуре
procedure TForm1.FormCreate(Sender: TObject);
begin
//Определяем тип клавиатуры
case GetKeyboardType(0) of
1: txt Type.Text:= 'PC/XT или совместимая (83 клавиши)';
2: txt Type.Text xtxt:= 'Olivetti" ICO " (102 клавиши)';
3: txt Type.Text xtxt:= 'PC/AT (84 клавиши) или похожая';
4: txt Type.Text:= 'Расширенная (101 или 102 клавиши)';
5: txt Type.Text:= 'Nokia 1050 или похожая';
6: txt Type.Text:= 'Nokia 9140 или похожая';
7: txt Type.Text:= 'японская';
end;
//Определяем код типа производителя
txtSubtype.Text:= IntToStr(GetKeyboardType(1));
//Определяем количество функциональных клавиш
txtKeys.Text:= IntToStr(GetKeyboardType(2));
end;
При создании формы происходит заполнение текстовых полей информацией о типе клавиатуры, коде типа, присвоенном производителем, и количестве функциональных клавиш.
На рис. 3.2 показан возможный результат определения информации о клавиатуре.
Рис. 3.2. Информация о клавиатуре
Опрос клавиатурыСуществует достаточно удобная альтернатива обработке событий клавиатурного ввода, которая может оказаться особенно полезной, если необходима информация о состоянии сразу нескольких клавиш. Это может понадобиться, если пользователь должен одновременно удерживать нажатыми несколько клавиш. Например, в гоночных симуляторах, чтобы проезжать поворот, необходимо одновременно удерживать клавишу ↑ (газ) и одну из клавиш поворота (← или →).
В листинге 3.17 приведен пример обработчика события Timer1Timer, определяющего, нажаты ли клавиши ↑, ↓, ←, →, а также пробел, Enter, Ctrl (правый), Shift (правый) и Alt (правый).
Листинг 3.17. Определение состояния некоторых клавиш
procedure TForm1.Timer1Timer(Sender: TObject);
var
buttons: TKeyBoardstate;
begin
//Получаем состояния клавиш
GetKeyboardState(buttons);
//Отобразим состояния клавиш
//..пробел
if buttons[VK_SPACE] and 128 <> 0 then
SendMessage(cmbSpace.Handle, BM_SETSTATE, BST_CHECKED, 0)
else
SendMessage(cmbSpace.Handle, BM_SETSTATE, BST_UNCHECKED, 0);
//..enter
if buttons[VK_RETURN] and 128 <> 0 then
SendMessage(cmbEnter.Handle, BM_SETSTATE, BST_CHECKED, 0)
else
SendMessage(cmbEnter.Handle, BM_SETSTATE, BST_UNCHECKED, 0);
//..правый Ctrl
if buttons[VK_RCONTROL] and 128 <> 0 then
SendMessage(cmbRCtrl.Handle, BM_SETSTATE, BST_CHECKED, 0)
else
SendMessage(cmbRCtrl.Handle, BM_SETSTATE, BST_UNCHECKED, 0);
//..правый Alt
if buttons[VK_RMENU] and 128 <> 0 then
SendMessage(cmbRAlt.Handle, BM_SETSTATE, BST_CHECKED, 0)
else
SendMessage(cmbRAlt.Handle, BM_SETSTATE, BST_UNCHECKED, 0);
//..правый Shift
if buttons[VK_RSHIFT] and 128 <> 0 then
SendMessage(cmbRShift.Handle, BM_SETSTATE, BST_CHECKED, 0)
else
SendMessage(cmbRShift.Handle, BM_SETSTATE, BST_UNCHECKED, 0);
//..вверх
if buttons[VK_UP] and 128 <> 0 then
SendMessage(cmbUp.Handle, BM_SETSTATE, BST_CHECKED, 0)
else
SendMessage(cmbUp.Handle, BM_SETSTATE, BST_UNCHECKED, 0);
//..вниз
if buttons[VK_Down] and 128 <> 0 then
SendMessage(cmbDown.Handle, BM_SETSTATE, BST_CHECKED, 0)
else
SendMessage(cmbDown.Handle, BM_SETSTATE, BST_UNCHECKED, 0);
//..влево
if buttons[VK_LEFT] and 128 <> 0 then
SendMessage(cmbLeft.Handle, BM_SETSTATE, BST_CHECKED, 0)
else
SendMessage(cmbLeft.Handle, BM_SETSTATE, BST_UNCHECKED, 0);
//..вправо
if buttons[VK_RIGHT] and 128 <> 0 then
SendMessage(cmbRight.Handle, BM_SETSTATE, BST_CHECKED, 0)
else
SendMessage(cmbRight.Handle, BM_SETSTATE, BST_UNCHECKED, 0);
end;
Для определения состояния клавиш используется API-функция GetKeyboardS tate, заполняющая массив buttons значениями, характеризующими, нажата ли клавиша (на самом деле тип TKeyBoardstate определен как array [0..255] of Byte). Причем значения в массиве buttons трактуются следующим образом:
• если установлен старший бит (проверка чего производится в листинге 3.17), то считается, что клавиша в данный момент нажата;
• если установлен младший бит, то считается, что функция, закрепленная за этой клавишей (например, Caps Lock) включена.
Для индексации массива можно использовать ASCII-коды символов, а также константы, соответствующие несимвольным клавишам (обозначения и коды для таких клавиш приведены в приложении 1).
Каждой контролируемой клавише (листинг 3.17) соответствует кнопка на форме. Для принудительной установки клавиши в нажатое или ненажатое состояние используется сообщение BM_SETSTATE. На рис. 3.3 приведен пример результата определения состояния клавиш в некоторый момент времени.
Рис. 3.3. Состояние некоторых клавиш клавиатуры
Рассмотренный способ работы с клавиатурой можно использовать даже для определения неисправных клавиш на клавиатуре, как это сделано, например, в одной из программ пакета Norton Utilities.
Имитация нажатия клавишКак и в случае с мышью, когда программно изменялось положение указателя мыши, состояние клавиш клавиатуры также может изменяться программно. Рассмотрим один из способов программного нажатия клавиш, который крайне прост благодаря наличию API-функции keybd_event, предназначенной именно для имитации клавиатурного ввода. Таким образом, имитация нажатия любой клавиши на клавиатуре сводится к вызову keybd_event с определенными параметрами.
Назначения параметров этой функции пояснено на примере (листинг 3.18).
Листинг 3.18. Вызов меню «Пуск»
procedureTForm1. cmbStartClick(Sender: TObject);
begin
//Имитируем нажатие клавиши Windows
keybd_event (VK_LWIN, 0, 0, 0);
//Имитируем отпускание клавиши Windows
keybd_event (VK_LWIN, 0, KEYEVENTF_KEYUP, 0);
end;
Здесь нас интересуют, прежде всего, первый и третий параметры функции keybd_ event (второй не используется, а четвертый предназначен для установки дополнительной информации, относящейся к нажатию клавиши). В первом параметре функции передается код «нажимаемой» или «отпускаемой» клавиши. Третий же параметр равен нулю при «нажатии» и константе KEYEVENTF_KEYUP при «отпускании» клавиши.
Внимание!
При использовании функции keybd_event главное – не забывать «отпускать» программно нажатые клавиши (что и делается в приведенных в данном разделе примерах). В противном случае существует риск «залипания» клавиш и, как следствие, ухудшения работы операционной системы.
Аналогичный приведенному в листинге 3.18 код программного нажатия клавиши Print Screen (создания копии экрана) приведен в листинге 3.19.
Листинг 3.19. Создание копии изображения на экране
procedure TForm1.cmbPrintScreenClick(Sender: TObject);
begin
//Нажимаем Print Screen
keybd_event(VK_SNAPSHOT, 0, 0, 0);
keybd_event(VK_SNAPSHOT, 0, KEYEVENTF_KEYUP, 0);
end;
В листинге 3.20 приведен пример программного нажатия сочетания клавиш (Windows+M, выполняющей сворачивание всех окон).
Листинг 3.20. Сворачивание всех окон
procedureTForm1. cmbMinimizeAllClick(Sender: TObject);
begin
//Нажимаем Windows+M
keybd_event(VK_LWIN, 0, 0, 0);
keybd_event(Byte('M'), 0, 0, 0);
keybd_event(Byte('M'), 0, KEYEVENTF_KEYUP, 0);
keybd_event(VK_LWIN, 0, KEYEVENTF_KEYUP, 0);
end;
Добавление к этому сочетанию клавиши Shift приведет к восстановлению прежнего состояния окон.
Последний пример иллюстрирует способ использования программного нажатия клавиш для оптимизации быстрого доступа к программам. Имеется в виду программное нажатие сочетаний клавиш, ассоциированных с ярлыками, расположенными на Рабочем столе или находящимися в меню Пуск. Для примера будет выбрано сочетание клавиш CtrL+ALt+E, обычно используемое для запуска браузера Internet Explorer (листинг 3.21).
Листинг 3.21. Быстрый запуск программы
procedureTForm1.cmbEIxplorerClick(Sender: TObject);
begin
//Нажимаем сочетание Ctrl+Alt+E
keybd_event(VK_CONTROL, 0, 0, 0);
keybd_event(VK_MENU, 0, 0, 0);
keybd_event(Byte('E'), 0, 0, 0);
keybd_event(Byte('E'), 0, KEYEVENTF_KEYUP, 0);
keybd_event(VK_MENU, 0, KEYEVENTF_KEYUP, 0);
keybd_event(VK_CONTROL, 0, KEYEVENTF_KEYUP, 0);
end;
Последний пример особенно полезен при необходимости запуска сразу нескольких программ (для этого сначала ярлыкам запускаемых программ должны быть назначены сочетания клавиш).
Правообладателям!
Это произведение, предположительно, находится в статусе 'public domain'. Если это не так и размещение материала нарушает чьи-либо права, то сообщите нам об этом.