Текст книги "Delphi. Трюки и эффекты"
Автор книги: Александр Чиртик
Жанр: Программирование, Компьютеры
сообщить о неприемлемом содержимом
Текущая страница: 6 (всего у книги 24 страниц)
Существует ряд задач, для выполнения которых бывает полезно иметь возможность получать сообщения от мыши даже тогда, когда указатель находится за пределами формы. За примером далеко ходить не надо: откройте редактор 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 приводятся объявления переменных (членов класса TForml) и методов, добавленных вручную.
Листинг 3.8. Форма для измерения пробега указателя
type
TForm1 = class(TForm)
…
private
isUpdating: 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;
В приведенном расчете нет ничего сложного, как, собственно, нет ничего сложного и во всем примере. Главная процедура приложения – обработчик для таймера Timerl. Таймер срабатывает с максимальной для него частотой (не 1 мс, конечно, но где-то 18 раз в секунду). Текст обработчикаТ1тег1Т1тег приводится в листинге 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;
Как можно увидеть при внимательном рассмотрении листинга 3.10, обновление показаний происходит при истинном значении переменной 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. Изменение начертания шрифта
procedure TForm1.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 для кнопки потому, что, к великому сожалению, обработчики co6biTHftMouseEnter nMouseLeave для нее (по крайней мере, с вкладки Standard) не предусмотрены.
3.2. Клавиатура
Клавиатура является основным средством для ввода информации в компьютер, поэтому не будем обходить стороной и рассмотрим некоторые не так часто используемые или не такие очевидные возможности работы с ней.
Определение информации о клавиатуреНачнем с небольшого примера, позволяющего определить некоторую информацию о клавиатуре (листинг 3.16). Пример основан на использовании API-функции GetKeyboardType.
Листинг 3.16. Определение информации о клавиатуре
procedure TForm1.FormCreate(Sender: TObject);
begin
//Определяем тип клавиатуры
case GetKeyboardType(0) of
1: txtType.Text := 'PC/XT или совместимая (83 клавиши)';
2: txtType.Text := 'Olivetti «ICO» (102 клавиши)';
3: txtType.Text := 'PC/AT (84 клавиши) или похожая';
4: txtType.Text := 'Расширенная (101 или 102 клавиши)';
5: txtType.Text := 'Nokia 1050 или похожая';
6: txtType.Text := 'Nokia 9140 или похожая';
7: txtType.Text := 'японская';
end;
//Определяем код типа производителя
txtSubtype.Text := IntToStr(GetKeyboardType(1));
//Определяем количество функциональных клавиш
txtKeys.Text := IntToStr(GetKeyboardType(2));
end;
При создании формы происходит заполнение текстовых полей информацией о типе клавиатуры, коде типа, присвоенном производителем, и количестве функциональных клавиш.
Пример возможного результата определения информации о клавиатуре приводится на рис. 3.2.
Рис. 3.2. Информация о клавиатуре
Опрос клавиатурыСуществует достаточно удобная альтернатива обработке событий клавиатурного ввода, которая может оказаться особенно полезной, когда нужно знать состояние сразу нескольких клавиш.
В листинге 3.17 приводится пример обработчика события TimerlTimer, определяющего, нажаты ли клавиши ↑, ↓, ←, →, а также пробел, Enter, Ctrl (правый) и 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-функцию GetKeyboardState, которая заполняет массив buttons (на самом деле тип TKeyBoardstate определен как array [0. 255] of Byte) значениями, характеризующими, нажата ли клавиша. Причем значения в массиве buttons трактуются следующим образом:
• если установлен старший бит (проверка чего и делается в листинге 3.17), то клавиша в данный момент нажата;
• если установлен младший бит, то функция, закрепленная за этой клавишей (например, Caps Lock), включена.
Для индексации массива можно использовать ASCII-коды символов, а также константы, соответствующие несимвольным клавишам (обозначения и коды для таких клавиш приводятся в приложении 1).
Каждой контролируемой клавише (листинг 3.17) соответствует кнопка на форме. Для принудительной установки кнопки в нажатое или ненажатое состояние используется посылка сообщения BMSETSTATE. Пример определения состояния клавиш в некоторый момент времени показан на рис. 3.3.
Рис. 3.3. Состояние некоторых клавиш клавиатуры
Интересно, что рассмотренный способ работы с клавиатурой можно использовать даже для определения неисправных клавиш на клавиатуре, например, как это сделано в одной из программ пакета Norton Utilities.
Имитация нажатия клавишСостояние клавиш на клавиатуре можно не только определять, его также можно программно изменять. Рассмотрим один из способов программного нажатия клавиш, который крайне прост благодаря наличию API-функции keybdevent, как раз и предназначенной для имитации клавиатурного ввода.
Назначения параметров этой функции поясним на примере (листинг 3.18).
Листинг 3.18. Показываем меню Пуск
procedure TForm1.cmbStartClick(Sender: TObject);
begin
//Имитируем нажатие клавиши Windows
keybd_event(VK_LWIN, 0, 0, 0);
//Имитируем отпускание клавиши Windows
keybd_event(VK_LWIN, 0, KEYEVENTF_KEYUP, 0);
end;
Нас интересуют, прежде всего, первый и третий параметры функции keybdevent (второй не используется, а третий предназначен для установки дополнительной информации, относящейся к нажатию клавиши). Первым параметром функции передается код «нажимаемой» или «отпускаемой» клавиши. Третий параметр равен нулю при «нажатии» и константе 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. Сворачивание всех окон
procedure TForm1.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. Быстрый запуск программы
procedure TForm1.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'. Если это не так и размещение материала нарушает чьи-либо права, то сообщите нам об этом.