Orion9

|
Posted: Wed Sep 16, 2026 13:40 Post subject: |
|
|
AkulaBig
В системное меню главного окна ТС была попытка добавить новые пункты, но они не работают. Ниже есть ответ самого Loopback почему.
В контекстное меню можно таким же образом попробовать добавить, но для начала придется с болваном изрядно пообщаться
 Hidden text Для динамического изменения стандартных или сторонних контекстных меню в Win32 API (C++) используется перехват оконных сообщений через хук вызовов процедур (CBT hook или GetMessage hook). Основная задача — поймать момент создания окна меню класса #32768 (системный класс для всплывающих меню), получить его хэндл (HMENU) и встроить туда свой элемент.
Ниже представлен классический способ установки хука WH_CBT перед вызовом TrackPopupMenu для внедрения кастомного пункта.
Реализация на C++ (Win32 API)
cpp
#include <windows.h>
#include <iostream>
// Глобальные переменные для управления хуком
HHOOK g_hHook = NULL;
UINT_PTR g_MyCustomItemID = 9999; // Уникальный ID вашего пункта меню
// Процедура хука CBT (Computer Based Training)
LRESULT CALLBACK CbtMenuHookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
// HCBT_CREATEWND вызывается в момент создания любого окна
if (nCode == HCBT_CREATEWND)
HWND hWndMenu = (HWND)wParam;
wchar_t className[32];
GetClassNameW(hWndMenu, className, 32);
// #32768 — это предопределенный атомарный класс для Windows Context Menu
if (wcscmp(className, L"#32768") == 0)
{
// Посылаем сообщение меню, чтобы получить его HMENU хэндл
// (или используем MN_GETHMENU = 0x01E5 начиная с Windows 2000)
HMENU hMenu = (HMENU)SendMessageW(hWndMenu, 0x01E5, 0, 0);
if (hMenu)
{
// Вставляем разделитель
AppendMenuW(hMenu, MF_SEPARATOR, 0, NULL);
// Вставляем наш кастомный элемент в конец меню
AppendMenuW(hMenu, MF_STRING, g_MyCustomItemID, L"Мой кастомный пункт");
}
}
}
// Передаем управление следующим хукам в цепочке
return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}
// Пример функции вызова меню
void ShowMenuWithHook(HWND hWndOwner, HMENU hOriginalMenu, int x, int y)
{
// 1. Устанавливаем локальный WH_CBT хук на текущий поток
g_hHook = SetWindowsHookExW(WH_CBT, CbtMenuHookProc, NULL, GetCurrentThreadId());
// 2. Вызываем стандартный трекинг меню.
// Пока меню инициализируется, сработает наш хук CbtMenuHookProc.
TrackPopupMenu(hOriginalMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, x, y, 0, hWndOwner, NULL);
// 3. Сразу после закрытия меню снимаем хук, чтобы не тратить ресурсы
if (g_hHook) {
UnhookWindowsHookEx(g_hHook);
g_hHook = NULL;
}
}
Используйте код с осторожностью.
Как обработать нажатие?
Когда пользователь нажмет на добавленный пункт меню, операционная система отправит стандартное уведомление WM_COMMAND в окно-владелец (hWndOwner). Обработайте его в оконной процедуре (WndProc):
cpp
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_COMMAND:
// Проверяем, совпадает ли ID с нашим кастомным пунктом
if (LOWORD(wParam) == g_MyCustomItemID)
{
MessageBoxW(hWnd, L"Кастомный пункт меню нажат!", L"Успех", MB_OK);
return 0;
}
break;
// ... обработка других сообщений ...
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
Используйте код с осторожностью.
⚠️ Важные нюансы
• Глобальный vs Локальный хук: Приведенный пример использует локальный хук (GetCurrentThreadId()), который модифицирует меню только внутри вашего приложения. Если ваша цель — внедриться в чужой процесс (например, в контекстное меню Проводника Windows/Explorer), вам потребуется компилировать хук в отдельную .dll и внедрять его глобально через WH_GETMESSAGE или использовать официальные Shell Extension (IContextMenu).
• Сообщение MN_GETHMENU (0x01E5): Это системное недокументированное сообщение, стабильно работающее во всех версиях Windows, начиная с Windows XP и заканчивая Windows 11. Оно возвращает внутренний хэндл HMENU для окна всплывающего меню.
 Hidden text Когда в системе или приложении одновременно открывается множество меню, идентифицировать именно ваше окно меню #32768 внутри хука можно по контекстным признакам. Само по себе окно меню обезличено, но у него есть связи с другими элементами интерфейса.
Вот 4 основных способа со 100% точностью определить, что перехваченное меню — то, которое вам нужно:
1. Проверка по координатам мыши (Самый простой способ)
Контекстное меню почти всегда открывается там, где пользователь кликнул мышкой. Вы можете сравнить позицию курсора с координатами создаваемого окна меню.
cpp
if (wcscmp(className, L"#32768") == 0)
{
// Получаем текущие координаты курсора
POINT ptCursor;
GetCursorPos(&ptCursor);
// Получаем координаты создаваемого меню
RECT rcMenu;
GetWindowRect(hWndTarget, &rcMenu);
// Проверяем, находится ли курсор в пределах или вблизи точки создания меню
// (делаем небольшой задел в 5-10 пикселей на случай смещения)
if (abs(rcMenu.left - ptCursor.x) < 10 && abs(rcMenu.top - ptCursor.y) < 10)
{
// Это с высокой вероятностью ваше меню, вызванное кликом!
}
}
Используйте код с осторожностью.
2. Проверка окна-владельца (Owner Window)
Каждое всплывающее меню привязано к конкретному окну (кнопке, форме или главному окну программы), которое его вызвало. Через функцию GetWindow с флагом GW_OWNER можно узнать хэндл (HWND) этого родительского окна и сравнить его с вашим.
cpp
if (wcscmp(className, L"#32768") == 0)
{
// Находим окно, которое создало/владеет этим меню
HWND hWndOwner = GetWindow(hWndTarget, GW_OWNER);
// g_MyTargetWindow — HWND вашего конкретного текстового поля, кнопки или формы
if (hWndOwner == g_MyTargetWindow)
{
// Меню принадлежит вашему конкретному элементу управления!
}
}
Используйте код с осторожностью.
3. Анализ существующего содержимого меню (Фильтрация по тексту пунктов)
Если вы знаете, какие пункты должны быть в целевом меню по умолчанию, вы можете проверить их наличие с помощью функции GetMenuStringW. Если меню содержит специфическое слово (например, "Копировать", "Свойства" или уникальное имя из сторонней библиотеки), значит это оно.
cpp
HMENU hMenu = (HMENU)SendMessageW(hWndTarget, 0x01E5, 0, 0);
if (hMenu)
{
wchar_t menuText[256];
// Проверяем, например, самый первый пункт меню (индекс 0)
GetMenuStringW(hMenu, 0, menuText, 256, MF_BYPOSITION);
// Если там написано то, что мы ожидаем увидеть в целевом меню:
if (wcscmp(menuText, L"Ожидаемый Пункт") == 0)
{
// Это наше меню. Встраиваем кастомный элемент.
}
}
Используйте код с осторожностью.
4. Включение хука строго «по требованию» (Паттерн Guard)
Вместо того чтобы держать хук постоянно активным и гадать, чье меню открылось, включайте его строго за миллисекунду до вызова меню и выключайте сразу же после.
Если меню вызывается сторонней функцией (ThirdPartyShowMenu()), оберните её:
cpp
// 1. Включаем хук прямо перед вызовом
g_hMenuHook = SetWindowsHookExW(WH_CBT, CbtMenuHookProc, NULL, GetCurrentThreadId());
// 2. Вызываем функцию, которая триггерит появление меню
ThirdPartyShowMenu();
// 3. Выключаем хук. Все остальные меню в системе теперь будут игнорироваться.
UnhookWindowsHookEx(g_hMenuHook);
Используйте код с осторожностью.
Примечание: Поскольку TrackPopupMenu внутри сторонних библиотек обычно блокирует поток до тех пор, пока меню не закроется, хук гарантированно перехватит только это конкретное меню и снимется сразу после его закрытия.
Это только для примера. На практике с ним можно долго общаться, пока он не перестанет выдавать что-то полезное. |
|