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);
I am doing an App for laptops Called Batterlizer,
It will get all the information about the battery, So everything was going well until I wanted to get the battery percent and other things , so here is my code:
SYSTEM_POWER_STATUS BatteryPower;
if(GetSystemPowerStatus( &BatteryPower ))
{
long unsigned int BatteryFull = BatteryPower.BatteryLifeTime;
const char BatteryFullTime[900] = {BatteryFull};
BatteryLeftText = CreateWindow("static", "Battery Life:",WS_CHILD |
WS_VISIBLE | WS_TABSTOP, 0, 0, 100, 20,hwnd, (HMENU)(501),(HINSTANCE)
GetWindowLong (hwnd, GWL_HINSTANCE), NULL);
UpdatedBatteryText = CreateWindow("static",BatteryFullTime, WS_VISIBLE |
WS_CHILD | WS_TABSTOP, 90, 0, 50, 20, hwnd, (HMENU)(501),(HINSTANCE)
GetWindowLong (hwnd, GWL_HINSTANCE),NULL);
}
the problem is , it says strange letters when it comes to BatteryFullTime,
Any Ideas guys?
Try the following:
char szBatteryLifeBuffer[900] = {0};
sprintf(szBatteryLifeBuffer, "%lu", BatteryFull);
SetWindowText(UpdatedBatteryText, szBatteryLifeBuffer);
// This also should work, too.
// SendMessage(UpdatedBatteryText, WM_SETTEXT, 0, (LPARAM)(LPTSTR)szBatteryLifeBuffer);
Get rid of
// This does not work.
const char BatteryFullTime[900] = {BatteryFull};
You're using C instead of C++, that makes it harder. In C++ :
std::string BatteryFullTime = std::to_string(BatteryFull);
No need to care about length, or remember details of the conversion function. However:
SetWindowText(UpdatedBatteryText, BatteryFullTime.c_str());
The Windows function does need a C string, but it's easy to get a C string from the C++ string.
I have a straight win32 c++ app and I'm filling the window with a ListView whose view type is set to LV_VIEW_TILE and I'm also setting the style to LVS_OWNERDATA.
I'm having trouble trying to work out how to get the subitems to show. This code creates the view.
DWORD exstyle =WS_EX_CLIENTEDGE|LVS_EX_DOUBLEBUFFER|LVS_EX_JUSTIFYCOLUMNS|LVS_EX_INFOTIP;
g_hwndList = CreateWindowEx(exstyle, WC_LISTVIEW, NULL, WS_CHILD | WS_VISIBLE | LVS_ICON | LVS_OWNERDATA, 0, 0, 0, 0, hWnd, (HMENU) 2702, hInst, NULL);
ListView_SetView(g_hwndList, LV_VIEW_TILE);
LVTILEVIEWINFO tileViewInfo = { };
tileViewInfo.cbSize = sizeof(LVTILEVIEWINFO);
tileViewInfo.dwFlags = LVTVIF_AUTOSIZE;
tileViewInfo.dwMask = LVTVIM_COLUMNS;
tileViewInfo.cLines = 1;
BOOL tst = ListView_SetTileViewInfo(g_hwndList, &tileViewInfo);
I only want one more subitem/column to appear. In my LVN_GETDISPINFO I currently have this:
static int colfmt[1];
colfmt[0] = LVCFMT_LEFT;
static int order[1];
order[0] = 1;
if ((nimfo->item.mask & LVIF_COLUMNS) == LVIF_COLUMNS) {
nimfo->item.cColumns = 1;
nimfo->item.piColFmt = PINT(colfmt);
nimfo->item.puColumns = PUINT(order);
}
if ((nimfo->item.mask & LVIF_TEXT) == LVIF_TEXT) {
nimfo->item.pszText = di->LABEL;
}
if ((nimfo->item.mask & LVIF_IMAGE) == LVIF_IMAGE) {
nimfo->item.iImage = di->IMAGE_INDEX;
}
I can't work out at what point and where I need to supply the subitem/column text, I'm never seeing the nimfo->item.subitem changing from 0 and for each call for LVIF_TEXT the structure values are always the same.
So at what point do I need to supply the extra textual data?
Many thanks.
I, rather stupidly, wasn't adding any columns and therefore wasn't being asked for the other items.
added this and everything works
LVCOLUMN col = {};
col.mask = LVCF_SUBITEM;
col.iSubItem = 0;
ListView_InsertColumn(g_hwndList, 0, &col);
I was thinking i could use the LVITEM structures LPARAM to attach a pointer to my class, but i can't seem to get it to work!
Heres the main parts of my code:
Creating the listview:
hlvQuiz = CreateChild(WC_LISTVIEW, "",
WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL | LVS_ICON | LVS_AUTOARRANGE,
0, 0, 320, 240, m_hwnd, FontNormal);
Adding items:
if (vQuizes.size() > 0)
{
LVITEM lvi;
lvi.mask = LVIF_TEXT | LVIF_PARAM;
lvi.iItem = 0;
lvi.iSubItem = 0;
lvi.cchTextMax = QUIZSTRLEN;
for (unsigned int i = 0; i < vQuizes.size(); i++)
{
lvi.lParam = (LPARAM)&vQuizes[i]; // adding pointer to lparam
lvi.pszText = vQuizes[i].szName;
ListView_InsertItem(hlvQuiz, &lvi);
}
}
Then later when i go to get my class back from the LPARAM:
LVITEM lvi;
lvi.iItem = ListView_GetNextItem(hwnd, -1, LVNI_SELECTED);
lvi.iSubItem = 0;
if (ListView_GetItem(fm->hlvQuiz, &lvi) == TRUE)
{
Quiz* q = (Quiz*)lvi.lParam;
if (q != NULL) // i get stopped here because my pointer is NULL
if (Exists(q->szPath) == IS_FILE)
ShellExecute(NULL, "edit", q->szPath, NULL, NULL, SW_SHOWNORMAL);
}
Is there anything that i'm doing wrong? the listview creates fine, and the items add, but the pointer to my class which i put in the LPARAM value seems to be ignored, or changed by the time i come to dereference it
I haven't worked at this low level before, but I suspect you need to set the mask member of the LVITEM structure to LVIF_PARAM (as well as the appropriate values for anything else you need) for the call to ListView_GetItem.
Your code works fine in Debug mode but not in Release mode because
you missed to specify the name of LPARAM in lvi.mask (and the name of any other field you want back).
Try this:
lvi.iItem = ListView_GetNextItem(hlvQuiz, -1, LVNI_SELECTED);
lvi.mask = LVIF_PARAM;
if (ListView_GetItem(fm->hlvQuiz, &lvi) == TRUE) ...
You'll receive a copy of the LPARAM's value you've setted. I think that this little strange behaviour is due to the Debug mode's help that initalize everything for you. The Release mode instead do not.