始终置顶






4.80/5 (12投票s)
2003年11月23日
3分钟阅读

189130

4224
一个 DLL,它创建了一个系统钩子来捕获 WM_INITMENUPOPUP 并将“始终置顶”选项添加到所有系统菜单中。
引言
在观看流媒体网络视频时,我喜欢同时进行多任务处理并使用其他应用程序。但是,与 Windows Media Player 不同的是,当视频嵌入 Internet Explorer 中时,没有“始终置顶”设置可以让我这样做。
Power Menu 是网络上提供的几种解决方案之一,可惜没有源代码 :(,但它有一些我不需要的附加选项。当然,我当时想“我自己也可以编写这个……”。
编码问题
此项目引发了许多编码问题。
- 隐藏基于对话框的应用程序
- 创建系统托盘图标
- 从 DLL 安装系统范围的钩子
- 挂接系统菜单事件并附加自定义项
尽管其中一些问题已在 Code Project 的其他地方处理过,但本文应能作为它们实际应用的有用示例。
隐藏基于对话框的应用程序
CDialog
类的默认行为使得隐藏基于对话框的 MFC 应用程序变得困难。为确保隐藏基于对话框的应用程序,需要执行以下步骤。
在 Visual Studio 资源编辑器中,将对话框的“可见”属性设置为 false
删除应用程序 InitInstance()
中对 DoModal
的引用
BOOL CAOTApp::InitInstance() { CWinApp::InitInstance(); m_pMainWnd = new CAOTDlg; return TRUE; }
创建新的 CAOTDlg 时,手动创建对话框。
CAOTDlg::CAOTDlg(CWnd* pParent /*=NULL*/) : CDialog(CAOTDlg::IDD, pParent)
{
m_hIcon = AfxGetApp()->
LoadIcon(IDR_MAINFRAME);
Create (IDD, pParent);
}
完成后,对话框需要自我销毁。我们可以做到这一点最晚的时间是在 PostNcDestroy()
中。
void CAOTDlg::PostNcDestroy() { delete this; // delete hidden dialog }
在某些情况下(这种情况就是其中之一),PostNcDestroy
不会自动调用,因此我们必须自己调用它。
void CAOTDlg::OnDestroy() { CDialog::OnDestroy(); // call default proc ClearHook (m_hWnd); // get rid of all of our hooks in the DLL PostNcDestroy (); // to ensure that hidden dialog is deleted }
系统托盘图标
Chris Maunder 的 CSystemTray 类 使添加系统托盘图标变得非常容易。
m_TrayIcon.Create (this, WM_ICON_NOTIFY, "Always on Top", m_hIcon, IDR_TRAYMENU);
要实现右键单击菜单(带默认项),我们只需要使用与托盘图标相同的 ID(即 IDR_TRAYMENU
)在资源编辑器中创建一个菜单。然后所有消息都将传递给 CSystemTray 类进行处理。
LRESULT CAOTDlg::OnTrayNotification(WPARAM wParam, LPARAM lParam) { // Delegate all the work back to the default // implementation in CSystemTray. return m_TrayIcon.OnTrayNotification(wParam, lParam); }
从 DLL 安装系统范围的钩子
所有系统范围的钩子都必须从 DLL 运行,以便可以从任何其他进程访问它们。创建 DLL 的基本要点如下。
为确保过程名称正确导出,并且不被链接器进行名称混淆,它们应该以 extern "C" 开头。
extern "C" { static LRESULT CALLBACK ShellProc(int nCode, WPARAM wParam, LPARAM lParam); static LRESULT CALLBACK MenuProc(int nCode, WPARAM wParam, LPARAM lParam); }
需要由使用 DLL 的不同进程(例如主窗口的句柄)访问的任何数据都需要存储在共享数据段中,并给出适当的链接器指令。
#pragma data_seg(".SHARDATA") HWND g_hwndMain = NULL; HHOOK g_hookShell = NULL; HHOOK g_hookMenu = NULL; HINSTANCE g_hInstance = NULL; UINT AOT_POPUP; UINT AOT_AOT; #pragma data_seg() #pragma comment(linker, "/section:.SHARDATA,rws")
DLL 的入口点是 DllMain
而不是 WinMain
。如果链接器给出错误,提示 _dllMain
已定义,则应从链接器开关中删除 _USRDLL
。
挂接系统菜单并附加自定义项
通过挂接 WH_CALLWNDPROC
并处理 WM_INITMENUPOPUP
来添加新菜单项。如果 lParam
的高位字为 TRUE
,则调用的菜单是系统菜单,我们可以添加我们的菜单项。
g_hookShell = SetWindowsHookEx(WH_CALLWNDPROC, (HOOKPROC)ShellProc, g_hInstance, 0); . . . CWPSTRUCT *wps = (CWPSTRUCT*)lParam; HWND hWnd = (HWND)(wps->hwnd); if(wps->message == WM_INITMENUPOPUP) { HMENU hMenu = (HMENU)wps->wParam; if ((IsMenu (hMenu) & (HIWORD(wps->lParam) == TRUE))) { if (GetWindowLong(hWnd, GWL_EXSTYLE) & WS_EX_TOPMOST) { AppendMenu (GetSystemMenu (hWnd, FALSE), MF_CHECKED | MF_BYPOSITION | MF_STRING, AOT_AOT, "Always on top"); } else { AppendMenu (GetSystemMenu (hWnd, FALSE), MF_BYPOSITION | MF_STRING, AOT_AOT, "Always on top"); } } }
起初我没有 bother 在菜单消失时将其移除,但这显然会在主对话框关闭且钩子被移除后仍然保留菜单项。
要删除新菜单项,需要处理 WM_MENUSELECT
消息。当菜单消失时,lParam
将为 NULL
,wParam
的高位字将为 0xFFFF
。如果存在,则删除 ID 为 AOT_AOT
的菜单项。
CWPSTRUCT *wps = (CWPSTRUCT*)lParam; HWND hWnd = (HWND)(wps->hwnd); if (wps->message == WM_MENUSELECT) { if((wps->lParam == NULL) && (HIWORD(wps->wParam) == 0xFFFF)) { RemoveMenu (GetSystemMenu (hWnd, FALSE), AOT_AOT, MF_BYCOMMAND); } }
菜单项本身通过挂接 WH_GETMESSAGE
并处理 WM_SYSCOMMAND
来处理,其中 wParam
的低位字是我们自定义菜单消息 AOT_AOT
。
MSG *msg = (MSG *)lParam; if ((msg->message == WM_SYSCOMMAND) && (LOWORD(msg->wParam) == AOT_AOT)) { if (GetWindowLong(HWND(msg->hwnd), GWL_EXSTYLE) & WS_EX_TOPMOST) { SetWindowPos(HWND(msg->hwnd), HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED); } else { SetWindowPos(HWND(msg->hwnd), HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED); } }
Bug
有两个有时会出现的错误
- “始终置顶”并不总是第一次调用时附加到系统菜单,尽管如果取消并再次调用,它就会在那里。 Winspector Spy 表明第一次调用
WM_INITPOPMENU
时,lParam
在这些情况下是不正确的。
- 单击任务栏中活动应用程序的图标并不总是会将其最小化(直到您右键单击其图标),
欢迎提出任何想法!