Все началось с того, что, печатая код в полноэкранном режиме, меня доставало переключение языков. У меня 3 раскладки, в коде нужно часто вставлять комментарии и другое. Много времени теряю на это, потому решил совместить следующую статью о хуках с этой проблемой.
Идея предельно проста, наша программа «сидит» в трее, при изменении раскладки клавиатуры мы показываем нужный флаг в определенной части экрана, с определенным размером.
Хуки? А что это?
Для начала, разберемся с хуками.
Hook – точка в механизме обработки сообщений, где мы можем следить за сообщениями какого-то потока, или всех существующих.
Существует много различных типов точек обработки сообщений в ОС Windows. Каждая из них существует для определенных сообщений. Например, мы можем следить только за сообщениями от клавиатуры (WH_KEYBOARD), или же за сообщениями от мыши (WH_MOUSE).
Чтобы создать хук, вам нужно сделать:
- Создать хук, установить его в цепочку обработки сообщений.
- Создать специальную функцию повторного вызова (callback-функция), которая также называется подключаемой процедурой.
- Все подключаемые процедуры имеют такой вид:
LRESULT CALLBACK HookProc
(
int nCode,
WPARAM wParam,
LPARAM lParam
)
|
- nCode – код hook-точки, который подключаемая процедура использует для идентификации действия.
- wParam – зависит от hook-точки.
- lParam – зависит от hook-точки.
- Для установки подключаемой процедуры в начало цепочки hook-точек, нам нужно вызвать WinApi-функцию SetWindowsHookEx.
HHOOK SetWindowsHookEx(
int idHook,
HOOKPROC lpfn,
HINSTANCE hMod,
DWORD dwThreadId
);
|
- idHook – тип hook-процедуры (WH_MOUSE, WH_KEYBOARD…)
- lpfn – Подключаемая процедура, callback-функция.
- hMod – handle для DLL, в которой будем ставить хук и обрабатывать его. Если нужно сделать локальный хук, этот параметр нужно поставить в NULL
- dwThreadId – идентификатор потока, с которым ассоциировать подключаемую процедуру. Если нужно контролировать все поток, этот параметр должен быть 0.
И еще одна важная часть, в hook procedure нам нужно вызывать ф-цию CallNextHookEx, она передает управление другой процедуре (если это нужно).
Думаю, теперь у вас хватит знаний, чтобы разобраться с кодом.
Пишем основную часть приложения
Давайте продумаем принцип работы программы: у нас, в папке с программой, лежат рисунки флагов. При получении нажатия на клавиатуре, мы сверяем, сменился ли язык ввода. Если изменения есть: мы создаем новое окно, передаем ему адрес на нужный рисунок, и, учитывая все настройки(прозрачность флага, время показа, размер, положение) показываем форму с флагом на фоне.
Для начала, нужно создать класс, в котором будут все настройки программы. Создайте новый класс Settings.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace OckShowKeyboardState
{
class Settings
{
public static string LastInputLanguage = "USA";
public static string FlagsPath = "flags/";
public static int ShowMiliseconds = 600;
public static double OpacityFlag = 0.8;
public static int XShowWindowPos = 0;
public static int YShowWindowPos = 0;
public static int WFlagSize = 100;
public static int HFlagSize = 65;
public static bool NowShowFlag = false;
}
}
|
Теперь создайте Windows-Form, имя: ShowFlag
Укажите его параметры:
- BackgroundImageLayout – Stretch
- FormBorderStyle – None
- ShowInTaskbar – None
- StartPosition – Manual
- TopMost - True
Также поместите на форму таймер (UpdateForm), по которому эта же форма будет закрываться. Вот код формы:
namespace OckShowKeyboardState
{
public partial class ShowFlag : Form
{
public ShowFlag(string BgImagePath, IntPtr hWnd)
{
InitializeComponent();
Bitmap FlagImage = new Bitmap(BgImagePath);
this.BackgroundImage = FlagImage;
this.Width = Settings.WFlagSize;
this.Height = Settings.HFlagSize;
this.Location = new Point(Settings.XShowWindowPos; Settings.YShowWindowPos);
this.Opacity = Settings.OpacityFlag;
UpdateForm.Interval = Settings.ShowMiliseconds;
UpdateForm.Enabled = true;
Hooks.SetForegroundWindow(hWnd);
}
private void UpdateForm_Tick(object sender, EventArgs e)
{
Settings.NowShowFlag = false;
this.Close();
}
}
}
|
Как всегда, пишем «ядро» программы. Здесь мы будем ставить ставить, обрабатывать hook-процедуру.
Создайте новый проект. Добавьте новый класс Hooks.cs Все комментарии и объяснения в коде:
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Threading;
namespace OckShowKeyboardState
{
class Hooks
{
private delegate IntPtr keyboardHookProc(int code, IntPtr wParam, IntPtr lParam);
const int WH_KEYBOARD_LL = 13;
const int WM_KEYDOWN = 0x100;
private static keyboardHookProc _proc = hookProc;
private static IntPtr hhook = IntPtr.Zero;
private static ShowFlag WindowShowFlag;
private static int _ProcessId;
private static InputLanguageCollection _InstalledInputLanguages;
private static string _CurrentInputLanguage;
public static void SetHook()
{
IntPtr hInstance = LoadLibrary("User32");
hhook = SetWindowsHookEx(WH_KEYBOARD_LL, _proc, hInstance, 0);
}
public static void UnHook()
{
UnhookWindowsHookEx(hhook);
}
private static string GetKeyboardLayoutId()
{
_InstalledInputLanguages = InputLanguage.InstalledInputLanguages;
IntPtr hWnd = GetForegroundWindow();
int WinThreadProcId = GetWindowThreadProcessId(hWnd, out _ProcessId);
IntPtr KeybLayout = GetKeyboardLayout(WinThreadProcId);
for (int i = 0; i < _InstalledInputLanguages.Count; i++)
{
if (KeybLayout == _InstalledInputLanguages[i].Handle)
{
_CurrentInputLanguage = _InstalledInputLanguages[i].Culture.ThreeLetterWindowsLanguageName.ToString();
}
}
return _CurrentInputLanguage;
}
public static IntPtr hookProc(int code, IntPtr wParam, IntPtr lParam)
{
if (code >= 0 && wParam == (IntPtr)WM_KEYUP)
{
IntPtr hWnd = GetForegroundWindow();
string NowLanguage = GetKeyboardLayoutId();
string Path = Settings.FlagsPath + GetKeyboardLayoutId() + ".GIF";
if (Settings.NowShowFlag)
{
Settings.LastInputLanguage = NowLanguage;
}
if (Settings.LastInputLanguage != NowLanguage)
{
Settings.NowShowFlag = true;
WindowShowFlag = new ShowFlag(Path, hWnd);
Settings.LastInputLanguage = NowLanguage;
WindowShowFlag.Show();
}
}
return CallNextHookEx(hhook, code, (int)wParam, lParam);
}
[DllImport("user32.dll")]
static extern IntPtr SetWindowsHookEx(int idHook, keyboardHookProc callback, IntPtr hInstance, uint threadId);
[DllImport("user32.dll")]
static extern bool UnhookWindowsHookEx(IntPtr hInstance);
[DllImport("user32.dll")]
static extern IntPtr CallNextHookEx(IntPtr idHook, int nCode, int wParam, IntPtr lParam);
[DllImport("kernel32.dll")]
static extern IntPtr LoadLibrary(string lpFileName);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int GetWindowThreadProcessId(IntPtr handleWindow, out int lpdwProcessID);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr GetKeyboardLayout(int WindowsThreadProcessID);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SetForegroundWindow(IntPtr hWnd);
}
}
|
Как видите, здесь я использую импортирование WinApi функций. Делается это просто: посмотрите в MSDN имя длл, в которой расположена нужная функция, потом в проект нужно подключить
using System.Runtime.InteropServices;
|
чтобы использовать директиву DllImport. Далее по такому шаблону делать импорт:
[DllImport("Name.dll", CharSet = CharSet.Auto)]
public static extern ReturnType FunctionName(Parameters);
|
Вот и все, основная часть программы написана. Теперь только нужно вызвать хук в конструкторе основной формы, убрать хук, в обработчике закрытия формы.
Установка хука:
Уничтожение:
Далее по вашему желанию вы можете использовать основную форму для настроек программы (то, что у нас в классе Settings.cs).
Вот что у меня получилось:

А вот такая у меня получилась главная форма:

Удачи.
Тысленко Макс (Ockonal)
03.12.2008