![]() |
Desktop Development »
Dialogs and Windows »
General
Intermediate
Always on TopBy Rob LangstonA DLL which creates a system hook to trap WM_INITMENUPOPUP and append an "Always on Top" option to all system menus. |
VC7WinXP, MFC, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
When watching streaming internet video I like to multitask and use other applications at the same time. However, unlike Windows Media Player, when the video is embedded in Internet Explorer there is not an "Always on Top" setting which will allow me to do this.
Power Menu is one of several solutions the web offers without source code :(, but has a number of extraneous options which I don't need. And of course I thought "I could code that myself ...".
There were a number of coding issues raised by this project.
Although some of these have been dealt with elsewhere on The Code Project, this article should hopefully act as a useful example of their practical application.
The default behaviour of the CDialog class makes it difficult to
hide an MFC dialog-based application. The following steps are necessary to
ensure that a dialog-based application is hidden.
In the Visual Studio resource editor, set the Visible property of the dialog to false
Remove references to DoModal in the application's
InitInstance()
BOOL CAOTApp::InitInstance()
{
CWinApp::InitInstance();
m_pMainWnd = new CAOTDlg;
return TRUE;
}
When the new CAOTDlg is constructed, create the dialog box manually.
CAOTDlg::CAOTDlg(CWnd* pParent /*=NULL*/) : CDialog(CAOTDlg::IDD, pParent) { m_hIcon = AfxGetApp()-> LoadIcon(IDR_MAINFRAME); Create (IDD, pParent); }
When we are finished with it, the dialog needs to destroy itself. The last
possible time we can do this is in PostNcDestroy().
void CAOTDlg::PostNcDestroy() { delete this; // delete hidden dialog }
In some cases (and this is one of them) PostNcDestroy does not
get called automatically, so we have to call it ourselves.
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's CSystemTray class makes it very easy to add a system tray icon.
m_TrayIcon.Create (this, WM_ICON_NOTIFY, "Always on Top", m_hIcon, IDR_TRAYMENU);
To implement a right click menu (with a default item), we only need create a
menu in the resource editor with the same ID as the tray icon ie
IDR_TRAYMENU. All messages are then passed to the CSystemTray class
to deal with.
LRESULT CAOTDlg::OnTrayNotification(WPARAM wParam, LPARAM lParam)
{
// Delegate all the work back to the default
// implementation in CSystemTray.
return m_TrayIcon.OnTrayNotification(wParam, lParam);
}
All system wide hooks have to run from a dll so that they can be accessed from any other process. The basic points of creating a DLL are as follows.
To ensure procedure names are exported correctly, and not mangled by the linker, they should be prefaced with extern "C".
extern "C" { static LRESULT CALLBACK ShellProc(int nCode, WPARAM wParam, LPARAM lParam); static LRESULT CALLBACK MenuProc(int nCode, WPARAM wParam, LPARAM lParam); }
Any data which needs to be accessed across different processes which use the dll (eg the handle to the main window) needs to be held in a shared data segment, and the appropriate linker directive given.
#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")
The entry point for a dll is DllMain rather than
WinMain. If the linker gives an error that _dllMain is
already defined, _USRDLL should be removed from the linker
switches.
The new menu item is appended by hooking WH_CALLWNDPROC and
handling WM_INITMENUPOPUP. If the high order word of
lParam is TRUE, the menu called is the system menu and
we can add our menu item.
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");
}
}
}
Originally I did not bother removing the item from the menu when it was dismissed, but that obviously kept the menu item in existence even after the main dialog had been closed and the hook removed.
To remove the new menu item, the WM_MENUSELECT message is
handled. When a menu is dismissed, lParam will be NULL
and the high order word of wParam will be 0xFFFF. If
it exists, the menu item with ID AOT_AOT is removed.
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);
}
}
The menu item itself is handled by hooking WH_GETMESSAGE and
handling WM_SYSCOMMAND where the low order word of
wParam is our custom menu message, 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);
}
}
There are two bugs which sometimes occur:
WM_INITPOPMENU is called, the
lParam is incorrect in these cases.Any thoughts gratefully received!
General
News
Question
Answer
Joke
Rant
Admin
Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads.
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 26 Nov 2003 Editor: Nishant Sivakumar |
Copyright 2003 by Rob Langston Everything else Copyright © CodeProject, 1999-2010 Web22 | Advertise on the Code Project |