Click here to Skip to main content
15,880,796 members
Articles / Desktop Programming / MFC
Article

Always on Top

Rate me:
Please Sign up or sign in to vote.
4.80/5 (13 votes)
26 Nov 20033 min read 185.5K   4.2K   59   33
A DLL which creates a system hook to trap WM_INITMENUPOPUP and append an "Always on Top" option to all system menus.

Introduction

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 ...".

Coding issues

There were a number of coding issues raised by this project.

  1. Hiding a dialog-based application
  2. Creating a system tray icon
  3. Installing a system wide hook from a dll
  4. Hooking the system menu events and appending a custom item

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.

Hiding a dialog-based 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
}

System tray icon

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);
}

Installing a system wide hook from a dll

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.

Hooking the system menu and appending a custom item

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);
    }
}

Bugs

There are two bugs which sometimes occur:

  • The "Always on Top" is not always appended to the system menu the first time it is called, although if dismissed and then recalled it is there. Winspector Spy indicates that the first time WM_INITPOPMENU is called, the lParam is incorrect in these cases.
  • Clicking an active application's icon in the taskbar does not always minimize it (until you have right clicked on its icon),

Any thoughts gratefully received!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Generalsettle method for the two bugs. Pin
easthg@tom.com24-Nov-09 2:56
easthg@tom.com24-Nov-09 2:56 
Questionx64 - Vista build? Pin
luck_man_2128-Sep-08 17:20
luck_man_2128-Sep-08 17:20 
AnswerRe: x64 - Vista build? Pin
amirghz0718-Oct-08 16:07
amirghz0718-Oct-08 16:07 
Generalsmall fix Pin
Jochen Baier12-Dec-07 2:00
Jochen Baier12-Dec-07 2:00 
GeneralIm new help!! Pin
daiwoq3-Jun-07 3:56
daiwoq3-Jun-07 3:56 
Generalnot working with .net apps Pin
Zorfael1-Dec-06 6:04
Zorfael1-Dec-06 6:04 
GeneralRe: not working with .net apps Pin
Sa6ry26-Nov-07 22:32
Sa6ry26-Nov-07 22:32 
GeneralWonderful! Pin
Grigore Herghelie17-Sep-05 3:32
Grigore Herghelie17-Sep-05 3:32 
GeneralDidn't work sometimes? Fixed it. Pin
Gernot Frisch24-May-05 3:13
Gernot Frisch24-May-05 3:13 
GeneralSimulating Click in header menu Pin
frankzappa8-Oct-04 22:09
frankzappa8-Oct-04 22:09 
QuestionDuring the Game ? Pin
Lokica5-Jun-04 17:29
Lokica5-Jun-04 17:29 
GeneralGreat utility, 2 small questions. Pin
Sims29-Jan-04 23:34
Sims29-Jan-04 23:34 
GeneralRe: Great utility, 2 small questions. Pin
Rob Langston13-Feb-04 8:42
Rob Langston13-Feb-04 8:42 
GeneralAlways On TOP Pin
Balkrishna Talele27-Nov-03 21:50
Balkrishna Talele27-Nov-03 21:50 
GeneralRe: Always On TOP Pin
David Crow25-Mar-04 4:32
David Crow25-Mar-04 4:32 
GeneralRe: Always On TOP Pin
Balkrishna Talele25-Mar-04 15:18
Balkrishna Talele25-Mar-04 15:18 
GeneralRe: Always On TOP Pin
nevedko19-Dec-04 18:18
nevedko19-Dec-04 18:18 
GeneralRe: Always On TOP Pin
Balkrishna Talele19-Dec-04 18:20
Balkrishna Talele19-Dec-04 18:20 
GeneralRe: minor correction to code Pin
Douglas R. Keesler13-May-05 14:47
Douglas R. Keesler13-May-05 14:47 
QuestionCan I do this with built-in menu for EDIT controls? Pin
snakeware24-Nov-03 10:51
snakeware24-Nov-03 10:51 
AnswerRe: Can I do this with built-in menu for EDIT controls? Pin
Rob Langston8-Dec-03 7:53
Rob Langston8-Dec-03 7:53 
GeneralRe: Can I do this with built-in menu for EDIT controls? Pin
goodboyws29-Feb-04 22:13
goodboyws29-Feb-04 22:13 
GeneralRe: Can I do this with built-in menu for EDIT controls? Pin
Rob Langston1-Mar-04 22:45
Rob Langston1-Mar-04 22:45 
GeneralRe: Can I do this with built-in menu for EDIT controls? Pin
goodboyws3-Mar-04 14:31
goodboyws3-Mar-04 14:31 
GeneralYou said it was for VC++ 6. Pin
WREY24-Nov-03 8:35
WREY24-Nov-03 8:35 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.