How to create dynamic MFC controls and handle message maps of the controls at runtime?
It really depends on which controls do you want to create, especially if you want to know which flags should you set. In general it goes down to this:
Normally a CWnd-derived control is created using Create or CreateEx. For a CButton, for instance:
CButton button;
button.Create("Button text", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON | DT_CENTER, CRect(5, 5, 55, 19), this, nID);
where the CRect specifies the button position, this is a pointer to the parent window, and nID is the control ID.
If the control doesn't come out as expected, it's probably because some flags are missing. I suggest you draw a sample control in design mode, check out the code for that control in the RC file, and copy the flags to the Create caller.
As for the message maps, they are normally routed to the parent window. The nID value you used in Create is important here, because it will be the number that identifies the control in the message map. If you have a fixed number of controls, you can hard-code the nID numbers for your controls (starting at 10000, for instance); if not, you'll have to provide a way for the parent window to identify them. Then you just add the message map entries.
ON_BN_CLICKED(10000, OnBnClicked)
ON_CONTROL_RANGE(BN_CLICKED, 10010, 10020, OnBtnsClicked)
You can use the ON_CONTROL_RANGE message map to map a range of IDs to the same function.
Related
I'm fairly new to MFC and would like to create an SDI application that has a pane of tabs always embedded on the right of the window with a view left of it. In my app I have a calculation core, with variables that are changed in the tabs with edit boxes. I would like to initialise these variables in the calculation class and then during the initialisation of the dialogs used for tabs set the initial values in the edit boxes to those of the corresponding variable in the calculator.
Currently, I create an instance of the calculator in my document class.
I also create a CTabbedPane in the MainFrame OnCreate Method as follows:
m_TabbedPane.Create(_T(""), this, CRect(0, 0, 290, 200),
TRUE,
(UINT)-1,
WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS |
WS_CLIPCHILDREN | CBRS_RIGHT |
CBRS_FLOAT_MULTI))
m_tab = new CParametersDlg();
m_tab->Create(IDD_TAB, this);
m_TabbedPane.AddTab(m_tab);
I would the like to be able in CParameterDlg's OnInitDialog do something like:
BOOL CParameterDlg::OnInitDialog() {
CDialog::OnInitDialog()
float value = pointerToDocument->GetCalculatorVariable();
And use value to initialise an edit box. However I can't access the document from in the main frames OnCreate as it returns null (using GetActiveDocument, AfxGetApp etc).
How can I initialise the tabs then? I have thought about trying to put the Calculator in the App class instead. Or possibly trying to initialise the dialogs somewhere else which is called later when the document is properly initialised and linked? Or should I be doing things entirely differently?
I think that CMainFrame::OnCreate() is too early in the sequence of events to access the document class, it would not normally be created yet.
It would be better to wait until the docuent is created / initialised, the document class could then call a new method in CMainFrame() passing this as a parameter to create the tabs.
There is a similar question here but I'm looking for a C++ version.
I want to create a .rs file with a DialogBox that uses a string as a variable instead a " " so I can change it in the program.
For example: In
DEFPUSHBUTTON "Hello World", IDOK, 8, 24, 75, 14
the "Hello World" would become a variable name, which I can 'define' or set in the program.
Why? I'm trying to make a DialogBox, like a 'template', and make many instances around the program with different Text in but same buttons in them.
Hope my gibberish is understanded.
Unfortunately, this is not possible. The caption text for a control must be a constant string because the resource file is actually compiled separately from your application and has no knowledge of variables defined elsewhere in your program's code.
Resource files do support string tables, but even these require the strings to be constant values. The advantage is that you can modify the resource file without access to the rest of the source code, which makes things like localization possible, even by outside translators.
So you're stuck hard-coding the initial caption of your DEFPUSHBUTTON. It can be an empty string or whatever value you want; you just need a placeholder. And you can put all of the possible captions in a string table (which I would recommend), but you cannot link the two using any automated mechanism.
You need to write the code to do this yourself. You could certainly dynamically generate resources, but that's quite a bit of trouble. I think the simplest way of doing this is just to create the dialog (using your "template" resource file), retrieve the handle of the controls whose caption text you want to change, and send them a WM_SETTEXT message with the new string. In fact, the SetDlgItemText function will do exactly this for you.
Perhaps that would be easier to explain with some code. Assume that you have a dialog resource defined, including all of your controls. All of the controls you wish to modify will need to have a unique ID assigned to each of them so that you can distinguish between them at runtime. It doesn't matter what the initial values you assign in the resource file are, as you're going to be changing them right off the bat. It might look like this:
IDD_TEMPLATE DIALOG DISCARDABLE 0, 0, xx, xx
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION
CAPTION "Dialog Template"
FONT 8, "MS Sans Serif"
BEGIN
CTEXT "Placeholder text", IDC_MESSAGE, xx, xx, xx, xx
DEFPUSHBUTTON "Placeholder", IDOK, xx, xx, xx, xx
PUSHBUTTON "Cancel", IDCANCEL, xx, xx, xx, xx
END
Then, in your dialog procedure (DialogProc), handle the WM_INITDIALOG message as follows:
INT_PTR CALLBACK TemplateDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_INITDIALOG:
{
// Load the caption strings you want to use from the string table
// in the resource file, or get them from wherever you want.
// These are the "variables" you wanted to use in the question.
TCHAR* pszMessage;
LoadString(hInstance, /* instance handle for app or resource DLL */
IDS_MESSAGE, /* ID of the string resource to load */
reinterpret_cast<LPTSTR>(&pszMessage),
0);
TCHAR* pszOkBtn;
LoadString(hInstance, IDS_OKBUTTON, reinterpret_cast<LPTSTR>(&pszOkBtn), 0);
// ...etc.
// Set the caption text for each control.
SetDlgItemText(hwndDlg, /* handle to the dialog box window */
IDC_MESSAGE, /* ID of the control to modify */
pszMessage); /* variable containing text to set */
SetDlgItemText(hwndDlg, IDOK, pszOkBtn);
// ...etc.
// And, if you want to set some other properties, you can do that too.
// For example, you might set the caption of the dialog itself.
TCHAR* pszTitle;
LoadString(hInstance, IDS_DLGCAPTION, reinterpret_cast<LPTSTR>(&pszTitle), 0);
SetWindowText(hwndDlg, pszTitle);
// ...etc.
return TRUE; // set the default focus
}
// ...process other messages as necessary
}
return FALSE; // we did not process the message
}
While I think Cody Gray's answer is better, there is always the option of creating your dialog template in-memory, and modifying it as you create new instances of the dialog. I wouldn't recommend it unless you find this kind of thing fun (I'm one of those people). I certainly wouldn't recommend it for what you're asking, but it is technically an option...
DialogBoxIndirect takes a pointer to a structure that is made up of a header (DLGTEMPLATE) that describes the dialog box itself, followed by the specified number of controls (DLGITEMTEMPLATE).
Effectively you can use the above to build a new in-memory dialog template each time you want a DialogBox with different text on the button. However, that's a little extreme if all you really want to do is change some text, which as Cody says, you can do with SetDlgItemText.
If you're really curious, here's an example.
I am new to MFC & BGCControlBar.
Now I plan to use CBCGPListCtrl which is defined as:
class BCGCBPRODLLEXPORT CBCGPListCtrl : public CListCtrl
&
CBCGPListCtrl m_wndWatch;
Now in the demo code I plan to change:
int CWatchBar::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CBCGPDockingControlBar::OnCreate(lpCreateStruct) == -1)
return -1;
m_Font.CreateStockObject (DEFAULT_GUI_FONT);
CRect rectDummy;
rectDummy.SetRectEmpty ();
// Create output pane:
const DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_VSCROLL | LVS_REPORT | LVS_EDITLABELS ;
m_wndWatch.Create (dwStyle, rectDummy, this, ID_LIST_1);
m_wndWatch.SendMessage (LVM_SETEXTENDEDLISTVIEWSTYLE, 0,
LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
m_wndWatch.InsertColumn (0, _T("Variable"), LVCFMT_LEFT, 100);
m_wndWatch.InsertColumn (1, _T("Value"), LVCFMT_LEFT, 100);
m_wndWatch.InsertItem (0, _T("m_nCount"));
m_wndWatch.SetItemText (0, 1, _T("100"));
m_wndWatch.SetFont (&m_Font);
return 0;
}
What I get is a list(as in pic), I can only edit the 1st colum and the data can not be retained. How can I edit the second column text and make the data retainable?
You need a grid control rather than a CListCtrl (or derived class). Actually, it's possible to add support for edition in other columns but it's a looooooot of work and not the kind of thing I'd recommend to a newcomer.
According to this page of their web site, they have a grid control.
There are a few things you need to do in order to make a MFC CListCtrl editable. This is a very broad overview without going into too many details:
As mentioned above it is quite a bit of work and you need to derive a class from CListCtrl, since CListCtrl by itself does not allow you to explicitly edit all of the columns.
You need a routine that will calculate the row/column number of the particular cell you clicked on, given a cursor position CPoint.
In the derived CListCtrl class you also need a method to edit the selected cell, creating and making visible a CEdit control of the appropriate size.
Create a derived CEdit control, such that it sends the LVN_ENDLABELEDIT message and self-destructs upon completion.
An example Visual Studio 2010 project implementing an editable list control is downloadable from this site:
http://www.technical-recipes.com/2014/making-a-mfc-list-control-editable/
Upon running/debugging the example, you get an example dialog project implementing an editable list control as shown:
In a Win32 application I have a dialog with a list control which is defined is the dialog template:
CONTROL "",IDC_LIST_Attributes,"SysListView32",LVS_REPORT |
LVS_SINGLESEL | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,36,246,110
In the runtime I retrieve the handle to that control and perform different operations with it - remove all items, add items, etc. It works fine.
The problem is I can't programmatically mark an item as selected. I use the following code for that:
LVITEM lvItem;
lvItem.stateMask = stateMask;
lvItem.state = state;
SendMessage( windowHandle, LVM_SETITEMSTATE, indexToSelect, (LPARAM)&lvItem);
This code runs and no changes happen to the list control. when I clisk on items with a mouse they are selected allright. What am I missing?
Have you tried ListView_SetItemState Macro?
From the MSDN Link:
Items will only show as selected if
the list-view control has focus or the
LVS_SHOWSELALWAYS style is used.
Another Link that my help.
I just wonder how to do it.
I write :
CEdit m_wndEdit;
and in the button event handler (dialog app),
I write :
m_wndEdit.Create(//with params);
but I still don't see the control appear in the UI.
I actually wrote this in the button handler :
CWnd* pWnd = GetDlgItem(IDC_LIST1);
CRect rect;
pWnd->GetClientRect(&rect);
//pWnd->CalcWindowRect(rect,CWnd::adjustBorder);
wnd_Edit.Create(ES_MULTILINE | ES_NOHIDESEL | ES_READONLY,rect,this,105);
wnd_Edit.ShowWindow(SW_SHOW);
this->Invalidate();
id 105 doesn't exist. (I used it in the Create member function of CEdit). I just put it in there. isn't it supposed to be the id you want to give to the new control ? Should it already exist ?
Check with the following set of flags as the example mentioned in MSDN:
pEdit->Create(ES_MULTILINE | WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_BORDER | ES_NOHIDESEL | ES_READONLY,
rect, this, 105);
The Invalidate() is not necessary
Add the WS_VISIBLE flag to your create flags, you don't need the ShowWindow
You are creating the button on the location where IDC_LIST1 is - you probably want to do pWdn->Destroy() after the GetClientRect()
The id you pass to Create() can be anything, of course if you want to handle messages from this button later you'll need to use the correct id. In that case it's easiest to manually add an entry to resource.h.
What do you mean with 'I put this code in the button event handler' - which button? A different one from the one you're trying to create, I may hope? Does your code get called at all, does it stop when you put a breakpoint in? What's the value of wnd_Edit->m_hWnd after the call to Create()?
wnd_Edit is a member of your dialog, right, and not a a function local variable?
What is wnd_Edit exactly? If it's a local variable in that function, that is likely the problem. The CWnd destructor destroys the window associated with the CWnd. So when wnd_Edit goes out of scope, the edit box is destroyed too.
If that's not it, check the return value of Create(). Is it NULL? If it is, check the value of GetLastError().