Home
Fractals
Tutorials
Books
My blog
My LinkedIn Profile

BOOKS i'm reading

Napoleon Hill Keys to Success: The 17 Principles of Personal Achievement, Napoleon Hill, ISBN: 978-0452272811
The 4-Hour Workweek: Escape 9-5, Live Anywhere, and Join the New Rich (Expanded and Updated), Timothy Ferriss, ISBN: 978-0307465351
The Fountainhead, Ayn Rand, ISBN: 0452273331

Control Client area minimum size (WM_GETMINMAXINFO) with MFC in C++

Olivier Langlois, IT superstar coach, Dominate LinkedIn Formula author
by Olivier Langlois

Contents





The code

This section is optional. If you are only interested in using the code and do not care about the implementation, you can feel free to skip to next section. Are you still there? Excellent! Good programmers are curious. Before describing the different tasks that the class CMinMaxFrame needs to fulfill, I am going to present you the overall code organization with a small class diagram.

WM_GETMINMAXINFO demo UML class diagram

Whenever I work with MFC, I impose myself this small design pattern where I decouple everything that is not related to MFC in a class called CSomethingLogic. That way, if I ever port the code to a different framework, it should make the port easier. That being said, the design is simple. CMinMaxFrame is derived from CFrameWnd and is using CMinMaxLogic. In order to use the code, you have to derive your Main frame class from CMinMaxFrame. CMinMaxFrame responsibilities are:

  • Keep track of the status bar size and visibility state
  • Keep track of the toolbar size, visibility state and its position (undocked, docked. If docked, on which frame side it is docked on)
  • Handle the WM_GETMINMAXINFO message and compute the frame size to have the requested client view size based on the information from the previous points.

Now that you know what the code must do, I will present the code itself. The code contains plenty of comments and should be self-explanatory but I will probably have some more insights to provide as well. First, here are the classes declarations:

/*
 * class CMinMaxLogic
 *
 * It is used with the class CMinMaxFrame. Its purpose is to isolate
 * everything that is not related to MFC to ease an eventual porting
 * to another framework (ie.: WTL).
 *
 * Note: This class assumes that the associated frame has a menu and the
 * following Window Styles:
 *
 * - WS_BORDER
 * - WS_CAPTION
 * - WS_THICKFRAME
 *
 * This condition should always be met since the MFC AppWizard
 * generated code is using WS_OVERLAPPEDWINDOW that includes all 3 styles
 * to create the frame window.
 */
class CMinMaxLogic
{
public:
    CMinMaxLogic(LONG x, LONG y);
    ~CMinMaxLogic(void);

/******************************************************************************
 *
 * Name      : setClientMin
 *
 * Purpose   : Compute the minimum frame size from the provided minimum client
 *             area size. It is called at construction and can be recalled anytime
 *             by the user.
 *
 * Parameters:
 *     x       (LONG) Minimum client horizontal size.
 *     y       (LONG) Minumum client vertical size.
 *
 * Return value : None.
 *
 ****************************************************************************/
    void setClientMin(LONG x, LONG y );

/******************************************************************************
 *
 * Name      : OnGetMinMaxInfo
 *
 * Purpose   : Set the minimum size to the minimum frame size and make
 *             adjustments based on the toolbar and status bar visibility
 *             state and their sizes.
 *
 * Parameters:
 *     lpMMI  (MINMAXINFO FAR*) MinMax info structure pointer.
 *
 * Return value : None.
 *
 ****************************************************************************/
    void OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI);

    BOOL  m_sbVisible; /* Status bar visibility      */
    LONG  m_sbHeight;  /* Status bar height          */
    BOOL  m_tbVisible; /* Toolbar visibility         */
    int   m_tbPos;     /* Toolbar position (left, right, top, bottom) */
    int   m_tbSize;    /* Toolbar size               */
private:
    LONG  m_cxMin;     /* Minimum client size        */
    LONG  m_cyMin;
    LONG  m_fyMin;     /* Minimum frame size that includes borders, the frame, the toolbar */
    LONG  m_fxMin;     /* and the status bar to have a client area of m_cxMin*m_cyMin      */
};

#define DEFAULTMINCLIENTSIZE 350

class CMinMaxFrame : public CFrameWnd
{
public:
    CMinMaxFrame( LONG minX = DEFAULTMINCLIENTSIZE,
                  LONG minY = DEFAULTMINCLIENTSIZE );

/******************************************************************************
 *
 * Name      : setClientMin
 *
 * Purpose   : Recompute the minimum frame size from the newly provided minimum
 *             client area size. It can be called anytime by the user.
 *
 * Parameters:
 *     x       (LONG) Minimum client horizontal size.
 *     y       (LONG) Minumum client vertical size.
 *
 * Return value : None.
 *
 ****************************************************************************/
    void setClientMin(LONG x, LONG y )
    {
        m_MinMaxLogic.setClientMin(x,y);
    }

/******************************************************************************
 *
 * Name      : setToolBar
 *
 * Purpose   : Register the toolbar to monitor for adjusting the minimum frame
 *             size to respect the requested the minimum client area size.
 *
 * Note      : Currently only 1 toolbar is supported but more could be
 *             supported with the help of a toolbar list.
 *
 * Parameters:
 *     pTB     (CToolBar *) Toolbar to register.
 *
 * Return value : None.
 *
 ****************************************************************************/
    void setToolBar( CToolBar *pTB )
    {
        m_pTB = pTB;
        if( pTB )
        {
            m_MinMaxLogic.m_tbPos = TBFLOAT;
        }
        else
        {
            m_MinMaxLogic.m_tbPos = TBNOTCREATED;
        }
    }

/******************************************************************************
 *
 * Name      : setStatusBar
 *
 * Purpose   : Register the status bar to monitor for adjusting the minimum
 *             frame size to respect the requested the minimum client area
 *             size.
 *
 * Parameters:
 *     pST     (CStatusBar *) Status bar to register.
 *
 * Return value : None.
 *
 ****************************************************************************/
    void setStatusBar( CStatusBar *pST )
    {
        // Compute the status bar height
        if( pST )
        {
            m_MinMaxLogic.m_sbHeight = pST->CalcFixedLayout(TRUE,TRUE).cy;
        }
        else
        {
            m_MinMaxLogic.m_sbHeight = 0;
        }
    }

// Overrides
/******************************************************************************
 *
 * Name      : RecalcLayout
 *
 * Purpose   : This function is called by the MFC framework whenever a
 *             toolbar status is changing (is attached or detached to/from
 *             the frame). It is used as a hook to maintain this class
 *             internal state concerning the toolbar position and size.
 *             It should not be called directly.
 *
 * Parameters:
 *     bNotify (BOOL) Not used.
 *
 * Return value : None.
 *
 ****************************************************************************/
    virtual void RecalcLayout(BOOL bNotify = TRUE);
protected:
    afx_msg void OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI);
    afx_msg BOOL OnBarCheck(UINT nID);
    DECLARE_MESSAGE_MAP()
private:
    CMinMaxLogic m_MinMaxLogic;
    CToolBar    *m_pTB;

    // TB Functions
    void triggerGetMinMaxInfoMsg(void);
    int getTBSize(int pos);
    int findDockSide(void);
};

The first task (Keep track of the status bar size and visibility state) is the easiest task so lets get rid of this one first. Since a status bar vertical size does not usually change, all that is needed is to store its value by calling CStatusBar::CalcFixedLayout(). This is done in CMinMaxFrame::setStatusBar():

/******************************************************************************
 *
 * Name      : setStatusBar
 *
 * Purpose   : Register the status bar to monitor for adjusting the minimum
 *             frame size to respect the requested the minimum client area
 *             size.
 *
 * Parameters:
 *     pST     (CStatusBar *) Status bar to register.
 *
 * Return value : None.
 *
 ****************************************************************************/
void setStatusBar( CStatusBar *pST )
{
    // Compute the status bar height
    if( pST )
    {
        m_MinMaxLogic.m_sbHeight = pST->CalcFixedLayout(TRUE,TRUE).cy;
    }
    else
    {
        m_MinMaxLogic.m_sbHeight = 0;
    }
}

The visibility state is acquired by handling the view menu item ID_VIEW_STATUS_BAR. This is done in CMinMaxFrame::OnBarCheck().

/*
 * CMinMaxFrame::OnBarCheck function
 *
 * Purpose   : MFC defined message handler. It is called whenever a toolbar
 *             or a status bar visibility state change. It is used to trigger
 *             a WM_GETMINMAXINFO since the minimum frame size to maintain a
 *             minimum client area size has changed.
 */
BOOL CMinMaxFrame::OnBarCheck(UINT nID)
{
    BOOL res = CFrameWnd::OnBarCheck(nID);

    // TODO: Add your command handler code here
    if( nID == ID_VIEW_STATUS_BAR )
    {
        m_MinMaxLogic.m_sbVisible = !m_MinMaxLogic.m_sbVisible;
        if( m_MinMaxLogic.m_sbVisible )
        {
            triggerGetMinMaxInfoMsg();
        }
    }
    else if( nID == ID_VIEW_TOOLBAR )
    {
        m_MinMaxLogic.m_tbVisible = !m_MinMaxLogic.m_tbVisible;
        if( m_MinMaxLogic.m_tbVisible )
        {
            triggerGetMinMaxInfoMsg();
        }
    }

    return res;
}

The same function is also used for tracking the toolbar visibility state. There is an assumption that is made here. The code assumes that at startup both bars are visible which might not always be the case. This aspect should be improved someday. In the case that one of the bar becomes visible, the frame minimum size must be recomputed and this is what triggerGetMinMaxInfoMsg() is doing:

/*
 * CMinMaxFrame::triggerGetMinMaxInfoMsg function
 */
void CMinMaxFrame::triggerGetMinMaxInfoMsg()
{
    /*
     * Trigger a WM_MINMAXINFO message by calling the function MoveWindow()
     * with the current frame size. The purpose of generating a call to the
     * WM_GETMINMAXINFO handler is to verify that the new client area size
     * still respect the minimum size.
     */
    RECT wRect;
    GetWindowRect(&wRect);
    MoveWindow(&wRect);
}

Now, the hardest part that is to keep track of the position and size of the toolbar. Even if it is not documented, the CFrameWnd virtual function RecalcLayout() is called everytime that a toolbar state is changing. CMinMaxFrame is using this knowledge to get notified when this happens:

/*
 * CMinMaxFrame::RecalcLayout function
 *
 * Purpose   : This function is called by the MFC framework whenever a
 *             toolbar status is changing (is attached or detached to/from
 *             the frame). It is used as a hook to maintain this class
 *             internal state concerning the toolbar position and size.
 *             It should not be called directly.
 */
void CMinMaxFrame::RecalcLayout(BOOL bNotify)
{
    CFrameWnd::RecalcLayout(bNotify);

    // TODO: Add your specialized code here and/or call the base class
    if( m_MinMaxLogic.m_tbPos != TBNOTCREATED )
    {
        if( !m_pTB->IsFloating() )
        {
            int newPos = findDockSide();
            if( m_MinMaxLogic.m_tbPos != newPos )
            {
                m_MinMaxLogic.m_tbPos  = newPos;
                m_MinMaxLogic.m_tbSize = getTBSize(m_MinMaxLogic.m_tbPos);

                triggerGetMinMaxInfoMsg();
            }
        }
        else
        {
            m_MinMaxLogic.m_tbPos  = TBFLOAT;
            m_MinMaxLogic.m_tbSize = 0;
        }
    }
}

/*
 * CMinMaxFrame::findDockSide function
 *
 * Note: This function is using AFXPRIV. It might not be working anymore
 *       with a future MFC version.
 */
#include "afxpriv.h"

int CMinMaxFrame::findDockSide()
{
    // dwDockBarMap
    static const DWORD dwDockBarMap[4] =
    {
        AFX_IDW_DOCKBAR_TOP,
        AFX_IDW_DOCKBAR_BOTTOM,
        AFX_IDW_DOCKBAR_LEFT,
        AFX_IDW_DOCKBAR_RIGHT
    };

    int res = TBFLOAT;

    for( int i = 0; i < 4; i++ )
    {
        CDockBar *pDock = (CDockBar *)GetControlBar(dwDockBarMap[i]);
        if( pDock != NULL )
        {
            if( pDock->FindBar(m_pTB) != -1 )
            {
                res = i;
                break;
            }
        }
    }
    return res;
}

/*
 * CMinMaxFrame::getTBSize function
 *
 * Purpose   : Returns the horizontal or the vertical toolbar size based on the
 *             toolbar position.
 */
int CMinMaxFrame::getTBSize(int pos)
{
    int res;

    CSize cbSize = m_pTB->CalcFixedLayout(FALSE,
                                       (pos==TBTOP||pos==TBBOTTOM)?TRUE:FALSE);
    if( pos == TBTOP || pos == TBBOTTOM )
    {
        res = cbSize.cy;
    }
    else
    {
        res = cbSize.cx;
    }

    return res;
}

There is a potential problem in the function CMinMaxFrame::findDockSize(). It is that the function is using a non documented function and a private class. This solution has been tested with MFC6 and MFC7 but there is no guaranty that it will continue to work with future versions of MFC. I am still looking for an "official" way to perform this task but I am not aware of another way to do it. CFrameWnd contains one CDockBar object for each side of the frame and this is with these objects that MFC knows where are located the toolbar. By the way, you might wonder how I got the knowledge to pull off this stunt. I got this information from the book MFC Internals. You could, of course, just dig into the MFC source code and find everything yourself but having a book that highlights important stuff in how MFC is working is a big timesaver. You should seriously consider having this book. It will be a blessing when you try to achieve something with MFC and there does not seem to be an obvious way to do it.



Page 1 2 3

Home :: Fractals :: Tutorials :: Books :: My blog :: My LinkedIn Profile :: Contact