Click here to Skip to main content
15,879,096 members
Articles / Desktop Programming / MFC
Article

Restore Your Application's Window Position At Ease

Rate me:
Please Sign up or sign in to vote.
4.79/5 (17 votes)
10 Nov 20057 min read 74K   1.4K   21   7
This is an article on how to restore your window to the saved position. All you need to do is just derive your own window class from the provided class. You can also extend the window position management function for some other correlative utilities.

Image 1

Content

Introduction

It's very useful to let your application window have the feature of restoring position and state when it is re-launched after being closed. Most commercial software have this feature.

There are two very important API functions:

BOOL GetWindowPlacement( HWND hWnd, WINDOWPLACEMENT *lpwndpl );
BOOL SetWindowPlacement( HWND hWnd, WINDOWPLACEMENT *lpwndpl );

Using these functions, you can get the current window placement information and set the window to a special placement. These two functions work fine when the window is shown and is ready to execute your commands. But they don't seem to work when you want to save at exit and restore at startup. Unfortunately, it is the latter case that we want to use the most. The problem is that people often don't know when to call these two functions.

The common class CWndPosManager and the three other MFC classes CDialogEx, CFrameWndEx and CMDIFrameWndEx are designed to solve these problems.

To gain the basic function for your application, you just need to derive your own class CYourDlg from CDialogEx in a dialog based application, or derive your own class CMainFrame from CFrameWndEx in a SDI application or from CMDIFrameWndEx in a MDI application. Usually, the basic function is enough.

Of course, these classes have some other functions, for example, to restore the window to the default placement and also to restore a window in different ways, such as always restore it to maximized state. And with the provided functions your can save more than one window placement, and easily restore your window to any one of them.

Using the code

In this section, I will explain how to add the features to your applications.

Basic function: Restore the window in the recommended way

As said earlier, to gain the basic function, you just need to derive your main window class from the corresponding class that I have provided instead of the default one.

Take the newly created SDI application for example. Firstly, replace the base class of your class CMainFrame from CFrameWnd to baseCMainFrame. Don't forget to do the same thing in the file MainFrm.cpp as well as in the file MainFrm.h. Secondly, add the following include and macro definition code in the file MainFrm.h just before the definition of the class CMainFrame like this:

#include "WndPosManager.h"

#ifndef baseCMainFrame
#define baseCMainFrame CFrameWndEx
#endif

class CMainFrame : public baseCMainFrame
{
    ...

Then copy the files WndPosManager.h and WndPosManager.cpp into your project directory and add them to your project. Now you should be able to compile and run your application.

It is now that the basic function is realized. The main window can be restored to the placement where it was closed last time in the recommended way. That's, if the last state is normal or maximized, then it will be restored to the same state; if the last state is minimized, then it will be restored to the state before it was minimized last time. It may be normal or maximized. All the restorations will keep the window's normal position and size unchanged unless you save it to a different normal placement. This point also holds true in the other restoring ways.

Extended function 1: Restore the window to the default position

You can add a toolbar button or/and a menu item with the ID for example ID_VIEW_DEFAULTLAYOUT, and then add a message map to the class CMainFrame like this:

BEGIN_MESSAGE_MAP(CMainFrame, baseCMainFrame)
    ON_COMMAND(ID_VIEW_DEFAULTLAYOUT, OnViewDefaultLayout)
    ...
END_MESSAGE_MAP()

Then in the message handler function OnViewDefaultLayout, just add the single line to the function:

void CMainFrame::OnViewDefaultLayout()
{
    // XR: Restore to the default window position
    m_wpMgr.RestoreDefWndPos(m_hWnd);
}

When you run the application, you can execute the command, and the window will be replaced to the default placement, which I have set in the center of the screen. Its size is the same as its initial normal size.

Extended function 2: Customize the restoring way

There are five different ways to restore your window. They are listed below:

WaysExplanationState Transfer*
WPR_RECOMMENDRestore in the recommended way.Nor->Nor; Max->Max; (Max, Min)->Max; (Nor, Min)->Nor
WPR_DIRECTRestore to the exact state when exited the last time.Nor->Nor; Max->Max; Min->Min
WPR_NORMALRestore to normal state.Any->Nor
WPR_MAXIMIZERestore to maximized state.Any->Max
WPR_MINIMIZERestore to maximized state.Any->Min

* 1. Nor stands for normal; Max stands for Maximized; Min stands for Minimized; Any stands for any state;

* 2. -> stands for the window is closed and re-launched. (A, B) stands for a sequence of states.

In the class CWndPosManager, I have provided two functions for you to customize the restoring way:

public:
    // XR: Get the restore option
    int  GetResOption() const;

    // XR: Set the restore option
    void SetResOption(int nResOption);

Each of the other three classes CDialogEx, CFrameWndEx and CMDIFrameWndEx has a CWndPosManager object member m_wpMgr by which you can call these two functions to display and specify the current restoring way.

Here, I will give a simple example to illustrate how to use it. Of course, you can also use it in some other ways you want.

In the last SDI project, add a series of menu items with IDs arranged from, for example ID_VIEW_RESTORERECOMMENDED to ID_VIEW_RESTOREMINIMIZED. Make sure that these IDs' real values are in increasing sequence.

Now add the following message maps to your class CMainFrame:

BEGIN_MESSAGE_MAP(CMainFrame, baseCMainFrame)
    ...
    ON_COMMAND_RANGE(ID_VIEW_RESTORERECOMMENDED, 
         ID_VIEW_RESTOREMINIMIZED, OnSetRestoreOption)    
    ON_UPDATE_COMMAND_UI_RANGE(ID_VIEW_RESTORERECOMMENDED, 
         ID_VIEW_RESTOREMINIMIZED, OnUpdateRestorerOptions)
END_MESSAGE_MAP()

And then modify these two message handlers like this:

void CMainFrame::OnSetRestoreOption(UINT nID)
{
    //XR: Set Restore Option
    m_wpMgr.SetResOption(nID - ID_VIEW_RESTORERECOMMENDED);
}

void CMainFrame::OnUpdateRestorerOptions(CCmdUI *pCmdUI)
{
    ASSERT(ID_VIEW_RESTOREDIRECT    == 
                ID_VIEW_RESTORERECOMMENDED + 1);
    ASSERT(ID_VIEW_RESTORENORMAL    == 
                ID_VIEW_RESTORERECOMMENDED + 2);
    ASSERT(ID_VIEW_RESTOREMAXIMIZED == 
                ID_VIEW_RESTORERECOMMENDED + 3);
    ASSERT(ID_VIEW_RESTOREMINIMIZED == 
                ID_VIEW_RESTORERECOMMENDED + 4);

    // XR: Update Menu items check state
    pCmdUI->SetCheck(pCmdUI->m_nID == ID_VIEW_RESTORERECOMMENDED 
        + m_wpMgr.GetResOption());
}

Now you can specify the restoring ways you want.

Extended function 3: Save/Restore your window to/from more than one placement

Strictly speaking, this is not an extended function because the basic function has used the functions that I am going to use again. They are member functions of CWndPosManager:

public:
    // XR: Save the window position information
    BOOL SaveWndPos(HWND hWnd, LPCTSTR lpszEntry, 
        LPCTSTR lpszSection = _T("Position")) const;
        
    // XR: Restore the window according to the saved 
    // window position information
    BOOL RestoreWndPos(HWND hWnd, LPCTSTR lpszEntry, 
        LPCTSTR lpszSection = _T("Position"));

Usually, there is no need for the last default parameter to change. As for the second parameter, when you specify a different lpszEntry, the placement for the window specified by the first parameter hWnd will be stored to a different position in the registry or in an INI file. The class CDialogEx specifies lpszEntry to _T("Dlg") acquiescently. And CFrameWndEx and CMDIFrameWndEx specify it to _T("MainWnd") acquiescently.

Just for an example, I will explain how to use it. Also you can use it in any way you want.

Add two toolbar buttons or/and two menu items with the IDs, for example ID_VIEW_SAVECURRENTPOSITION and ID_VIEW_RESTORELASTPOSITION. Then add these message maps to the class CMainFrame:

BEGIN_MESSAGE_MAP(CMainFrame, baseCMainFrame)
    ...
    ON_COMMAND(ID_VIEW_SAVECURRENTPOSITION, OnViewSaveCurrentPosition)
    ON_COMMAND(ID_VIEW_RESTORELASTPOSITION, OnViewRestoreLastPosition)
    ...
END_MESSAGE_MAP()

Then implement the message handlers:

void CMainFrame::OnViewSaveCurrentPosition()
{
    // XR: Save Current Position
    m_wpMgr.SaveWndPos(m_hWnd, _T("MainWnd2"));
}

void CMainFrame::OnViewRestoreLastPosition()
{
    // XR: Restore Last Position
    m_wpMgr.RestoreWndPos(m_hWnd, _T("MainWnd2"));
}

Here, we use the string _T("MainWnd2") to save the window to a different position so that we can easily restore the window using the saved information.

Points of interest

Here I want to discuss the time when to save and restore the window placement.

Since a dialog application as well as a SDI or MDI application receives a WM_DESTROY message when it exits, it's high time that we save the placement information of the window that is about to be destroyed. This is not very hard to handle.

But, when to restore the window placement seems not so easy. If you call SetWindowPlacement too early, it's possible that you will fail to restore the window placement you want. Because the window placement may be changed by the MFC architect in the process of creating the window. So the most appropriate time to restore the window placement is when the window is just being to be shown for the first time. As for a dialog, the function OnInitDialog is a good place to do this.

For a SDI or MDI Frame window, the handler void OnShowWindow(BOOL bShow, UINT nStatus) of the message WM_SHOWWINDOW is a good place to do this. But there is another problem that every time SetWindowPlacement is called, the WM_SHOWWINDOW will be received again and its handler OnShowWindow will also be called again. If there is no control, this loop can even crash the application. Since, we just want to call the SetWindowPlacement once, we can set a control variable for example m_bEnableRes which is set to TRUE at an early time, for example, when we handle the message WM_CREATE, and do the thing like this:

void baseCWindowEx::OnShowWindow(BOOL bShow, UINT nStatus)
{
    CFrameWnd::OnShowWindow(bShow, nStatus);

    // XR: Restore window position
    if (m_bEnableRes == TRUE && bShow == TRUE)
    {
        m_bEnableRes = FALSE;
        m_wpMgr.RestoreWndPos(m_hWnd, m_strEntry);
    }
}

Note that the parameter bShow will be set to TRUE when the window is being shown and to FALSE when it is to be hidden.

Another problem seems to occur in SDI applications. The window will be shown quickly for a moment when a new document is being created. If you don't change the window placement, it results in flicker. To avoid this flicker, we can set the member variable m_nCmdShow of the CWinApp object to SW_HIDE at an early time, for example, when we handle the message WM_CREATE. Thus we will not see the flicker in the process of startup and it will seem to be smoother.

History

  • 23rd September, 2005 - Article released.

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
Software Developer
China China
_____________________________
Xia Xiongjun loves this site. Smile | :)

Comments and Discussions

 
GeneralUse with SDI splitter with left and right views Pin
jon.oman7-Nov-05 16:24
jon.oman7-Nov-05 16:24 
GeneralRe: Use with SDI splitter with left and right views Pin
Xia Xiongjun9-Nov-05 5:53
Xia Xiongjun9-Nov-05 5:53 
GeneralRe: Use with SDI splitter with left and right views Pin
jon.oman9-Nov-05 8:25
jon.oman9-Nov-05 8:25 
GeneralRe: Use with SDI splitter with left and right views Pin
jon.oman9-Nov-05 8:30
jon.oman9-Nov-05 8:30 
GeneralSolution to problem Pin
jon.oman9-Nov-05 9:21
jon.oman9-Nov-05 9:21 
I found out that adding to your code did not solve the problem I was having. So, although your code works great for the main window, it is not appropriate for saving the state of a splitter bar, that separates two views. (At least I did not get it to work.)

But, I found the solution to my problem elsewhere. Since I have not tried to position a splitter bar before, I was not aware of the following:
void CMainFrame::OnSize(UINT nType, int cx, int cy) 
{
   baseCMainFrame::OnSize(nType, cx, cy);
	
   // TODO: Add your message handler code here
	
   CRect rect;
   GetWindowRect( &rect );


   if( m_bSplitterCreated )  // m_bSplitterCreated set in OnCreateClient
   {
      m_wndSplitter.SetColumnInfo(0, rect.Width()/4, 10);
      m_wndSplitter.SetColumnInfo(1, 3 * rect.Width()/4, 10);
      m_wndSplitter.RecalcLayout();
   }
}


This did what I wanted it to.

Thanks, Jon Oman
AnswerRe: Solution to problem Pin
Xia Xiongjun10-Nov-05 20:49
Xia Xiongjun10-Nov-05 20:49 
GeneralRe: Solution to problem Pin
jon.oman11-Nov-05 10:34
jon.oman11-Nov-05 10:34 

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.