Click here to Skip to main content
15,883,917 members
Articles / Desktop Programming / MFC
Article

A drop in class for saving and restoring window position

Rate me:
Please Sign up or sign in to vote.
3.50/5 (6 votes)
12 Mar 20034 min read 60.1K   1K   33   7
This article describes how to save and restore the size and position of your dialog-based application.

Introduction

Well-designed and well-behaved applications remember where they were on the screen the last time they were executed.  I've implemented this behavior several times and in at least three different ways.  I decided it was time to formalize this functionality and present it for others to use.

The result is a single header file - WindowPosition.h which can be cut from the body of the article below.

Implementation

CWindowPosition is a Singleton class for saving and restoring the position of a window to a well-known location in the system registry. It can be used as a drop-in module and requires only a few calls in the correct location to be effective. Unfortunately it cannot be done with a single line in the source since this would require depending on destructors firing at the right time, and for most applications, by the time the destructor is called for this class, there is no window left to query for its position information. So, to use this class, instantiate it somewhere convenient while starting your application.

(If you prefer to have a more human readable form of the window position in the registry, use the cs_RegFormat variable at the top of the header file and the WriteProfileString and GetProfileString code below it in the same header file.  This shows the two registry methods I have used in the past.)

Here are the steps necessary for a complete implementation.

Set a Registry Key

First, make sure that you have set a registry key for your application. This is typically done in the InitInstance() method of the application. Otherwise the registry settings will be created in your Windows folder, typically C:\Windows\appname.INI where appname is the name you gave your application, in this case it is MyDialog.

BOOL CMyDialog ::InitInstance()
{
    // Standard initialization
        
    .
    . Other initialization code here
    .
        
    // Start WindowPlacement code block
    {
        // Set the registry key.  Otherwise application settings will be <BR>        // placed
        // in an INI file in the Windows folder.
        SetRegistryKey("WindowPositionApp");
    }
    // End WindowPlacement code block
    
    // For a dialog application the creation of the dialog is here
    CMyDialog dlg;
    dlg.DoModal();
        
    .
    . Other code here
    .
    
    // Since the dialog has been closed, return FALSE so that we exit the
    //  application, rather than start the application's message pump.
    return FALSE;
}

Create the CWindowPosition object

Next, create the window position object.  It has to live as long as your application, so make it a member of your dialog class.  I made it private as a matter of style.  Do as you like.

// Include the window position header file
#include "WindowPosition.h"

class CMyDialog : public CDialog
{
private:
    // Create the window position object and make it private
    CWindowPosition m_WindowPosition;

    // Construction
public:
    CMyDialog(CWnd* pParent = NULL);    // standard constructor
    
    .
    . Other code here
    .
};

Call the constructor

As a matter of form I use the constructor initializer list form for constructing members of my classes.  Pass NULL or 'this' as the argument to the constructor.  The argument is a pointer to the window that is to be saved and restored.  If you pass 'this' you will get a warning 'warning C4355: 'this' : used in base member initializer list'.  Microsoft's documentation had this to say, though I had no problems using it this way.

Compiler Warning (levels 1 and 4) C4355

'this' : used in base member initializer list
 
The this pointer is only valid within nonstatic member functions, but was used in the initializer list for a base class.

This is a level-1 warning when Microsoft extensions are enabled (/Ze) and a level-4 warning otherwise.
// turn off 'warning C4355: 'this' : used in base member initializer list'
#pragma warning ( disable:4355 )

// Constructor
CMyDialog::CMyDialog(CWnd* pParent /*=NULL*/)
    : CDialog(CMyDialog::IDD, pParent),
    m_WindowPosition(NULL)
{    
    .
    . Other constructor code here
    .
}

Add the Restore position code block

Add code similar to that shown next to restore the window's position, if it has been saved previously.  If it has not been saved, then the call to SetWindowPlacement does nothing.

BOOL CMyDialog::OnInitDialog()
{
    CDialog::OnInitDialog();
    
    .
    . Other code here
    .
    
    // Start WindowPlacement code block
    {
        // Be sure to set the registry key in the main application module.  
        // Otherwise application settings will be placed in an INI file in <BR>        // the Windows folder.

        // Set the pointer to the window whose position is to be saved/<BR>        // restored. You can also initialize it in the constructor if you <BR>        // wish.  Microsoft issues a 'warning C4355: 'this' : used in base <BR>        // member initializer list' if you do so, but it works nontheless.  <BR>        // We'll set it explicitly here.
        m_WindowPosition.SetPositionWnd(this);
        // Restore the window position, if previously saved.
        m_WindowPosition.SetWindowPlacement();
    }
    // End WindowPlacement code block
    
    // TODO: Add extra initialization here

    return TRUE;  // return TRUE  unless you set the focus to a control
}

Add the Save position code block

Now add an override for DestroyWindow and add the single line of code shown

// Override DestroyWindow() for a dialog application.
BOOL CMyDialog::DestroyWindow() 
{
    m_WindowPosition.SaveWindowPlacement();
    return CDialog::DestroyWindow();
}

Wrapping it up

I always appreciate it when applications remember where I put them.  The functionality is not difficult, and probably should have been included by default in the application framework.  Like so many other things in MFC.  Sigh!  I hope this helps others make me and others like me happier with their applications.

And of course, the code

You can copy the code from here or use the embedded links.  Call it whatever you like - I used WindowPosition.h since that is what the class defined in it is called.

// WindowPosition.h
// 
// Copyright(C) 1999-2003 toShay Consulting
// All rights reserved.
// 
// toShay Consulting grants you ("Licensee") a non-exclusive, royalty free, 
// licence to use, modify and redistribute this software in source and binary 
// code form, provided that i) this copyright notice and licence appear on <BR>// all copies of the software; and ii) Licensee does not utilize the software <BR>// in a manner which is disparaging to toShay Consulting.
//
// This software is provided "as is" without a warranty of any kind. All 
// express or implied conditions, representations and warranties, including
// any implied warranty of merchantability, fitness for a particular purpose
// or non-infringement, are hereby excluded. toShay Consulting and its <BR>// licensors shall not be liable for any damages suffered by licensee as a <BR>// result of using, modifying or distributing the software or its derivatives.<BR>// In no event will toShay Consulting be liable for any lost revenue, profit <BR>// or data, or for direct, indirect, special, consequential, incidental or <BR>// punitive damages, however caused and regardless of the theory of liability,<BR>// arising out of the use of or inability to use software, even if toShay <BR>// Consulting has been advised of the possibility of such damages.
//
// This software is not designed or intended for use in on-line control of 
// aircraft, air traffic, aircraft navigation or aircraft communications; or <BR>// in the design, construction, operation or maintenance of any nuclear 
// facility. Licensee represents and warrants that it will not use or 
// redistribute the Software for such purposes. 
//
////////////////////////////////////////////////////////////////////////<BR>// Strings used in saving and restoring window position information
// in the Registry.
const CString cs_RegEntry = "Settings";
const CString cs_RegKey   = "WindowPlacement";
// const CString cs_RegFormat = "%ld,%ld,%ld,%d,%d,%d,%d,%d,%d,%d,%d";


// 
// CWindowPosition is a Singleton class for saving and restoring the position<BR>// of a window to a well-known location in the system registry.  It can be <BR>// used as a drop-in module and requires only a few calls in the correct <BR>// location to be effective.  Unfortunately it cannot be done with a single <BR>// line in the source since this would require depending on destructors <BR>// firing at the right time, and for most applications, by the time the <BR>// destructor is called for this class, there is no window left to query for <BR>// its position information.  So, to use this class, instantiate it somewhere<BR>// convenient while starting your application.
// 
// If you prefer to have a more human readable form of the window position in<BR>// the registry, use the cs_RegFormat variable above and the <BR>// WriteProfileString and GetProfileString code below.
// 
// Here are the steps necessary for a complete implementation in a dialog <BR>// application. First, make sure that you have set a registry key for your <BR>// application.  This is typically done in the InitInstance() method of <BR>// the application.
// 
// BOOL CWindowPositionApp::InitInstance()
// {
//     // Standard initialization
//     .
//     . Other initialization code here
//     .
//     // Start WindowPlacement code block
//     {
//         // Set the registry key.  Otherwise application settings will be <BR>//         //  placed in an INI file in the Windows folder.
//         SetRegistryKey("WindowPositionApp");
//     }
//     // End WindowPlacement code block
//     
//     // For a dialog application the creation of the dialog is here
//     CMyDialog dlg;
//     dlg.DoModal();
//     .
//     . Other code here
//     .
//     
//     // Since the dialog has been closed, return FALSE so that we exit the
//     //  application, rather than start the application's message pump.
//     return FALSE;
// }
// 
// // Include the window position header file
// #include "WindowPosition.h"
// 
// class CMyDialog : public CDialog
// {
// private:
//     // Create the window position object and make it private
//     CWindowPosition m_WindowPosition;
// 
//     // Construction
// public:
//     CMyDialog(CWnd* pParent = NULL);    // standard constructor
//     
//     .
//     . Other code here
//     .
// };
// 
// // Constructor
// CMyDialog::CMyDialog(CWnd* pParent /*=NULL*/)
//     : CDialog(CMyDialog::IDD, pParent),
//     m_WindowPosition(this)
// {
//     .
//     . Other constructor code here
//     .
// }
// 
// BOOL CMyDialog::OnInitDialog()
// {
//     CDialog::OnInitDialog();
//     .
//     .
//     .
//     // Start WindowPlacement code block
//     {
//         // Be sure to set the registry key in the main application module.  
//         // Otherwise application settings will be placed in an INI file <BR>//         //in the Windows folder.
// 
//         // Set the pointer to the window whose position is to be saved/<BR>//         // restored. You can also initialize it in the constructor if you <BR>//         // wish.  Microsoft issues a 'warning C4355: 'this' : used in base<BR>//         // member initializer list' if you do so, but it works nontheless.<BR>//         // We'll set it explicitly here.
//         m_WindowPosition.SetPositionWnd(this);
//         // Restore the window position, if previously saved.
//         m_WindowPosition.SetWindowPlacement();
//     }
//     // End WindowPlacement code block
//     
//     // TODO: Add extra initialization here
// 
//     return TRUE;  // return TRUE  unless you set the focus to a control
// }
// 
// // Override DestroyWindow() for a dialog application.
// BOOL CMyDialog::DestroyWindow() 
// {
//     m_WindowPosition.SaveWindowPlacement();
//     return CDialog::DestroyWindow();
// }
// 
// 
class CWindowPosition
{
private:
    CWnd* m_pPositionWnd;

public:
    CWindowPosition(CWnd* pPositionWnd = NULL) :
        m_pPositionWnd(pPositionWnd)
    {
    };

    virtual ~CWindowPosition()
    {
    };

    // The singleton
    static CWindowPosition&  Instance()
    { 
        static CWindowPosition theOnlyWindowPosition;
        return theOnlyWindowPosition; 
    };

    void SetPositionWnd(CWnd* pPositionWnd)
    {
        Instance().m_pPositionWnd = pPositionWnd;
    };
    
    void SaveWindowPlacement()
    {
        // Save the last window size and position
        WINDOWPLACEMENT wndpl;
        if (Instance().m_pPositionWnd && <BR>            Instance().m_pPositionWnd->GetWindowPlacement(&wndpl))
        {
            CWinApp* pApp = AfxGetApp();
            ASSERT(pApp);
            /*
            CString strWP;
            strWP.Format(cs_RegFormat, 
                wndpl.flags,
                wndpl.length,
                wndpl.showCmd,
                wndpl.ptMaxPosition.x,
                wndpl.ptMaxPosition.y,
                wndpl.ptMinPosition.x,
                wndpl.ptMinPosition.y,
                wndpl.rcNormalPosition.left, 
                wndpl.rcNormalPosition.top, 
                wndpl.rcNormalPosition.right,
                wndpl.rcNormalPosition.bottom);

            pApp->WriteProfileString(cs_RegEntry, cs_RegKey, strWP);
            */
            if (pApp)
            {
                pApp->WriteProfileBinary(cs_RegEntry, cs_RegKey, <BR>                     (BYTE*)&wndpl, sizeof(wndpl));
            }
        }
    };

    void SetWindowPlacement()
    {
        CWinApp* pApp = AfxGetApp();

        if (pApp)
        {
            /*
            WINDOWPLACEMENT wndpl;

            CString strWP;
            strWP = pApp->GetProfileString(cs_RegEntry, cs_RegKey, NULL);

            if (!strWP.IsEmpty())
            {
                sscanf(strWP, cs_RegFormat, 
                    &wndpl.flags,
                    &wndpl.length,
                    &wndpl.showCmd,
                    &wndpl.ptMaxPosition.x,
                    &wndpl.ptMaxPosition.y,
                    &wndpl.ptMinPosition.x,
                    &wndpl.ptMinPosition.y,
                    &wndpl.rcNormalPosition.left, 
                    &wndpl.rcNormalPosition.top, 
                    &wndpl.rcNormalPosition.right,
                    &wndpl.rcNormalPosition.bottom);

                Instance().m_pPositionWnd->SetWindowPlacement(&wndpl);
            }
            */
            WINDOWPLACEMENT* pwndpl;
            BYTE* pb = NULL;
            UINT nLen = 0;
            if (pApp && pApp->GetProfileBinary(cs_RegEntry, cs_RegKey, <BR>                                               &pb, &nLen))
            {
                pwndpl = reinterpret_cast<WINDOWPLACEMENT*>(pb);
                Instance().m_pPositionWnd->SetWindowPlacement(pwndpl);
            }

        }
    };
};

About Jan S. Yoder

Jan has been programming for 15 years since joining Autodesk when they were smallish and located in lovely Sausalito California.  After attending Autodesk University (as I like to call it, my equivalent of a BS/CS) for almost five years he left to pursue Pen programming, going on to help found and run two consulting companies.  He is now living in the Tampa Bay area.  He can be reached at toShay@shay.to , his nom-de-plume.

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
Tonga Tonga
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Generalfound a memory leak. Pin
Jason1981-198128-May-10 3:47
Jason1981-198128-May-10 3:47 
GeneralJust say thanx Pin
tbc19654-May-03 23:12
tbc19654-May-03 23:12 
GeneralA nice refinement. Pin
Craig Muller30-Dec-02 12:07
Craig Muller30-Dec-02 12:07 
GeneralThis might be better done with a mixin class Pin
Sven Axelsson27-Dec-02 9:04
Sven Axelsson27-Dec-02 9:04 
GeneralRe: This might be better done with a mixin class Pin
jyodak127-Dec-02 17:02
jyodak127-Dec-02 17:02 
Sven Axelsson wrote:
This approach is fine, but as the article itself states, there are issues with passing this around. Also you have to add an (unneccesary) instance variable. I prefer to do this with a mixin class

Perhaps an even better idea might be to create a subclass of CDialog that handles the saving and restoring of position by overriding InitDialog, and DestroyDialog so that it happened automatically when the dialog was created and destroyed. You would then inherit directly from a class something like CPositionDialog that handled position information directly. I'll have to think about this and perhaps post a new method when it is not so late. Good idea, and thanks for the contribution.

Jan
GeneralRe: This might be better done with a mixin class Pin
WREY28-Dec-02 13:24
WREY28-Dec-02 13:24 
GeneralRe: This might be better done with a mixin class Pin
Sven Axelsson29-Dec-02 0:03
Sven Axelsson29-Dec-02 0:03 

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.