65.9K
CodeProject is changing. Read more.
Home

How to Keep an MDI Window Always on Top

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.73/5 (8 votes)

Jan 19, 2009

CPOL

2 min read

viewsIcon

61075

downloadIcon

1825

Making a single MDI child window stay always on top of the other MDI child windows.

Introduction

It is not possible to set a style (like WS_EX_TOPMOST) on an MDI child window so it stays on top of other MDI child windows. Instead, we have to mark a window to be the top most, and then manually bring that window to the top when another window is activated.

Microsoft has provided a nice article called How To Keep an MDI Window Always on Top (Q108315). But the solution(s) in the article are not implemented using the MFC framework.

Using the code

The sample code included in this article is just a standard Document / View MFC application created with the AppWizard. It has been modified so the newest View gets the status of being the top most. When changing to a different View, the View with the top most status will remain in front.

Modifications have only been made to CChildFrame, and they are very simple.

Implement a global variable to remember the MDI window with the top-most status:

HWND CChildFrame::m_TopWindow = NULL; // There can only be one top-most window

Handle the WM_WINDOWPOSCHANGED mesage:

The WM_WINDOWPOSCHANGED message is sent to a window, when it changes size, position, or z-order. It is only sent to the window being activated (not to the window losing focus).

BEGIN_MESSAGE_MAP(CChildFrame, CMDIChildWnd)
 //{{AFX_MSG_MAP(CChildFrame)
 ON_WM_WINDOWPOSCHANGED()
 //}}AFX_MSG_MAP
END_MESSAGE_MAP()

void CChildFrame::OnWindowPosChanged(WINDOWPOS* lpwndpos)
{
 // This message-handler is sent to the window
 // being activated (Changing position, size or z-order)
 // Check if another window has the status of being top-most, instead of this one
 if (m_TopWindow!=NULL && m_TopWindow!=m_hWnd && ::IsWindow(m_TopWindow))
 {
  ::SetWindowPos(m_TopWindow, HWND_TOP, 0, 0, 0, 0, 
                 SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
 }
 CMDIChildWnd::OnWindowPosChanged(lpwndpos);
}

Reset the global variable when the top-most window closes:

When the top-most window is closed, we must reset the global variable so other windows will not try to bring it up front.

BEGIN_MESSAGE_MAP(CChildFrame, CMDIChildWnd)
 //{{AFX_MSG_MAP(CChildFrame)
 ON_WM_CLOSE()
 //}}AFX_MSG_MAP
END_MESSAGE_MAP()

void CChildFrame::OnClose() 
{
 // Check if the top-most window is closing
 if (m_hWnd==m_TopWindow)
  m_TopWindow = NULL;
 CMDIChildWnd::OnClose();
}

Finally, we need to mark a window as being the top-most:

In this sample application, the newly opened window will get the status of being the top-most, and other existing windows will bring it to the front when they are activated.

BOOL CChildFrame::PreCreateWindow(CREATESTRUCT& cs)
{
 // TODO: Modify the Window class or styles here by modifying
 //  the CREATESTRUCT cs
 if( !CMDIChildWnd::PreCreateWindow(cs) )
     return FALSE;
 // The newly opened window will get the status of being the top-most
 m_TopWindow = m_hWnd;
 return TRUE;
}

Point of interest

There seems to be an unofficial way to make this happen with use of of the ::SetParent() function, which is used by many Visual Basic programs. Microsoft doesn't recommend doing this, as it creates a lot of unwanted sideeffects. PRB: SetParent Does Not Change Standard Forms into Child Windows (Q253814)

Instead of changing an MDI child window to be always on top, then one could also consider to make a modeless CDialog. Modeless dialogs can move out of the MDI mainframe, so if having a dual monitor solution, then one can place the dialog on the other screen.