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:
All the other pages are designed with exactly the same height. Yet, when I display the sheet I can resize it like this:
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?
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
}
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.
CResizingMFCPropertySheet::CResizingMFCPropertySheet(const CString& strWindowID)
: CMFCPropertySheet()
, m_strWindowID(strWindowID)
{
minimum_rc.SetRect(0, 0, 0, 0);
}
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.
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.