Оценка 155 Оценить ![]() ![]() ![]() ![]() ![]() ![]()
|
Исходные тексты элемента управления (winapi)
Демонстрационный проект (winapi)
Исходные тексты (MFC)
Демонстрационный проект (MFC)
WinHotkeyCtrl – элемент управления, предназначенный для задания и управления «горячими клавишами» (hotkey`s). В отличие от стандартного элемента управления Windows (HotKeyCtrl), WinHotkeyCtrl обладает рядом преимуществ:

WinHotkeyCtrl строится на базе стандартного элемента управления EditCtrl методом сабклассирования (subclassing), что обеспечивает удобство и легкость его использования с шаблонами окон диалогов.
С помощью директив препроцессора в одном исходном файле реализованы 2 версии WinHotkeyCtrl: для Windows 98/NT и для Windows 2000 (и выше).
Для начала необходимо сабклассировать окно элемента управления EditCtrl, чтобы можно было несколько изменить его функциональность, превратив в WinHotkeyCtrl.
WNDPROC _wpEditProc = NULL;
BOOL InitWinHotkeyCtrls() {
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
if (!GetClassInfoEx(GetModuleHandle(NULL), _T("Edit"), &wcex))
return(FALSE);
_wpEditProc = wcex.lpfnWndProc;
return(_wpEditProc != NULL);
}
BOOL SubClassWinHotkeyCtrl(HWND hwndWhc) {
_ASSERT(hwndWhc);
if (!_wpEditProc) // инициализация всех WinHokeyCtrlif (!InitWinHotkeyCtrls())
return(FALSE);
SetWindowLongPtr(hwndWhc, GWLP_WNDPROC, (LONG_PTR)(WNDPROC)_WinHotkeyCtrlProc);
SetWinHotkey(hwndWhc, MAKEWHCDATA(0, 0, 0, 0)); // см. ниже
return(TRUE);
}
LRESULT CALLBACK _WinHotkeyCtrlProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
// переопределяем или дополняем обработчики сообщений
}
return(CallWindowProc(_wpEditProc, hwnd, uMsg, wParam, lParam));
}
|
Для перехвата ввода с клавиатуры используются хуки (hook`s). В случае с Windows 98/NT – локальный WH_KEYBOARD, что несколько ограничивает функциональность WinHotkeyCtrl. А в Windows 2000 (и выше) используется RAW INPUT глобальный системный хук WH_KEYBOARD_LL.
| ПРИМЕЧАНИЕ Как написано в документации PlatformSDK, большинство глобальных системных хуков должны обязательно находиться в динамически подключаемой библиотеке DLL. При этом DLL подгружается в адресное пространство процесса производящего какое-либо «отлавливаемое» действие (например, посылку сообщений окну в случае WH_GETMESSAGE). Всё вышесказанное не относится к так называемым RAW INPUT хукам (WH_KEYBOARD_LL и WH_MOUSE_LL), появившимся в Windows NT 4.0 SP3. Их фильтрующая функция вызывается в том же потоке, который установил хук, методом посылки сообщения этому потоку. Таким образом, фильтрующая функция RAW INPUT хука может находиться и в EXE, а SetWindowsHookEx должна вызываться из GUI потока, имеющего очередь сообщений (окно). |
Хук устанавливается при получении одним из элементов управления WinHotkeyCtrl фокуса ввода (сообщение WM_SETFOCUS), а снимается, соответственно, при потере этим элементом фокуса ввода (WM_KILLFOCUS) или его разрушении (WM_DESTROY). Задача хука – отлавливать все сообщения от клавиатуры (WM_KEYDOWN, WM_SYSKEYDOWN, WM_KEYUP и WM_SYSKEYUP) и направлять сообщения о них активному элементу управления.
HWND _hwndWhc = NULL; // описатель окна активного элемента управления HHOOK _hhookKb = NULL; // описатель хука #if _WIN32_WINNT < 0x500 LRESULT CALLBACK _KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { if (nCode == HC_ACTION) { PostMessage(_hwndWhc, WM_KEY, wParam, (lParam & 0x80000000)); } return(1); // запрещаем дальнейшую обработку сообщения } #else// _WIN32_WINNT >= 0x500 LRESULT CALLBACK _LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { if (nCode == HC_ACTION && (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN || wParam == WM_KEYUP || wParam == WM_SYSKEYUP)) { PostMessage(_hwndWhc, WM_KEY, ((PKBDLLHOOKSTRUCT)lParam)->vkCode, (wParam & 1)); } return(1); // запрещаем дальнейшую обработку сообщения } #endif// _WIN32_WINNT >= 0x500 BOOL _UninstallKbHook() { BOOL fOk = FALSE; if (_hhookKb) { fOk = UnhookWindowsHookEx(_hhookKb); _hhookKb = NULL; } _hwndWhc = NULL; return(fOk); } BOOL _InstallKbHook(HWND hwndHkc) { if (_hhookKb) _UninstallKbHook(); _hwndWhc = hwndHkc; #if _WIN32_WINNT < 0x500 _hhookKb = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)_KeyboardProc, NULL, GetCurrentThreadId()); #else// _WIN32_WINNT >= 0x500 _hhookKb = SetWindowsHookEx(WH_KEYBOARD_LL, (HOOKPROC)_LowLevelKeyboardProc, GetModuleHandle(NULL), NULL); #endif// _WIN32_WINNT >= 0x500return(_hhookKb != NULL); } |
WinHotkeyCtrl, получив сообщение о клавиатурном вводе (WM_KEY = WM_USER + XXX), соответсвенно изменяет своё состояние. При этом wParam содержит виртуальный код нажатой (отпущенной) клавиши, а lParam – булево значение её текущего состояния (TRUE – если клавиша отпущена).
| ПРЕДУПРЕЖДЕНИЕ Крайне не рекомендуется производить в фильтрующей функции глобального системного хука каких-либо длительных операций (например, сложных расчётов или файловый ввод/вывод), в противном случае у пользователя может сложиться впечатление, что ваша программа (и операционная система) зависла. |
Собственно комбинация «горячих клавиш» определяется виртуальным кодом одной из алфавитно-цифровых клавиш, клавиш перемещения курсора и любых других, за исключением 4 клавиш-модификаторов (Win, Ctrl, Alt и Shift).
Так как WinHotkeyCtrl в данной реализации предполагает перехват абсолютно всех комбинаций «горячих клавиш», для хранения информации о текущем состоянии элемента управления вполне достаточно всего 4 байт (DWORD):
#define MAKEWHCDATA(vkCode, fModSet, fModRel, fIsPressed) \
((DWORD)(((BYTE)((DWORD_PTR)(vkCode) & 0xff)) | \
(((DWORD)((BYTE)((DWORD_PTR)(fModSet) & 0xff))) << 8) | \
(((DWORD)((BYTE)((DWORD_PTR)(fModRel) & 0xff))) << 16) | \
(((DWORD)((BYTE)((DWORD_PTR)(fIsPressed) & 0xff))) << 24)))
|
Где vkCode – виртуальный код клавиши, fModSet – флаги нажатых в данный момент клавиш-модификаторов (Win, Ctrl, Alt и Shift), fModRel – флаги отпущенных модификаторов, fIsPressed – булево значение, определяющее нажата ли в данный момент клавиша.
Таким образом, всю информацию о текущем состоянии WinHotkeyCtrl можно записать в блок пользовательских данных окна элемента управления GWLP_USERDATA (см. функцию SetWindowLongPtr в документации PlatformSDK). В противном случае пришлось бы выделять блок в куче процесса под структуру (new, malloc, HeapAlloc) и сохранять уже указатель на него. Желающие реализовать WinHotkeyCtrl с запрещенными комбинациями могут так и поступить.
| ПРИМЕЧАНИЕ Дополнительную информацию об элементе управления можно и в свойствах окна (см. функции SetProp, GetProp и RemoveProp в документации PlatformSDK). |
Алгоритм обработки сообщения от хука (WM_KEY) довольно прост, хотя и имеет ряд нюансов:
case WM_KEY: {
DWORD dwWhcData = (DWORD)GetWindowLongPtr(hwnd, GWLP_USERDATA);
DWORD vkCode = LOBYTE(LOWORD(dwWhcData));
DWORD fModSet = HIBYTE(LOWORD(dwWhcData));
DWORD fModRel = LOBYTE(HIWORD(dwWhcData));
BOOL fIsPressed = HIBYTE(HIWORD(dwWhcData));
DWORD fMod = 0;
BOOL fRedraw = TRUE;
switch (wParam) {
case VK_LWIN:
case VK_RWIN: fMod = MOD_WIN; break;
case VK_CONTROL:
case VK_LCONTROL:
case VK_RCONTROL: fMod = MOD_CONTROL; break;
case VK_MENU:
case VK_LMENU:
case VK_RMENU: fMod = MOD_ALT; break;
case VK_SHIFT:
case VK_LSHIFT:
case VK_RSHIFT: fMod = MOD_SHIFT; break;
}
if (fMod) { // клавиша-модификаторif (!lParam) { // нажатаif(!fIsPressed && vkCode) {
fModSet = fModRel = 0;
vkCode = 0;
}
fModRel &= ~fMod;
} elseif (fModSet & fMod) // отпущена
fModRel |= fMod;
if (fIsPressed || !vkCode) {
if (!lParam) {
if (!(fModSet & fMod)) { // новый модификатор
fModSet |= fMod;
} else
fRedraw = FALSE;
} else fModSet &= ~fMod;
}
} else { // обычная клавишаif (wParam == VK_DELETE && fModSet == (MOD_CONTROL | MOD_ALT)) {
fModSet = fModRel = 0; // пропустить Ctrl+Alt+Del
vkCode = 0;
fIsPressed = FALSE;
} elseif (wParam == vkCode && lParam) {
fIsPressed = FALSE;
fRedraw = FALSE;
} else {
if (!fIsPressed && !lParam) { // была нажата сначала одна, а теперь - другаяif (fModRel & fModSet) {
fModSet = fModRel = 0;
}
vkCode = (DWORD)wParam;
fIsPressed = TRUE;
}
}
}
dwWhcData = MAKEWHCDATA(vkCode, fModSet, fModRel, fIsPressed);
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG)dwWhcData);
if (fRedraw) // чтобы избежать мерцания
_SetWhcText(hwnd, dwWhcData);
return(0);
}
|
Стандартное меню EditCtrl`а (с командами «Вырезать», «Вставить» и т. д.) нужно либо вообще убрать, либо заменить своим. В обоих случаях необходимо переопределить обработчик сообщения элемента управления WM_CONTEXTMENU, например, так:
case WM_CONTEXTMENU: {
UINT id;
HMENU hmenu, hmenu2;
hmenu = CreatePopupMenu();
#if _WIN32_WINNT >= 0x500
hmenu2 = CreatePopupMenu();
for (id = VK_BROWSER_BACK; id <= VK_LAUNCH_APP2; id++)
AppendMenu(hmenu2, MF_STRING, id, GetKeyName(id));
AppendMenu(hmenu, MF_STRING | MF_POPUP, (UINT_PTR)hmenu2, _T("Multimedia"));
#endif// _WIN32_WINNT >= 0x500
hmenu2 = CreatePopupMenu();
AppendMenu(hmenu2, MF_STRING, VK_RETURN, GetKeyName(VK_RETURN));
AppendMenu(hmenu2, MF_STRING, VK_ESCAPE, GetKeyName(VK_ESCAPE));
AppendMenu(hmenu2, MF_STRING, VK_TAB, GetKeyName(VK_TAB));
AppendMenu(hmenu2, MF_STRING, VK_CAPITAL, GetKeyName(VK_CAPITAL));
AppendMenu(hmenu2, MF_STRING, VK_BACK, GetKeyName(VK_BACK));
AppendMenu(hmenu2, MF_STRING, VK_INSERT, GetKeyName(VK_INSERT));
AppendMenu(hmenu2, MF_STRING, VK_DELETE, GetKeyName(VK_DELETE));
for (id = VK_SPACE; id <= VK_DOWN; id++)
AppendMenu(hmenu2, MF_STRING, id, GetKeyName(id));
AppendMenu(hmenu, MF_STRING | MF_POPUP, (UINT_PTR)hmenu2, _T("Standard"));
hmenu2 = CreatePopupMenu();
for (id = VK_F1; id <= VK_F24; id++)
AppendMenu(hmenu2, MF_STRING, id, GetKeyName(id));
AppendMenu(hmenu, MF_STRING | MF_POPUP, (UINT_PTR)hmenu2, _T("Functionality"));
DWORD dwWhcData = (DWORD)GetWindowLongPtr(hwnd, GWLP_USERDATA);
DWORD vkCode = LOBYTE(LOWORD(dwWhcData));
DWORD fModSet = HIBYTE(LOWORD(dwWhcData));
DWORD fModRel = LOBYTE(HIWORD(dwWhcData));
BOOL fIsPressed = HIBYTE(HIWORD(dwWhcData));
AppendMenu(hmenu, MF_SEPARATOR, 0, NULL);
AppendMenu(hmenu, (fModSet & MOD_WIN) ?
(MF_STRING | MF_CHECKED) : MF_STRING, VK_LWIN, _T("Win-key"));
AppendMenu(hmenu, (fModSet & MOD_CONTROL) ?
(MF_STRING | MF_CHECKED) : MF_STRING, VK_CONTROL, _T("Control-key"));
AppendMenu(hmenu, (fModSet & MOD_SHIFT) ?
(MF_STRING | MF_CHECKED) : MF_STRING, VK_SHIFT, _T("Shift-key"));
AppendMenu(hmenu, (fModSet & MOD_ALT) ?
(MF_STRING | MF_CHECKED) : MF_STRING, VK_MENU, _T("Alt-key"));
UINT uMenuID = TrackPopupMenu(hmenu,
TPM_RIGHTALIGN | TPM_RIGHTBUTTON | TPM_NONOTIFY | TPM_RETURNCMD,
LOWORD(lParam), HIWORD(lParam), 0, hwnd, NULL);
if (uMenuID && uMenuID < 256) {
switch (uMenuID) {
case VK_LWIN:
if (vkCode) {
fModSet ^= MOD_WIN;
fModRel |= fModSet & MOD_WIN;
}
break;
case VK_CONTROL:
if (vkCode) {
fModSet ^= MOD_CONTROL;
fModRel |= fModSet & MOD_CONTROL;
}
break;
case VK_SHIFT:
if (vkCode) {
fModSet ^= MOD_SHIFT;
fModRel |= fModSet & MOD_SHIFT;
}
break;
case VK_MENU:
if (vkCode) {
fModSet ^= MOD_ALT;
fModRel |= fModSet & MOD_ALT;
}
break;
default:
vkCode = uMenuID;
fIsPressed = FALSE;
break;
}
dwWhcData = MAKEWHCDATA(vkCode, fModSet, fModRel, fIsPressed);
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG)dwWhcData);
_SetWhcText(hwnd, dwWhcData);
SetFocus(hwnd);
}
DestroyMenu(hmenu);
return(0);
}
|
Чтобы отслеживать изменение состояния WinHotkeyCtrl достаточно в обработчик EN_CHANGE сообщения WM_COMMAND родительского окна (диалога) вставить проверку:
void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify) {
if (codeNotify == EN_CHANGE) {
if (id == IDC_WINHOTKEY) {
BOOL fEnable = (BOOL)(LOBYTE(LOWORD(GetWinHotkey(hwndCtl))) != 0);
EnableWindow(GetDlgItem(hwnd, IDC_BUTTON), fEnable);
}
} elseif (codeNotify == BN_CLICKED) {
switch (id) {
case IDC_BUTTON:
// ...break;
}
}
}
|
Благодаря совместимому формату «горячую клавишу» легко зарегистровать:
DWORD hk = GetWinHotkey(hwndWhc);
UINT fModifiers = HIBYTE(LOWORD(hk)),
vkCode = LOBYTE(LOWORD(hk));
if (vkCode)
RegisterHotKey(hwnd, IDH_HOTKEY, fModifiers, vkCode);
|
При использовании библиотеки MFC суть реализации остается прежней, меняется только форма в соответсвии с принципами объектно-ориентированного программирования и самой библиотеки MFC.
class CWinHotkeyCtrl : public CEdit
{
DECLARE_DYNAMIC(CWinHotkeyCtrl)
public:
CWinHotkeyCtrl();
virtual ~CWinHotkeyCtrl();
void UpdateText();
DWORD GetWinHotkey();
BOOL GetWinHotkey(UINT* pvkCode, UINT* pfModifiers);
void SetWinHotkey(DWORD dwHk);
void SetWinHotkey(UINT vkCode, UINT fModifiers);
private:
static HHOOK sm_hhookKb;
static CWinHotkeyCtrl* sm_pwhcFocus;
UINT m_vkCode;
DWORD m_fModSet;
DWORD m_fModRel;
BOOL m_fIsPressed;
private:
BOOL InstallKbHook();
BOOL UninstallKbHook();
#if _WIN32_WINNT < 0x500
static LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam);
#else// _WIN32_WINNT >= 0x500static LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam);
#endif// _WIN32_WINNT >= 0x500
afx_msg LRESULT OnKey(WPARAM wParam, LPARAM lParam);
protected:
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);
afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);
afx_msg void OnSetFocus(CWnd* pOldWnd);
afx_msg void OnKillFocus(CWnd* pNewWnd);
afx_msg void OnContextMenu(CWnd* /*pWnd*/, CPoint /*point*/);
afx_msg void OnDestroy();
protected:
virtualvoid PreSubclassWindow();
};
|
При этом единственное кардинальное отличие MFC-версии WinHotkeyCtrl состоит в том, что вместо описателя окна элемента управления в статической переменной сохраняется указатель на его класс.
| ПРИМЕЧАНИЕ Все проекты демонстрационных версий собраны в Microsoft Visual C++ 7.1. |
Оценка 155 Оценить ![]() ![]() ![]() ![]() ![]() ![]()
|