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.
In this section, I will explain how to add the features to your applications.
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.
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()
{
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.
There are five different ways to restore your window. They are listed below:
Ways | Explanation | State Transfer* |
WPR_RECOMMEND | Restore in the recommended way. | Nor->Nor; Max->Max; (Max, Min)->Max; (Nor, Min)->Nor |
WPR_DIRECT | Restore to the exact state when exited the last time. | Nor->Nor; Max->Max; Min->Min |
WPR_NORMAL | Restore to normal state. | Any->Nor |
WPR_MAXIMIZE | Restore to maximized state. | Any->Max |
WPR_MINIMIZE | Restore 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:
int GetResOption() const;
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)
{
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);
pCmdUI->SetCheck(pCmdUI->m_nID == ID_VIEW_RESTORERECOMMENDED
+ m_wpMgr.GetResOption());
}
Now you can specify the restoring ways you want.
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:
BOOL SaveWndPos(HWND hWnd, LPCTSTR lpszEntry,
LPCTSTR lpszSection = _T("Position")) const;
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()
{
m_wpMgr.SaveWndPos(m_hWnd, _T("MainWnd2"));
}
void CMainFrame::OnViewRestoreLastPosition()
{
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.
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);
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.
- 23rd September, 2005 - Article released.