I am really confused. :(
Here is a new property sheet:
#include "stdafx.h"
#include "resource.h"
#include "VisitsRotaMFCPropertySheet.h"
CVisitsRotaMFCPropertySheet::CVisitsRotaMFCPropertySheet()
:CResizingMFCPropertySheet(_T("VisitsRota"), AFX_IDS_APP_TITLE, nullptr, 0)
{
ConstructSheet();
}
CVisitsRotaMFCPropertySheet::~CVisitsRotaMFCPropertySheet()
{
}
BOOL CVisitsRotaMFCPropertySheet::OnInitDialog()
{
BOOL bResult = CResizingMFCPropertySheet::OnInitDialog();
m_Menu.LoadMenu(IDR_MENU);
SetMenu(&m_Menu);
return bResult;
}
void CVisitsRotaMFCPropertySheet::ConstructSheet()
{
m_psh.dwFlags |= PSH_NOAPPLYNOW;
AddPage(&m_ElderlyInfirmPage);
AddPage(&m_ShepherdingPage);
}
It is derived from CResizingMFCPropertySheet. This is the source for that class:
https://www.dropbox.com/s/fzpfo4c3dpt6l51/ResizingMFCPropertySheet.cpp?dl=0
Now, I have two pages in this window. Here is one for the definitions:
IDD_PAGE_ELDERLY_INFIRM DIALOGEX 0, 0, 420, 202
STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_CAPTION
CAPTION "Elderly && Infirm"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
GROUPBOX "Elders ...",IDC_STATIC,6,7,132,188
LISTBOX IDC_LIST_BOOKSTUDY,12,18,120,147,LBS_SORT | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP
PUSHBUTTON "Add",IDC_BUTTON_ADD_GROUP,12,172,35,18
PUSHBUTTON "Edit",IDC_BUTTON_EDIT_ELDER,55,172,35,18
PUSHBUTTON "Delete",IDC_BUTTON_DELETE_GROUP,97,172,35,18
GROUPBOX "Publishers ...",IDC_STATIC,144,7,132,188
LISTBOX IDC_LIST_ELDERY_INFIRM,150,18,120,147,LBS_SORT | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP
PUSHBUTTON "Add",IDC_BUTTON_ADD_ELDERLY,150,172,35,18
PUSHBUTTON "Edit",IDC_BUTTON_EDIT_ELDERLY,193,172,35,18
PUSHBUTTON "Delete",IDC_BUTTON_DELETE_ELDERLY,235,172,35,18
GROUPBOX "Report Settings ...",IDC_STATIC,281,7,132,188
LTEXT "Starting month:",IDC_STATIC,286,18,120,8
COMBOBOX IDC_COMBO_MONTH,286,31,120,12,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
LTEXT "Number of months:",IDC_STATIC,286,49,78,12
COMBOBOX IDC_COMBO_NUM_MONTHS,376,49,30,96,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
LTEXT "Number of publishers to visit each month:",IDC_STATIC_NUM_PUB,286,65,84,18
COMBOBOX IDC_COMBO_PUB_PER_MONTH,376,66,30,12,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP
LTEXT "Starting publisher:",IDC_STATIC,286,90,120,8
COMBOBOX IDC_COMBO_PUBLISHER,286,103,120,12,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP
END
It is correctly set up as a page and I have initially set the control data via the IDE:
IDD_PAGE_ELDERLY_INFIRM AFX_DIALOG_LAYOUT
BEGIN
0,
0, 0, 0, 100,
0, 0, 0, 100,
0, 100, 0, 0,
0, 100, 0, 0,
0, 100, 0, 0,
0, 0, 0, 100,
0, 0, 0, 100,
0, 100, 0, 0,
0, 100, 0, 0,
0, 100, 0, 0,
100, 0, 0, 100,
100, 0, 0, 0,
100, 0, 0, 0,
100, 0, 0, 0,
100, 0, 0, 0,
100, 0, 0, 0,
100, 0, 0, 0,
100, 0, 0, 0,
100, 0, 0, 0
END
I have adjusted my CDialog application to invoke the property sheet instead. The sheet itself sizes:
Why is the sheet control not automatically resizing? I just don't get it. My other application uses the same base class and yet all those property pages correctly resizing the controls etc using the dynamic layout features.
Update
I added this to one of my pages:
void CElderlyInfirmPage::OnSize(UINT nType, int cx, int cy)
{
CMFCPropertyPage::OnSize(nType, cx, cy);
AfxMessageBox(_T("Size"));
// TODO: Add your message handler code here
auto pManager = GetDynamicLayout();
if (pManager != nullptr)
{
AfxMessageBox(_T("Valid"));
}
}
It confirms that the "page" does not actually have a dynamic layout manager. Only the sheet does. So I think the problem is the fact that we can't use dynamic layout mechanism.
Update 2
I made some progress. Example:
It turns out that the property page doesn't seem to load the dynamic layout resources like it does for a dialog. I started to create it manually:
BOOL CElderlyInfirmPage::OnInitDialog()
{
CMFCPropertyPage::OnInitDialog();
// TODO: Add extra initialization here
ReadSettings();
InitMonthCombo();
// Init to THIS month
COleDateTime datNow = COleDateTime::GetCurrentTime();
m_cbMonth.SetCurSel(datNow.GetMonth()-1);
EnableDynamicLayout(TRUE);
auto pManager = GetDynamicLayout();
if (pManager != nullptr)
{
pManager->Create(this);
pManager->AddItem(IDC_COMBO_MONTH, CMFCDynamicLayout::MoveHorizontal(100), CMFCDynamicLayout::SizeNone());
pManager->AddItem(IDC_COMBO_NUM_MONTHS, CMFCDynamicLayout::MoveHorizontal(100), CMFCDynamicLayout::SizeNone());
pManager->AddItem(IDC_COMBO_PUB_PER_MONTH, CMFCDynamicLayout::MoveHorizontal(100), CMFCDynamicLayout::SizeNone());
pManager->AddItem(IDC_COMBO_PUBLISHER, CMFCDynamicLayout::MoveHorizontal(100), CMFCDynamicLayout::SizeNone());
}
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
As you can see, the controls move now so it is progress. But the problem now is that I have a lot of IDC_STATIC controls on these pages and I don't want to change the ID numbers. This is because the application already has translations for localization and if I change the ID values I blow up the translations. So I am wondering if I can use the [CMFCDynamicLayout::LoadResource][3] method to load the complete settings from the RC file. But I can't work out how to call LoadResource here. I am sure it would be the answer to this question.
Update 3
I just traced the code and if you look here:
LRESULT CPropertySheet::HandleInitDialog(WPARAM, LPARAM)
{
LRESULT lResult = OnInitDialog();
CMFCDynamicLayout* pDynamicLayout = GetDynamicLayout();
if (pDynamicLayout != NULL)
{
CRect rectWindow;
GetWindowRect(rectWindow);
m_sizeMin = rectWindow.Size();
for (CWnd *pChild = GetWindow(GW_CHILD); pChild->GetSafeHwnd() != NULL; pChild = pChild->GetWindow(GW_HWNDNEXT))
{
HWND hwndChild = pChild->GetSafeHwnd();
if (!pDynamicLayout->HasItem(hwndChild))
{
if (pChild->SendMessage(WM_GETDLGCODE) & DLGC_BUTTON)
{
pDynamicLayout->AddItem(hwndChild, CMFCDynamicLayout::MoveHorizontalAndVertical(100, 100), CMFCDynamicLayout::SizeNone());
}
else if (IsLeftNavigationPane(hwndChild))
{
pDynamicLayout->AddItem(hwndChild, CMFCDynamicLayout::MoveNone(), CMFCDynamicLayout::SizeVertical(100));
}
else if (DYNAMIC_DOWNCAST(CPropertyPage, pChild) == NULL || CanAddPageToDynamicLayout())
{
pDynamicLayout->AddItem(hwndChild, CMFCDynamicLayout::MoveNone(), CMFCDynamicLayout::SizeHorizontalAndVertical(100, 100));
}
}
}
}
return lResult;
}
It does not seem to actually work with the layout properly.
I tried to use:
LoadDynamicLayoutResource(m_lpszTemplateName);
And I traced it. It eventually ended up here:
BOOL CMFCDynamicLayout::LoadResource(CWnd* pHostWnd, LPVOID lpResource, DWORD dwSize)
{
if (pHostWnd->GetSafeHwnd() == NULL || !::IsWindow(pHostWnd->GetSafeHwnd()) || lpResource == NULL)
{
return FALSE;
}
CMFCDynamicLayoutData layoutData;
BOOL bResult = layoutData.ReadResource(lpResource, (UINT)dwSize);
layoutData.ApplyLayoutDataTo(pHostWnd, FALSE);
return bResult;
}
It failed on the ApplyLayoutDataTo call, on the first if statement:
BOOL CMFCDynamicLayoutData::ApplyLayoutDataTo(CWnd* pHostWnd, BOOL bUpdate)
{
if (pHostWnd->GetSafeHwnd() == NULL || m_listCtrls.IsEmpty())
{
return FALSE;
}
ASSERT_VALID(pHostWnd);
pHostWnd->EnableDynamicLayout(FALSE);
pHostWnd->EnableDynamicLayout();
m_listCtrls.IsEmpty() was empty. So it hadn't read it in properly anyway.
I think I have no choice but to assign IDs to all my controls, even the static ones and manually build the dynamic layout up. Unless you have other ideas.
Dynamic layout is already be enabled for all classes derived from CDialog which call the default CDialog::OnInitDialog, which in turn uses CMFCDynamicLayout::LoadResource to read resizing information for child controls.
That include CMFCPropertyPage. The information is already loaded, so if you call EnableDynamicLayout it deletes the existing object and creates a new one. Just remove the call to EnableDynamicLayout.
This way pManager->Create(this); won't be necessary, but you can keep it in there. It won't do anything because pManager already created and the method knows not to create twice.
CPropertySheet does require EnableDynamicLayout and pManager->Create. PropertySheet cannot be designed in dialog editor, so MFC ignores resizing for its child windows. Dynamic resizing has to be implemented manually.
MCVE:
class CMyPage : public CMFCPropertyPage
{
CButton bn;
BOOL OnInitDialog()
{
CMFCPropertyPage::OnInitDialog();
//add test button dynamically
bn.Create(L"Test", WS_CHILD | WS_VISIBLE, CRect(0, 0, 100, 30), this, 301);
auto pManager = GetDynamicLayout();
if(pManager != nullptr)
{
pManager->AddItem(bn.GetDlgCtrlID(),
CMFCDynamicLayout::MoveHorizontal(100),
CMFCDynamicLayout::SizeNone());
}
return TRUE;
}
};
class CMySheet :public CMFCPropertySheet
{
public:
CMyPage Page1;
CMySheet()
{
Page1.Construct(IDD_PAGE1);
AddPage(&Page1);
}
static 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);
if (message == PSCB_PRECREATE)
((LPDLGTEMPLATE)lParam)->style |= (DS_3DLOOK | DS_SETFONT
| WS_THICKFRAME | WS_SYSMENU | WS_POPUP | WS_VISIBLE | WS_CAPTION);
return nRes;
}
BOOL OnInitDialog()
{
BOOL res = CMFCPropertySheet::OnInitDialog();
EnableDynamicLayout(TRUE);//required for propertysheet
auto pManager = GetDynamicLayout();
if(pManager)
{
pManager->Create(this);
for(CWnd *child = GetWindow(GW_CHILD);
child; child = child->GetWindow(GW_HWNDNEXT))
{
if(child->SendMessage(WM_GETDLGCODE) & DLGC_BUTTON)
pManager->AddItem(*child,
CMFCDynamicLayout::MoveHorizontalAndVertical(100, 100),
CMFCDynamicLayout::SizeNone());
else
pManager->AddItem(*child,
CMFCDynamicLayout::MoveNone(),
CMFCDynamicLayout::SizeHorizontalAndVertical(100, 100));
}
}
return res;
}
INT_PTR DoModal()
{
// Hook into property sheet creation code
m_psh.dwFlags |= PSH_USECALLBACK;
m_psh.pfnCallback = XmnPropSheetCallback;
return CMFCPropertySheet::DoModal();
}
};
...
CMySheet sh;
sh.DoModal();
I am trying to create a control at run-time, but it causes assertion, and I don't know what's causing it. The control I am using is the Tree ComboBox Control from this link: https://www.codeproject.com/Articles/187762/Tree-ComboBox-Control
I added the code to register the class as follow:
CTreeComboBox::CTreeComboBox()
...
{
...
RegisterWindowClass();
}
CTreeComboBox::~CTreeComboBox()
{
m_BrushAlert.DeleteObject();
}
BOOL CTreeComboBox::RegisterWindowClass()
{
WNDCLASS wndcls;
HINSTANCE hInst = AfxGetInstanceHandle();
if (!(::GetClassInfo(hInst, _T("TreeComboBox"), &wndcls)))
{
wndcls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
wndcls.lpfnWndProc = ::DefWindowProc;
wndcls.cbClsExtra = wndcls.cbWndExtra = 0;
wndcls.hInstance = hInst;
wndcls.hIcon = NULL;
wndcls.hCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW);
wndcls.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
wndcls.lpszMenuName = NULL;
wndcls.lpszClassName = _T("TreeComboBox");
if (!AfxRegisterClass(&wndcls))
{
AfxThrowResourceException();
return FALSE;
}
}
return TRUE;
}
I tried to create the control at run-time using the following code in the test program:
BOOL CTestComboBoxDlg::OnInitDialog()
{
...
m_ComboBox2.CreateEx(WS_EX_CLIENTEDGE, _T("TreeComboBox"), _T(""), WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP,
CRect(0, 0, 100, 50), this, 100000, NULL);
return TRUE; // return TRUE unless you set the focus to a control
}
I also tried creating the control using a button click event thinking that I should let the GUI finish initializing, but the same error occur:
void CTestComboBoxDlg::OnBnClickedButton1()
{
m_ComboBox2.CreateEx(WS_EX_CLIENTEDGE, _T("TreeComboBox"), _T(""), WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP,
CRect(0, 0, 100, 50), this, 100000, NULL);
}
When I run the program, it stopped at the file dbgrptt.cpp at the following line:
__try
{
if (_CRT_ASSERT == nRptType && _InterlockedIncrement(&_crtAssertBusy) > 0)
{
/* use only 'safe' functions -- must not assert in here! */
_ERRCHECK(_itoa_s(nLine, szLineMessage, DBGRPT_MAX_MSG, 10));
OutputDebugStringA("Second Chance Assertion Failed: File ");
OutputDebugStringA(szFile ? szFile : "<file unknown>");
OutputDebugStringA(", Line ");
OutputDebugStringA(szLineMessage);
OutputDebugStringA("\n");
It stop here===> _CrtDbgBreak();
retval=-1;
__leave;
}
The program run fine if I create the control manually using the Visual Studio GUI editor, so I am not sure what's wrong. Can you help me figure out how to create this control at run-time?
Note: change the statement: TRACE1(_T("Item selected: %s\n"), GetItemText(hItem)); to TRACE(_T("Item selected: %s\n"), GetItemText(hItem)); in the file ComboTreeCtrlExt.cpp if you if you want to run the code and are using MFC
To answer my own question. Move the following code from the CTreeComboBox::PreSubclassWindow() to the CTreeComboBox::OnCreate()
CRect rect(0, 0, 0, 0);
DWORD dwStyle = WS_POPUP | WS_BORDER;
CWnd* pWnd = &m_Tree;
pWnd->CreateEx(0, WC_TREEVIEW, NULL, dwStyle, rect, GetParent(), 0, NULL);
m_Tree.Init(this);
GetClientRect(rect);
SetDroppedWidth(rect.Width());
SetDroppedHeight(m_nDroppedHeight);
dwStyle = CBS_DROPDOWNLIST & GetStyle();
ASSERT(CBS_DROPDOWNLIST == dwStyle);
In a C++ app I create a button using CreateWindowEx and later try to change its position using SetWindowPos, but the button doesn't appear where I want it.
What's interesting is that when I resize the window (with the mouse, not programatically), I can see for a split second a blank silhouette the same size of the button where the button is supposed to appear. This must be because I also call SetWindowPos in response to window resizing events. However the actual button stays at the same location. I'd post a screenshot but for some reason the silhouette never shows up in screenshots.
This is the code that changes the X position (the code that changes the Y position is almost identical):
HRESULT Control::put_Left(float left)
{
RECT windowRect;
::GetWindowRect(m_hWnd, &windowRect);
if (m_isTopLevel)
{
BOOL bResult = ::SetWindowPos(
m_hWnd,
nullptr,
static_cast<int>(DesktopDpi::GetInstance().DipsToPixelsX(left)),
windowRect.top,
0,
0,
SWP_NOSIZE | SWP_NOZORDER | SWP_NOREPOSITION
);
if (!bResult)
return HRESULT_FROM_WIN32(::GetLastError());
}
else
{
// NOTE: for a button this is the code that will execute, because a
// button is not a top-level window
HWND hWndParent = ::GetParent(m_hWnd);
if (hWndParent == nullptr)
return HRESULT_FROM_WIN32(::GetLastError());
POINT parentPos = {0, 0};
if (!::ClientToScreen(hWndParent, &parentPos))
return E_FAIL;
BOOL bResult = ::SetWindowPos(
m_hWnd,
nullptr,
static_cast<int>(DesktopDpi::GetInstance().DipsToPixelsX(left)),
windowRect.top - parentPos.y,
0,
0,
SWP_NOSIZE | SWP_NOZORDER | SWP_NOREPOSITION
);
if (!bResult)
return HRESULT_FROM_WIN32(::GetLastError());
}
return S_OK;
}
Are you sure the parent of the button isn't moving it back to the old position when it handles WM_SIZE? It sure sounds like it from your description.
I'm wondering if it's possible to toggle back and forth between fullscreen mode and windowed mode in an OpenGL window(I'm writing for Windows using C++ and win32), without destroying the OpenGL context, and thus having to reload assets(Textures, VBOs, etc) in the process?
This is undesirable because it introduces a delay in switching between fullscreen and windowed mode, potentially a long one, as well as making it easier to screw things up by forgetting to reinitialize something.
As a followup to that, are there certain visual effects that are broken by managing to do this?
I've done a fair bit of searching and reading for the past few days, and despite a lot of flaming of SDL and other frameworks for having the same problem(I'm not using them anyway, but...), the best I've managed to find is a possible lead on opening a 1x1 window in the background to retain the context while a secondary window is destroyed or created at whim. And that's seeming unreliable from the comments I found regarding it, and seems very kludgey regardless.
Is there a proper way to do this, or is the proper way the often-given-as-an-example method of destroying your window, and recreating it, including destroying your OpenGL context and recreating it?
Basically it's just resizing the window and specifying flags that the border is invisible.
SetWindowLongPtr(hWnd, GWL_STYLE,
WS_SYSMENU | WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE);
MoveWindow(hWnd, 0, 0, width, height, TRUE);
to set it back:
RECT rect;
rect.left = 0;
rect.top = 0;
rect.right = width;
rect.bottom = height;
SetWindowLongPtr(hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW | WS_VISIBLE);
AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE);
MoveWindow(hWnd, 0, 0, rect.right-rect.left, rect.bottom-rect.top, TRUE);
or for a not-resizable window:
SetWindowLongPtr(hWnd, GWL_STYLE, WS_CAPTION | WS_POPUPWINDOW | WS_VISIBLE);
AdjustWindowRect(&rect, WS_CAPTION | WS_POPUPWINDOW, FALSE);
MoveWindow(hWnd, 0, 0, rect.right-rect.left, rect.bottom-rect.top, TRUE);
and then just resize your OpenGL viewport settings.
If you want to set the display mode too, use this:
// change display mode if destination mode is fullscreen
if (fullscreen) {
DEVMODE dm;
dm.dmSize = sizeof(DEVMODE);
dm.dmPelsWidth = width;
dm.dmPelsHeight = height;
dm.dmBitsPerPel = bitsPerPixel;
dm.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL;
success = ChangeDisplaySettings(&dm, 0) == DISP_CHANGE_SUCCESSFUL;
}
// reset display mode if destination mode is windowed
if (!fullscreen)
success = ChangeDisplaySettings(0, 0) == DISP_CHANGE_SUCCESSFUL;
Here's the code I use, which uses SetWindowPos() rather than MoveWindow(), as discussed in the comments of the other answer.
void enter_fullscreen(application* App)
{
POINT Point = {0};
HMONITOR Monitor = MonitorFromPoint(Point, MONITOR_DEFAULTTONEAREST);
MONITORINFO MonitorInfo = { sizeof(MonitorInfo) };
if (GetMonitorInfo(Monitor, &MonitorInfo)) {
DWORD Style = WS_POPUP | WS_VISIBLE;
SetWindowLongPtr(App->Window, GWL_STYLE, Style);
SetWindowPos(App->Window, 0, MonitorInfo.rcMonitor.left, MonitorInfo.rcMonitor.top,
MonitorInfo.rcMonitor.right - MonitorInfo.rcMonitor.left, MonitorInfo.rcMonitor.bottom - MonitorInfo.rcMonitor.top,
SWP_FRAMECHANGED | SWP_SHOWWINDOW);
}
App->IsFullscreen = true;
}
void exit_fullscreen(application* App)
{
bool WasMaximized = App->IsMaximized;
DWORD Style = WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN;
if (WasMaximized) {
Style = Style | WS_MAXIMIZE;
}
ivec2 WindowPosition = WasMaximized ? App->WindowPosition : App->NormalWindowPosition;
ivec2 WindowSize = WasMaximized ? App->WindowSize : App->NormalWindowSize;
SetWindowLongPtr(App->Window, GWL_STYLE, Style);
SetWindowPos(App->Window, 0,
WindowPosition.X, WindowPosition.Y, WindowSize.X, WindowSize.Y,
SWP_FRAMECHANGED | SWP_SHOWWINDOW);
App->IsFullscreen = false;
}
I call it on F11, but also on WM_ACTIVATE. Otherwise the window would sometimes keep rendering on top on Windows 7, even if another application would receive all messages, including mouse and keyboard.