Как отловить смену раскладки клавиатуры (WM_INPUTLANGCHANGE)?

Форма на коннект с БД. Два поля ввода: имя и пароль. И вот я решил на нее прикрепить флажок, чтобы юзеру было удобнее видеть в какой от раскладке. Вот в этой форме я и не могу поймать WM_INPUTLANGCHANGE.
Причем, если я заменяю сообщение на какое либо другое, например WM_SIZE, то все нормально отлавливается.
Кто-нибудь в Дельфях (Билдере) его поймать?
Господи, да чего там ловить-то?? Все ловится на раз.

Пример 1.

type
TMainWnd = class(TForm)
LabLang: TLabel;
public
procedure WmInputLangChange( var msg :TMessage ); message WM_INPUTLANGCHANGE;
end;

procedure TMainWnd.WmInputLangChange(var msg: TMessage);
var
szBuf : array[0..KL_NAMELENGTH] of Char;
begin
GetKeyboardLayoutName( @(szBuf[0]) );
LabLang.Caption := 'Keyboard layout was set to '
+ string(szBuf)
+ ' at ' + FormatDateTime( 'hh:nn:ss', Time ) ;
end;



Может у тебя окно твое того... не topmost, или child? Проверь window style
>А если в примере 1 на Вашу TMainWnd положить TEdit или TButton, которые должны присутствовать в логин-окне, то она перестанет ловить WM_INPUTLANGCHANGE.
Действительно. Никогда раньше на это не натыкался. И в самом деле идея c subclassing от WPooh в некоторых случаях помогает.

Однако стало интересно разобраться, что же все-таки происходит с этим сообщением на самом деле. Минут пятнадцать с WinSight32 и парой тестов помогли мне выяснить следующее.

  1. Если в окне нет контролов, которые могут имеют фокус воода, или контролы есть, но фокуса ни у одного из них нет, то пара CHANGEREQUEST/CHNAGE идет окну.
  2. Если в окне есть контрол, имеющий фокус ввода, то СHANGEREQUEST/CHNAGE идет контролу, a не окну. Причем это не обязательно Edit Control. Сообщения пойдут любому контролу с фокусом, например Button Control'у.
  3. На все вышеизложенное, накладывается одна интересная особенность (наблюдалось во всех случаях, не важно, главному окну ли шлются сообщения, или контролу). WM_INPUTLANGCHANEGEREQUEST всегда идет dispatched (т.е. через очередь сообщений), а WM_INPUTLANGCHANE всегда sent (т.е. Windows вызывает оконную поцедуру напрямую - вот почему у WPooh получилось).


Описанное поведение наблюдалось как под W2KPro, так и под Win95 (т.е. есть основания полагать, что и под всеми остальными - то же самое). Я нигде не смог найти упоминания от такой специфике WM_INPUT... сообщений. Наверное MS забыла нам об этом рассказать.

Выводы

  • В общей очереди (как советует Faizullin Rustam) можно перехватить только WM_..REQUEST. Впрочем, если Ваша программа не собирается "reject the change", то этого вполне достаточно.
  • Если нужно ловить именно WM_..CHANGE, то ситуация хуже. Придется делать subclassing и все время учитывать, кто имеет фокус. И хорошо, если это только один контрол (как в случае с типичным login-окном). Но для login-окна это, пожалуй, несложно. Сразу засубклассить все контролы (их там явно мало) + одработчик для главного окна


P.S. Я пожалуй погорячился, когда сказал, что "MS забыла". В WinAPI reference все таки написано, что "WM_..REQUEST is posted", а "WM_..CHANGE is sent" . Но все равно, все эт так невнятно...

(с) Kid_Deceiver
Дело в том, что у меня MDI приложение. Как только я исправил стиль главной формы на fsNormal она начинает спокойно отлавливать эту мессагу.
Но при этом мое логон-окно, которое тоже имеет стиль fsNormal и выводится как модальное продолжает отказываться его ловить.

В связи с этим вопрос еще более конкретизируется:
Можно ли в MDI приложении ловить WM_INPUTLANGCHANGE.
Вот этот пример работает и со стилем fMDIform(это Дельфи 3)

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;

type
TForm1 = class(TForm)
Edit1: TEdit;
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
procedure MyProc(var m:TMsg;var Handled: Boolean);
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.MyProc(var m:TMsg;var Handled: Boolean);
begin
if (m.message=WM_INPUTLANGCHANGEREQUEST)or
(m.message=WM_INPUTLANGCHANGE)
then
ShowMessage('LanguageChange');
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
Application.OnMessage:=MyProc;
end;

end.
Взяв за основу предложение WPooh c subclassing, удается ловить WM_INPUTLANGCHANGE:
class TForm1 : public TForm
{
__published: // IDE-managed Components
TLabel *Label1; // "Пользователь"
TEdit *Edit1; // для ввода пользователя
TLabel *Label2; // "Пароль"
TEdit *Edit2; // для ввода пароля
TButton *Button1; // ok
TButton *Button2; // Cancel
TLabel *Label3; // вывод языка
void __fastcall FormActivate(TObject *Sender);
private: // User declarations
public: // User declarations
__fastcall TForm1(TComponent* Owner);
FARPROC wpEdit1;
FARPROC wpEdit2;
FARPROC wpButton1;
FARPROC wpButton2;
void __fastcall ShowKbdLayout(void);
};
...
//---------------------------------------------------------------------------
LRESULT APIENTRY MySubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
FARPROC fp;

if (uMsg == WM_INPUTLANGCHANGE)
Form1->ShowKbdLayout();

if (hwnd == Form1->Edit1->Handle)
fp = Form1->wpEdit1;
else if (hwnd == Form1->Edit2->Handle)
fp = Form1->wpEdit2;
else if (hwnd == Form1->Button1->Handle)
fp = Form1->wpButton1;
else if (hwnd == Form1->Button2->Handle)
fp = Form1->wpButton2;

return ::CallWindowProc(fp, hwnd, uMsg, wParam, lParam);
}
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
wpEdit1 = (FARPROC)::SetWindowLong(Edit1->Handle, GWL_WNDPROC, (LONG)MySubclassProc);
wpEdit2 = (FARPROC)::SetWindowLong(Edit2->Handle, GWL_WNDPROC, (LONG)MySubclassProc);
wpButton1 = (FARPROC)::SetWindowLong(Button1->Handle, GWL_WNDPROC, (LONG)MySubclassProc);
wpButton2 = (FARPROC)::SetWindowLong(Button2->Handle, GWL_WNDPROC, (LONG)MySubclassProc);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ShowKbdLayout(void)
{
TCHAR buf[ KL_NAMELENGTH ];
if (GetKeyboardLayoutName(buf))
Label3->Caption = buf;
else
Label3->Caption = "GetKeyboardLayoutName failed";
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormActivate(TObject *Sender)
{
ShowKbdLayout();
}
//---------------------------------------------------------------------------
Возможно это не очень красивое решение

(c) Prog2 Michael

TopList Rambler's Top100