Главная » Delphi » Переключатель с заменой системной иконки — промежуточный вариант

0

Начнем с иконок. Главную иконку приложения заменим на Keyboard.ico (находится в папке с проектом). Для того чтобы добавить свои иконки, нам придется несколько забежать вперед и включить их в ресурсы приложения (о ресурсах мы подробнее поговорим в следующих главах).

Сначала создадим две иконки размером 16×16 (для Tray этого более чем достаточно), одну красного цвета с надписью "Ru", вторую — синего с надписью "Еп". Я их создавал с помощью упоминавшегося в главе 4 редактора Liquidl- соп, но можно использовать и Image Editor, тем более что он нам все равно понадобится. После создания иконок я их не компилировал в ресурс (RES- файл) через Liquidlcon, а оставил, как есть (в каталоге проекта они называются Rus.ico и Eng. ico). Затем открываем Image Editor, создаем новый файл ресурсов (File | New | Resource file) и называем его Icon.res. В нем создаем две иконки с названиями "MAINICONRUS" и "MAINICONENG". Названия обязательно писать заглавными буквами (по крайней мере, все пособия это рекомендуют), а не так, как это делает Image Editor по умолчанию. Напоминаю, что все изменения в ресурсы нужно вносить при закрыюй Delphi (правда, от всех "глюков" Image Editor вы все равно не гарантированы).

Дальше откроем созданные иконки (File | Open | Icons) и просто переносим изображение методом Select All-Copy-Paste поверх заготовок иконок в ресурсном файле (чтобы открыть окно с заготовкой, надо дважды щелкнуть по названию в перечне ресурсов— см. рис. 5.1). Разумеется, можно было бы нарисовать иконку и прямо в ресурсном файле, используя заготовку, просто в редакторе Liquidlcon работать намного удобнее, чем в Image Editor. В данном случае я сделал иконки круглыми (темное поле в углах означает "прозрачный" цвет).

Теперь созданный ресурс надо, во-первых, присоединить к проекту, во- вторых, извлечь из него полученные иконки. Для первого мы просто в тексте главной программы LangSwitch.dpr к имеющейся строке ($r *.res) добавим ниже еще одну: {$r icon. res). Наконец, заменим оператор

HIconl:=CopyIcon(Application.Icon.Handle) в функции CreateMyicon на следующий текст:

Instance :=GetModuleHandle(nil); {получаем дескриптор молуля) if word(GetKeyboardLayout(0)) = $419 {если язык по умолчанию русский}

then HIconl:=LoadIcon(instance,’MAINICONRUS’)

{получаем дескриптор русской иконкиI else HIconl:=LoadIcon(instance,’MAINICONENG’);

(получаем дескриптор английской иконки}

Теперь программа при запуске будет сворачиваться в одну из иконок: русскую или английскую. Функция GetKeyboardLayout возвращает, как видим, сразу идентификатор языка, а не его название типа используемой в LoadKeyboardLayout строки (идентификатор — только часть названия, см. главу 5). Параметр 0 в вызове функции означает раскладку для текущего процесса — т. е. эта функция возвращает либо ту раскладку, которая установлена в используемом файловом менеджере, откуда запускается программа, либо, если загрузка осуществляется через Автозапуск, ту раскладку, которая установлена в системе по умолчанию (в среде Delphi перед пробным запуском нужно устанавливать именно тот язык, что по умолчанию, иначе будет путаница). Использовать в данном случае функцию GetKeyboardLayoutName, которая возвращает название системной раскладки по умолчанию, было бы идеологически неправильно. Проверьте работу программы в этой части, сменив раскладку по умолчанию несколько раз.

Так, теперь ловушки. Нам их понадобится целых три: одна, как и раньше, будет отслеживать клавишу, вторая — системное сообщение о том, что раскладка переключилась, и третья — о смене активного окна с его собственной раскладкой. Теоретически все указанное должно бы делаться через одну ловушку типа wh getmessage (см. описание hook в [17]), но на практике по ряду причин все будет надежно работать, только если сделать три специализированные: первую, как раньше, типа wh keyboard, вторую — wh getmessage, и третью — wh_cbt. Продвинутые читатели немедленно заметят, что, по крайней мере, нажатие клавиш можно также отслеживать через ловушку wh getmessage, но я их спешу успокоить— попробуйте сами, и вы убедитесь, что отслеживаются далеко не все нажатия, и, видимо, это так и должно быть (процедура GetMsgProc обрабатывает только оконные сообщения, системные

сообщения ею не обрабатываются [14]). Полный текст DLL приведен в листинге 7.2.

[ Листинг 7.2. Библиотека для "шпионской" ловушки с учетом ! раскладки клавиатуры

library Langhook; uses

Messages, Windows;

const

HookMsgKey =wm_User+$lll; Iсообщение о нажатии клавиши} HookMsgLangl =wm_User+$112; (язык – английский} HookMsgLang2 =wm_User+$113; {язык – русский}

var

HookHandleKey,HookHandleLang,HookHandleWin:hHook;

function KeyboardProc(Code: Integer; wParam: wParam; ]Param:

lParam): integer; stdcall; begin {перехват нажатия клавиатуры)

if code<0 then Result:=CallNextHookEx(HookHandleKey, code, WParam, LParam)

else

begin

Result:=CallNextHookEx(HookHandleKey, code, WParam, LParam);

if byte(LParam shr 24)<$80 then {только нажатие}

begin

if byte (LParam shr 16)= $1D then {клавиша RCoritrol}

if byte(LParam shr 24)=1 then {конкретно – дополнительная}

begin

SendMessage(FindWindow(‘DX•,’LangSwitch’), HookMsgKey,

WParam, LParam); Result:=1; end; end;

if byte (LParam shr 16)= $38 then Result:=1; {отключение Alt, как входа в меню)

end; end;

function LangProc(Code: Integer; wParam: wParam; lParam: lParam): integer; stdcall;

begin (перехват системного сообщения}

if codecO then Result:=CallNextHookEx(HookHandleLang, code, wParam, LParam)

else

begin

Result:=CallNextHookEx(HookHandleLang, code, wParam, LParam);

{запрос на изменение языка:! if (TMsg(Pointer(LParam)л).message = wm_INPUTLANGCHANGEREQUEST) then begin

(если раскладка русская:}

if word(TMsg(Pointer(LParam)л).IParam)=$419 then SendMessage(FindWindow(‘DX’,’LangSwitch’), HookMsgLang2,

WParam, LParam); {если раскладка английская:)

if word(TMsg(Pointer(LParam)л).lParam)=$409 then SenciMessage(FindWindow(‘DX’,’LangSwitch’), HookMsgLangl, WParam, LParam);

end; end; end;

function WinProc(Code: Integer; wParam: wParam; IParam: IParam): integer; stdcall;

begin {перехват перехода фокуса ввода}

if code<0 then Result:=CallNextHookEx(HookHandleWin, code, wParam, LParam)

else begin

Result:=0;

CallNextHookEx(HookHandleWin, code, wParam, LParam);

if code = HCBT_SETFOCUS then

{если окно собирается получить фокус ввода}

if (wParam<>FindWindow(‘DX’,’LangSwitch’))

then          {кроме окна LangSwitch)

begin

{если раскладка русская:} if

word(GetKeyboardLayout

(GetWindowThreadProcessId(wParam,nil)))=$419

then

SendMessage(FindWindow)’DX’,’LangSwitch’), HookMsgLang2, WParam, LParam);

(если раскладка английская:) if

word(GetKeyboardLayout

(GetWindowThreadProcessId(wParam,nil))>=$409

then

SendMessage(FindWindow(‘DX’,’LangSwitch’), HookMsgLangl, WParam, LParam);

end; end; end;

prooedure SetHook; stdcall; (установка ловушек) begin

HookHandleKey := SetWindowsHookEx(WH_KEYBOARD,KeyboardProc, hlnstance, 0);

HookHandleLang := SetWindowsHookEx(WH_GETMESSAGE,LangProc, hlnstance, 0);

HookHandleWin := SetWindowsHookEx(WH_CBT,WinProc,hlnstance, 0); end;

prooedure DelHook; stdcall; (удаление ловушек) begin

UnhookWindowsHookEx(HookHandleLang); UnhookWindowsHookEx(HookHandleKey) ; UnhookWindowsHookEx(HookHandleWin); end;

exports

SetHook, DelHook; begin end.

Заметьте, что клавиша для переключения раскладки заменена на правый <Ctrl> по той причине, что я сам пользуюсь программой при написании текста этой главы, а ранее установленный окончательный вариант (с пользовательской установкой клавиши, см. далее) пришлось, естественно, из системы удалить, иначе было бы невозможно проверять описываемые. Ну, а сам я привык пользоваться именно <Ctrl>.

Все подробности расписывать долго: в ловушке wh_getmessage (в процедуре LangProc) отслеживается событие vto_inputlangchangerequest (запрос на переключение языка), определяется, какой язык собирается быть загруженным, и посылается соответствующее сообщение нашей программе. А в ловушке wh cbt отслеживается переход фокуса ввода от окна к окну и определяется, какая раскладка действует в том окне, которое этот фокус получит. Характеристики ловушек, типы процедур, которые выполняются, и что означают в каждом случае параметры wparam и lParam, вы можете посмотреть в [17, 18J, а также, разумеется, на официальном сайте [16].

Теперь осталось внести изменения в основную программу LangSvvitch.dpr. Сначала объявим константы, те же, что и в ловушке:

const

idmEXIT-1; {пункт всплывающего меню "Закрыть") Ico_Mese«ge = wm_User+$110; {сообщение от иконки окну) HookMsgKey = wm_CJser+$lll; {сообщение о нажатии клавиши} HookMsgLangl = wm_User+$112; {язык – английский} HookMsgLang2 = wm_User+$113; {язык – русский}

Процедуры по обработке этих сообщений будут выглядеть так (вставить их надо, естественно, внутрь процедуры windowProc):

if Msg HookMsgKey then LayoutChange; {переключаем раскладку

по сообщению о нажатии клавиши} if Msg = HookMsgLangl then {переключение при смене фокуса ввода} begin

HIconl:=LoadIcon(instance,’MAINICONENG’);

{получаем дескриптор английской иконки) noIconData.hlcon:=HIconl;

Shell_NotifyIcon(NIM_Modi?y,SnoIconData); (поменяли иконку} end;

if Msg = HookMsgLang2 then {переключение при смене фокуса ввода) begin

HIconl:=LoadIcon(instance,’MAINICONRUS’);

(получаем дескриптор русской иконки} noIconData.hlcon:=НIcon1;

Shell_NotifyIcon(NIM_Modify,SnoIconData); {поменяли иконку) end;

Безупречной работы от нашей простой программы во всех случаях ожидать не следует. В Windows 98 не всегда отслеживается переключение в диалоговых окнах; в ХР с этим все в порядке, зато наблюдаются "глюки" в Office ХР с наиболее подходящими для нашей цели расширенными системными клавишами (правый <Ctrl>, правый <Enter>) — с обычными клавишвми все отлично работает, а вне Office нормально работает и с указанными. Этот недостаток преодолеть очень просто— надо зайти в Панель управления, запустить сервис Языки и региональные стандарты, и на вкладке Языки | Подробнее | Параметры клавиатуры | Смена сочетания клавиш снять галочку в пункте Переключать раскладки клавиатуры (оставив отмеченным пункт Переключать языки ввода)3. Тогда везде все заработает нормально, причем другой очевидный, казалось бы, путь решения проблемы — выбрать для имитации из двух возможных сочетаний клавиш только одно, реально установленное в системе для переключения языка— к желаемому результату не приводит. О том, что именно вы потеряете с отключением этого пункта, мы еще будем говорить в следующей главе.

Кроме того, в среде Windows 98 наблюдались конфликты с некоторыми автоматически запускаемыми сервисами (у автора — с файрволом Sygate), однако это происходило только, если включить программу в автозапуск, и тем самым она окажется в очереди сообщений позже указанных сервисов. Программа надежно работает, если она запускалась последней, однако "в лоб" через автозапуск это реализовать не удастся, все системные сервисы здесь запускаются в самую последнюю очередь3. А вот в ХР очередь сообщений работает иначе — но не будем в это углубляться, способы обойти проблем) у нас есть, а заниматься отладкой можно до бесконечности, чтобы убедиться в этом, достаточно заглянуть в историю версий любой подобной программы, хоть Punto, хоть RusLat. Кстати, казус с конфликтами ловушек встречается даже в фирменных приложениях— как пример можно привести конфликт очень удобного в использовании переводчика Socral от фирмы "Арсенал" с программами, написанными на Delphi.

Учитывая указанные обстоятельства, придется обязательно реализовать установки, какую именно клавишу использовать и заодно стоит ли выключать системную функцию <Alt> для входа в меню. На выбранном нами пути реализовать их не получится. Так что ловушки мы отладили, а теперь приступим к изучению отображения файлов на память (memory mapped files).

Заметки на полях

Почему глобальные ловушки не позволяют прямо передавать параметры в программу и обратно? Дело в особенностях функционирования динамически загружаемых библиотек (DLL) в Windows. Код установленной нами ловушки отображается в память всех процессов в программе, но только код— а не данные!

Поэтому параметры и будут действительны только для передавшей их программы, а ее нам как раз и не надо отслеживать. Механизм отображения файлов на память позволяет получить общее пространство данных для всех отображаемых процессов, и тогда передача будет работать. Есть и другие способы передачи данных между процессами, например, через ODE, через сообщение wm copydata, через так называемые "втомы", но в случае ловушек традиционно используются именно memory mapped files. Подробнее об этом см. (19, 20]. "Меппинг" нам еще понадобится в дальнейшем для другой цели — ускоренного чтения дисковых файлов (см. главу 14).

Источник: Ревнч Ю. В.  Нестандартные приемы программирования на Delphi. — СПб.: БХВ-Петербург, 2005. — 560 е.: ил.

По теме:

  • Комментарии