visual-c++mfcpropertysheet

How do I prevent this CMFCPropertySheet from resizing too small in the height?


I have a CResizingMFCPropertySheet with several pages on it. This class is derived from CMFCPropertySheet. The class definition is fairly basic:

#include "stdafx.h"
#include "ResizingMFCPropertySheet.h"
#include "resource.h"

/////////////////////////////////////////////////////////////////////////////
// CMyPropertySheet

IMPLEMENT_DYNAMIC(CResizingMFCPropertySheet, CMFCPropertySheet)

CResizingMFCPropertySheet::CResizingMFCPropertySheet(const CString& strWindowID)
    : CMFCPropertySheet()
    , m_strWindowID(strWindowID)
{
}

CResizingMFCPropertySheet::CResizingMFCPropertySheet(const CString& strWindowID, UINT nIDCaption, CWnd* pParentWnd, UINT iSelectPage)
    : CMFCPropertySheet(nIDCaption, pParentWnd, iSelectPage)
    , m_strWindowID(strWindowID)
{
}

CResizingMFCPropertySheet::CResizingMFCPropertySheet(const CString& strWindowID, LPCTSTR pszCaption, CWnd* pParentWnd, UINT iSelectPage)
    : CMFCPropertySheet(pszCaption, pParentWnd, iSelectPage)
    , m_strWindowID(strWindowID)
{
}

CResizingMFCPropertySheet::~CResizingMFCPropertySheet()
= default;

BEGIN_MESSAGE_MAP(CResizingMFCPropertySheet, CMFCPropertySheet)
    ON_WM_SIZE()
    ON_WM_GETMINMAXINFO()
    ON_WM_DESTROY()
    ON_WM_PAINT()
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CMyPropertySheet message handlers

int CALLBACK XmnPropSheetCallback(HWND hWnd, UINT message, LPARAM lParam)
{
    extern int CALLBACK AfxPropSheetCallback(HWND, UINT message, LPARAM lParam);
    // XMN: Call MFC's callback
    int nRes = AfxPropSheetCallback(hWnd, message, lParam);

    switch (message)
    {
    case PSCB_PRECREATE:
        // Set our own window styles
        ((LPDLGTEMPLATE)lParam)->style |= (DS_3DLOOK | DS_SETFONT
            | WS_THICKFRAME | WS_SYSMENU | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_MINIMIZEBOX | WS_MAXIMIZEBOX);
        break;
    }
    return nRes;
}

BOOL CResizingMFCPropertySheet::OnInitDialog()
{
    BOOL bResult = CMFCPropertySheet::OnInitDialog();

    //GetClientRect(save_rc);
    GetClientRect(minimum_rc);

    //InitialiseResizeIcon();
    SetupDynamicLayout();
    RestoreWindowPosition(m_strWindowID, this, true);

    return bResult;
}

void CResizingMFCPropertySheet::OnSize(UINT nType, int cx, int cy)
{
    CMFCPropertySheet::OnSize(nType, cx, cy);
    /*

    if (!GetActivePage()) return;
    if (!GetTabControl()) return;

    if (nType == SIZE_MINIMIZED)
        return;

    int dx = cx - save_rc.Width();
    int dy = cy - save_rc.Height();

    int count = 0;
    for (CWnd *child = GetWindow(GW_CHILD); child; child = child->GetWindow(GW_HWNDNEXT))
        count++;

    HDWP hDWP = ::BeginDeferWindowPos(count);
    for (CWnd *child = GetWindow(GW_CHILD); child; child = child->GetWindow(GW_HWNDNEXT))
    {
        bool move = false;

        //If you add child controls manually, you want to move not resize
        if(child == &m_lblResize && m_lblResize.GetSafeHwnd() != nullptr)
            move = true;

        CRect r;
        child->GetWindowRect(&r);
        ScreenToClient(&r);

        if (move || child->SendMessage(WM_GETDLGCODE) & DLGC_BUTTON)
        {
            //move the main buttons and the child controls
            r.left += dx;
            r.top += dy;
            ::DeferWindowPos(hDWP, child->m_hWnd, 0, r.left, r.top, 0, 0,
                SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
        }
        else
        {
            if (child->GetSafeHwnd() == m_pNavigationControl->GetSafeHwnd())
            {
                r.bottom += dy;
                ::DeferWindowPos(hDWP, child->m_hWnd, nullptr,
                    r.left, r.top, r.Width(), r.Height(),
                    SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
            }
            else
            {
                //this must be a child window, resize it
                r.right += dx;
                r.bottom += dy;
                ::DeferWindowPos(hDWP, child->m_hWnd, 0, 0, 0, r.Width(), r.Height(),
                    SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
            }
        }
    }

    ::EndDeferWindowPos(hDWP);
    GetClientRect(&save_rc);
    Invalidate(TRUE);
    */
    Invalidate(TRUE);
}

INT_PTR CResizingMFCPropertySheet::DoModal()
{
    // Hook into property sheet creation code
    m_psh.dwFlags |= PSH_USECALLBACK;
    m_psh.pfnCallback = XmnPropSheetCallback;

    return CMFCPropertySheet::DoModal();
}

void CResizingMFCPropertySheet::OnGetMinMaxInfo(MINMAXINFO* lpMMI)
{
    CMFCPropertySheet::OnGetMinMaxInfo(lpMMI);
    lpMMI->ptMinTrackSize.x = minimum_rc.Width();
    lpMMI->ptMinTrackSize.y = minimum_rc.Height();
}

void CResizingMFCPropertySheet::InitialiseResizeIcon()
{
    CRect rcIcon, rcClient;

    m_bmpResize.LoadOEMBitmap(OBM_SIZE);
    m_lblResize.Create(nullptr, WS_CHILD | WS_VISIBLE | SS_BITMAP | WS_CLIPSIBLINGS,
        CRect(0, 0, 16, 16), this, IDC_STATIC_RESIZE);
    m_lblResize.SetBitmap(m_bmpResize);

    GetClientRect(rcClient);
    m_lblResize.GetClientRect(rcIcon);
    m_lblResize.SetWindowPos(&CWnd::wndBottom,
        rcClient.right - rcIcon.Width(), 
        rcClient.bottom - rcIcon.Height(), 0, 0, SWP_NOSIZE);
}

void CResizingMFCPropertySheet::RestoreWindowPosition(CString strWindow, CWnd* pWindow, bool bOverrideState)
{
    int     max_x, max_y;
    RECT    rtWindow;

    if (pWindow == nullptr)
        return;

    // Only restore if there is a previously saved position
    if ((rtWindow.top = AfxGetApp()->GetProfileInt(strWindow, _T("Top"), -1)) != -1 &&
        (rtWindow.left = AfxGetApp()->GetProfileInt(strWindow, _T("Left"), -1)) != -1 &&
        (rtWindow.bottom = AfxGetApp()->GetProfileInt(strWindow, _T("Bottom"), -1)) != -1 &&
        (rtWindow.right = AfxGetApp()->GetProfileInt(strWindow, _T("Right"), -1)))
    {
        max_x = rtWindow.right - rtWindow.left;
        max_y = rtWindow.bottom - rtWindow.top;

        // Get a handle to the monitor
        HMONITOR hMonitor = ::MonitorFromPoint(
            CPoint(rtWindow.left, rtWindow.top), MONITOR_DEFAULTTONEAREST);

        // Get the monitor info
        MONITORINFO monInfo;

        monInfo.cbSize = sizeof(MONITORINFO);
        if (::GetMonitorInfo(hMonitor, &monInfo) == 0)
            AfxMessageBox(_T("GetMonitorInfo failed"));
        else
        {
            // Adjust for work area
            rtWindow.left += monInfo.rcWork.left - monInfo.rcMonitor.left;
            rtWindow.top += monInfo.rcWork.top - monInfo.rcMonitor.top;

            // Ensure top left point is on screen
            if (CRect(monInfo.rcWork).PtInRect(CPoint(rtWindow.left, rtWindow.top)) == FALSE)
            {
                rtWindow.left = monInfo.rcWork.left;
                rtWindow.top = monInfo.rcWork.top;
            }

            rtWindow.right = rtWindow.left + max_x;
            rtWindow.bottom = rtWindow.top + max_y;

            // Restore window size
            pWindow->MoveWindow(&rtWindow, FALSE);
        }

        if (bOverrideState)
        {
            // Let us override by restoring the window state
            int iState = AfxGetApp()->GetProfileInt(strWindow, _T("ShowCmd"), SW_SHOWNORMAL);
            pWindow->ShowWindow(iState);
        }
    }
}

void CResizingMFCPropertySheet::SaveWindowPosition(CString strWindow, CWnd* pWindow)
{
    WINDOWPLACEMENT wp;

    if (pWindow == nullptr)
        return;

    pWindow->GetWindowPlacement(&wp);

    // Commit to registry
    AfxGetApp()->WriteProfileInt(strWindow, _T("Top"), wp.rcNormalPosition.top);
    AfxGetApp()->WriteProfileInt(strWindow, _T("Left"), wp.rcNormalPosition.left);
    AfxGetApp()->WriteProfileInt(strWindow, _T("Bottom"), wp.rcNormalPosition.bottom);
    AfxGetApp()->WriteProfileInt(strWindow, _T("Right"), wp.rcNormalPosition.right);
    AfxGetApp()->WriteProfileInt(strWindow, _T("ShowCmd"), wp.showCmd);
}


void CResizingMFCPropertySheet::OnDestroy()
{
    SaveWindowPosition(m_strWindowID, this);

    CMFCPropertySheet::OnDestroy();
}


CWnd* CResizingMFCPropertySheet::InitNavigationControl()
{
    m_pNavigationControl = CMFCPropertySheet::InitNavigationControl();

    return m_pNavigationControl;
}


void CResizingMFCPropertySheet::SetupDynamicLayout()
{
    EnableDynamicLayout(TRUE);
    auto pManager = GetDynamicLayout();
    if (pManager != nullptr)
    {
        pManager->Create(this);

        // The navigation control only needs to be stretched vertically
        pManager->AddItem(m_pNavigationControl->GetSafeHwnd(),
            CMFCDynamicLayout::MoveNone(), CMFCDynamicLayout::SizeVertical(100));

        // The resize control needs to be moved 100% in both directions
        //pManager->AddItem(m_lblResize.GetSafeHwnd(),
            //CMFCDynamicLayout::MoveHorizontalAndVertical(100, 100), CMFCDynamicLayout::SizeNone());

        for (CWnd *child = GetWindow(GW_CHILD); child; child = child->GetWindow(GW_HWNDNEXT))
        {
            if (child->GetSafeHwnd() != m_lblResize.GetSafeHwnd() &&
                child->GetSafeHwnd() != m_pNavigationControl->GetSafeHwnd())
            {
                // All buttons need to be moved 100% in all directions
                if (child->SendMessage(WM_GETDLGCODE) & DLGC_BUTTON)
                {
                    pManager->AddItem(child->GetSafeHwnd(),
                        CMFCDynamicLayout::MoveHorizontalAndVertical(100, 100), CMFCDynamicLayout::SizeNone());
                }
                else // This will be the main tab control which needs to be stretched in both directions
                {
                    pManager->AddItem(child->GetSafeHwnd(),
                        CMFCDynamicLayout::MoveNone(), CMFCDynamicLayout::SizeHorizontalAndVertical(100, 100));
                }
            }
        }
    }
}



INT_PTR CResizingMFCPropertySheet::OnToolHitTest(CPoint point, TOOLINFO* pTI) const
{
    CRect rc;

    GetWindowRect(rc);
    rc.left = rc.right - ::GetSystemMetrics(SM_CXHSCROLL);
    rc.top = rc.bottom - ::GetSystemMetrics(SM_CYVSCROLL);
    if (rc.PtInRect(point))
        return HTBOTTOMRIGHT;

    return CMFCPropertySheet::OnToolHitTest(point, pTI);
}

void CResizingMFCPropertySheet::OnPaint()
{
    CPaintDC dc(this); // device context for painting
                       // TODO: Add your message handler code here
                       // Do not call CResizingMFCPropertySheet::OnPaint() for painting messages

    CRect rc;
    GetClientRect(&rc);
    rc.left = rc.right - ::GetSystemMetrics(SM_CXHSCROLL);
    rc.top = rc.bottom - ::GetSystemMetrics(SM_CYVSCROLL);
    HTHEME ht = OpenThemeData(GetSafeHwnd(), L"STATUS");
    if (ht)
    {
        DrawThemeBackground(ht, dc, SP_GRIPPER, 0, &rc, nullptr);
        CloseThemeData(ht);
    }
    else
    {
        dc.DrawFrameControl(rc, DFC_SCROLL, DFCS_SCROLLSIZEGRIP);
    }
}

For the most part it behaves as I want. But I have a problem where it lets me resize the sheet too small in height. For example, here is one of my pages in the IDE:

Page Def

All the other pages are designed with exactly the same height. Yet, when I display the sheet I can resize it like this:

Height

As you can see, it is allowing me to reduce the height too much. We have this handler:

void CResizingMFCPropertySheet::OnGetMinMaxInfo(MINMAXINFO* lpMMI)
{
    CMFCPropertySheet::OnGetMinMaxInfo(lpMMI);
    lpMMI->ptMinTrackSize.x = minimum_rc.Width();
    lpMMI->ptMinTrackSize.y = minimum_rc.Height();
}

And, minimum_rc is defined in OnInitDialog:

BOOL CResizingMFCPropertySheet::OnInitDialog()
{
    BOOL bResult = CMFCPropertySheet::OnInitDialog();

    //GetClientRect(save_rc);
    GetClientRect(minimum_rc);

    //InitialiseResizeIcon();
    SetupDynamicLayout();
    RestoreWindowPosition(m_strWindowID, this, true);

    return bResult;
}

So I am stumped here. How do I prevent this sheet from resizing too small in the height?


Solution

  • You should set the minimum extents in the passed lpMMI structure before calling the base class OnGetMinMaxInfo function.

    It is also a good idea to set the minimum_rc to "empty" in your derived class constructor (or declaration) and check that it is not empty before trying to use it's width and height, so that you only use those values after OnInitDialog has set them.

    void CResizingMFCPropertySheet::OnGetMinMaxInfo(MINMAXINFO* lpMMI)
    {
    //  CMFCPropertySheet::OnGetMinMaxInfo(lpMMI); // Will set 'default' values
        if (!minimum_rc.IsRectEmpty()) {
            lpMMI->ptMinTrackSize.x = minimum_rc.Width();
            lpMMI->ptMinTrackSize.y = minimum_rc.Height();
        }
        CMFCPropertySheet::OnGetMinMaxInfo(lpMMI); // Will set your modified values
    }
    

    Update

    It seems that there were some issues when trying to do it the aforementioned way for property sheets. In the end this is what worked.

    1. Initialise the rectangle to empty in the constructor:
        CResizingMFCPropertySheet::CResizingMFCPropertySheet(const CString& strWindowID)
            : CMFCPropertySheet()
            , m_strWindowID(strWindowID)
        {
            minimum_rc.SetRect(0, 0, 0, 0);
        }
    
    1. Inflate the size of the rectangle when you get the size in OnInitDialog:
        BOOL CResizingMFCPropertySheet::OnInitDialog()
        {
            BOOL bResult = CMFCPropertySheet::OnInitDialog();
        
            GetWindowRect(minimum_rc);
            minimum_rc.InflateRect(10, 10);
        
            //InitialiseResizeIcon();
            SetupDynamicLayout();
            RestoreWindowPosition(m_strWindowID, this, true);
        
            return bResult;
        }
    

    I picked a value of 10 pixels. It might be because of the border thickness but without the offset the window size is just a tad too small.

    1. Handle OnGetMinMaxInfo like this:
        void CResizingMFCPropertySheet::OnGetMinMaxInfo(MINMAXINFO* lpMMI)
        {
            CMFCPropertySheet::OnGetMinMaxInfo(lpMMI); // Will set your modified values
            if (!minimum_rc.IsRectEmpty())
            {
                lpMMI->ptMinTrackSize.x = minimum_rc.Width();
                lpMMI->ptMinTrackSize.y = minimum_rc.Height();
            }
        }
    

    That is it. The only improvement would be the accurate size to inflate the rectangle but apart from that this is fully operational.