:: MVP ::
|
|
:: RSS ::
|
|
|
Delphi уже давно (если быть точным – с версии 2009) поддерживает UNICODE, но, по непонятным
для меня причинам, до сих пор (а на пороге уже Delphi XE8) не "научила" компоненты, предназначенные
для редактирования текста, воспринимать Alt-коды, вводимые с клавиатуры, хотя из кода это можно
сделать без каких либо проблем. Ввод символа ≠ из кода можно реализовать, например, так:
procedure TForm1.Button1Click(Sender: TObject);
begin
Edit1.Text := Edit1.Text + Chr( 8800 );
end;
|
Хотелось бы иметь возможность вводить подобные символы с клавиатуры, как, например,
это позволяет делать MS Word. В этой статье я хочу показать, как можно исправить это
досадное упущение.
В начале несколько слов для тех, кто не знаком с Alt-кодами. ALT-код — код, символы
которого вводятся посредством кнопки Alt и последовательности цифр на NUM-паде
(небольшом блоке из 17 клавиш, обычно находящемся с правого края клавиатуры). Так, для
того, чтобы ввести символ '≠', нужно нажать кнопку Alt, и не отпуская ее
нажать на NUM-паде последовательность цифр 8 + 8 + 0 +
0.
Очевидно, что для решения этой задачи нам нужно перехватывать нажатие клавиши Alt,
запоминать последовательность цифр, введенных с NUM-пад (при нажатой кнопке Alt), а
при отпускании клавиши Alt вставлять в текст символ, соответствующий введенному коду.
Это наводит на мысль о необходимости использования клавиатурного хука.
Мы можем воспользоваться одним из 2 вариантов, а именно WH_KEYBOARD или WH_KEYBOARD_LL. Если
кратко описать разницу между ними, то можно сказать так: WH_KEYBOARD срабатывает при попадании в
очередь сообщений потока сообщений WM_KEYUP/WM_KEYDOWN, а WH_KEYBOARD_LL срабатывает при
низкоуровневых клавиатурных событиях, поступающих от драйвера клавиатуры (процедура обработки
WH_KEYBOARD_LL выполняется в контексте приложения, установившего хук).
Рассмотрим оба варианта, и начнем с WH_KEYBOARD.
WH_KEYBOARD
const
KHF_ALTDOWN_HI: Cardinal = $2000;
function KeyHook( Code: Integer; WParam: WPARAM; lParam: LPARAM ): LRESULT; stdcall;
const
{$J+}
CodeStr: string = '';
{$J-}
var
Key: Cardinal;
IsAltDown: Boolean;
SelPos, SelLen: Integer;
Str: string;
begin
if Code = HC_ACTION then
begin
if Form2.ActiveControl = Form2.Edit1 then
begin
IsAltDown := HiWord( lParam ) and KHF_ALTDOWN_HI = KHF_ALTDOWN_HI;
if IsAltDown then
begin
if HiWord( lParam ) and KF_UP = 0 then
if WParam in [96..105] then
CodeStr := CodeStr + Chr( WParam - 48 );
end
else
begin
if CodeStr <> '' then
begin
SelPos := Form2.Edit1.SelStart + 1;
SelLen := Form2.Edit1.SelLength;
Str := Form2.Edit1.Text;
if SelLen > 0 then
Delete( Str, SelPos, SelLen );
Insert( Chr( StrToInt( CodeStr ) ), Str, SelPos );
Form2.Edit1.Text := Str;
Form2.Edit1.SelStart := SelPos;
CodeStr := '';
if WParam = VK_MENU then
begin
Result := 1;
Exit;
end;
end;
end;
end;
end;
Result := CallNextHookEx( KeyboardHook, Code, wParam, lParam );
end;
|
Если у вас возник вопрос, почему для хранения введенного пользователем кода используется локальная
константа, и как это вообще работает, прочтите статью Константы в Delphi,
а здесь я повторяться не стану. Теперь рассмотрим параметры процедуры хука:
- Code – может принимать одно из следующих значений:
- HC_ACTION – приходит при удалении сообщения из очереди;
- HC_NOREMOVE – приходит, когда клавиатурное сообщение не удаляется
из очереди, потому что приложение вызвало функцию PeekMessage с
параметром PM_NOREMOVE. При вызове хука с этим кодом не гарантируется
передача действительного состояния клавиатуры;
- wParam – содержит виртуальный код клавиши (например, VK_F1, VK_RETURN, VK_LEFT);
- lParam расшифровывается следующим образом:
- Биты 0-15 содержат количество повторений нажатой клавиши в случае "залипания";
- Биты 16-23 содержат скан код нажатой клавиши. Это аппаратно зависимый код,
который зависит от конкретной клавиатуры;
- Бит 24 равен 1, если нажатая клавиша является расширенной (функциональной или
на цифровой клавиатуре), иначе 0;
- Биты 25-28 зарезервированы;
- Бит 29 равен 1, если при нажатии клавиши была нажата клавиша Alt, иначе 0;
- Бит 30 говорит о состоянии клавиши до отправки сообщения. Бит равен 1, если до этого
кнопка отправки сообщения была нажата, иначе 0;
- Бит 31 говорит о текущем состоянии клавиши. Он равен 1, если клавиша отпускается, иначе 0.
Первое, что нужно определить – нажата ли клавиша Alt. Это можно сделать несколькими способами. Рассмотрим их:
const
KHF_ALTDOWN_HI: Cardinal = $2000; // Для проверки HiWord( lParam )
function KeyHook( Code: Integer; WParam: WPARAM; lParam: LPARAM ): LRESULT; stdcall;
var
IsAltDown: Boolean;
{...}
begin
if Code = HC_ACTION then
begin
if Form2.ActiveControl = Form2.Edit1 then
begin
IsAltDown := HiWord( lParam ) and KHF_ALTDOWN_HI = KHF_ALTDOWN_HI;
{...}
end;
end;
Result := CallNextHookEx( KeyboardHook, Code, wParam, lParam );
end;
|
// Несколько измененный вариант предыдущего кода
const
KHF_ALTDOWN: Cardinal = $20000000; // Для проверки lParam
function KeyHook( Code: Integer; WParam: WPARAM; lParam: LPARAM ): LRESULT; stdcall;
var
IsAltDown: Boolean;
{...}
begin
if Code = HC_ACTION then
begin
if Form2.ActiveControl = Form2.Edit1 then
begin
IsAltDown := lParam and KHF_ALTDOWN = KHF_ALTDOWN;
{...}
end;
end;
Result := CallNextHookEx( KeyboardHook, Code, wParam, lParam );
end;
|
var
IsAltDown: Boolean;
{...}
function KeyHook( Code: Integer; WParam: WPARAM; lParam: LPARAM ): LRESULT; stdcall;
begin
if Code = HC_ACTION then
begin
if Form2.ActiveControl = Form2.Edit1 then
begin
if WParam = VK_MENU then
IsAltDown := HiWord( lParam ) and KF_UP = 0;
{...}
end;
end;
Result := CallNextHookEx( KeyboardHook, Code, wParam, lParam );
end;
|
Если клавиша Alt нажата, запоминает вводимые с NUM-пада цифры, формируя
из них строку. При отпускании клавиши Alt заносим символ, соответствующий
введенному коду, в текстовое поле в позицию каретки или заменяя выделенный текст.
Обратите внимание на то, что для работы 3-его примера переменная IsAltDown должна
быть глобальной, в то время как для первых 2-х премеров это не обязательно.
Теперь рассмотрим вариант с WH_KEYBOARD_LL.
WH_KEYBOARD_LL
const
LLKHF_ALTDOWN: Cardinal = KF_ALTDOWN shr 8;
LLKHF_UP: Cardinal = KF_UP shr 8;
function LLKeyHook( Code: Integer; WParam: wParam; Msg: PKbdDllHookStrukt ): Longint; stdcall;
const
{$J+}
CodeStr: Cardinal = 0;
{$J-}
var
Key: Cardinal;
IsAltDown: Boolean;
SelPos, SelLen: Integer;
Str: string;
begin
case Code of
HC_ACTION: begin
if Form2.ActiveControl = Form2.Edit1 then
begin
// LLKHF_ALTDOWN - Кнопка ALT нажата
// 0 - Кнопка ALT отжата
IsAltDown := Msg.flags and LLKHF_ALTDOWN = LLKHF_ALTDOWN;
if IsAltDown then
begin
if Msg.flags and LLKHF_UP = 0 then
begin
if Msg^.vkCode in [96..105] then
CodeStr := CodeStr * 10 + Msg^.vkCode - 96;
end;
end
else
begin
if CodeStr > 0 then
begin
SelPos := Form2.Edit1.SelStart + 1;
SelLen := Form2.Edit1.SelLength;
Str := Form2.Edit1.Text;
if SelLen > 0 then
Delete( Str, SelPos, SelLen );
Insert( Chr( CodeStr ), Str, SelPos );
Form2.Edit1.Text := Str;
Form2.Edit1.SelStart := SelPos;
end;
CodeStr := 0;
if Msg^.vkCode in [VK_LMENU, VK_RMENU] then
begin
keybd_event( VK_MENU, 0, 0, 0 );
keybd_event( VK_MENU, 0, KEYEVENTF_KEYUP, 0 );
Result := 1;
Exit;
end;
end;
end;
end;
end;
Result := CallNextHookEx( LLKeybHook, Code, WParam, Longint( Msg ) );
end;
|
Рассмотрим параметры структуры TKbdDllHookStrukt:
- vkCode – виртуальный код клавиши;
- scanCode – скан-код нажатой клавиши. Это аппаратно зависимый код,
который зависит от конкретной клавиатуры;
- flags - расшифровывается следующим образом:
- Бит 0 равен 1, если нажатая клавиша является расширенной (функциональной
или на цифровой клавиатуре), иначе 0;
- Бит 1 равен 1, если сообщение пришло от Low Level Integrity процесса, иначе 0;
- Биты 2 и 3 зарезервированы;
- Бит 4 равен 1, если это внедренное (injected) событие, иначе 0;
- Бит 5 равен 1, если при нажатии клавиши была нажата клавиша Alt, иначе 0;
- Бит 6 зарезервирован;
- Биты 7 говорит о текущем состоянии клавиши. Он равен 1, если клавиша отпускается, иначе 0.
- time – время отправки сообщения;
- dwExtraInfo – дополнительная информация, связанная с сообщением.
Принцип действия аналогичен примеру на WH_KEYBOARD, за исключением того, что для запоминания введенного
числового кода используется не строка, а числовая переменная. Какой именно способ использовать, зависит
от ваших личных предпочтений.
Полные исходные коды находятся в примерах к данной статье. В примерах поддержка Alt-кодов показана на
примере компонента TEdit, однако этот метод можно применить к любым другим компонентам редактирования текста.
P.S.
На основе данных примеров реализованы 2 компонента, скачать которые вы можете в разделе
Мои компоненты.
P.P.S.
Я был приятно удивлен, узнав, что разработчики наконец-то добавили стандартным компонентам поддержку Alt-кодов.
Причем узнал совершенно случайно, наткнувшись в Embarcadero's Quality Portal на пост Composing keys via Alt-code to enter in an FMX TEdit control is not accepted. Ради интереса
протестировал это на том, что в данный момент было под рукой - Delphi 10 Seattle Update 1, все прекрасно работает,
пользуйтесь на здоровье (правда пока только в VCL)!
.: Пример к данной статье :.
|
При использовании материала - ссылка на сайт обязательна
|
|