I want to add simple Cedit to my derived GUI class from CWnd. This class is simple container and treat the same as Panel in MFC. in constructor of class I add simple CEdit instance in the class.
but the when I instantiated the panel in client dialog, the panel shows but the button does not show.why it doesn't show.
The panel code
Panel header file
#pragma once
#include "afxwin.h"
class CPanel :
public CWnd
{
public:
CPanel(void);
~CPanel(void);
virtual void PreSubclassWindow();
virtual void DoDataExchange(CDataExchange* pDX);
CEdit *txt;
DECLARE_MESSAGE_MAP()
};
panel source file
#include "stdafx.h"
#include "Panel.h"
CPanel::CPanel(void)
{
WNDCLASS wndcls;
HINSTANCE hins=AfxGetInstanceHandle();
if(!(::GetClassInfo(hins,_T("CPanelCtrl"),&wndcls))){
wndcls.style=CS_DBLCLKS|CS_HREDRAW|CS_VREDRAW;
wndcls.lpfnWndProc=::DefWindowProc;
wndcls.cbClsExtra=wndcls.cbWndExtra=0;
wndcls.hInstance=hins;
wndcls.hIcon=NULL;
wndcls.hCursor=AfxGetApp()->LoadStandardCursor(IDC_CROSS );
wndcls.hbrBackground=(HBRUSH)(COLOR_3DFACE+13);
wndcls.lpszMenuName=NULL;
wndcls.lpszClassName=_T("CPanelCtrl");
txt=new CEdit();
txt->Create(ES_PASSWORD,CRect(10,10,25,35),this,1);
if (!AfxRegisterClass(&wndcls))
{
AfxThrowResourceException();
return;
}
else{
return;
}
}
}
CPanel::~CPanel(void)
{
}
void CPanel::PreSubclassWindow()
{
// TODO: Add your specialized code here and/or call the base class
CWnd::PreSubclassWindow();
}
void CPanel::DoDataExchange(CDataExchange* pDX)
{
// TODO: Add your specialized code here and/or call the base class
CWnd::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CPanel, CWnd)
END_MESSAGE_MAP()
In dialog box in OninitDialog method I do like this
panel=new CPanel();
panel->Create(L"CPanelCtrl",L"Hello ", WS_VISIBLE , CRect(70, 70, 400, 200), this, 1);
Two hints:
first: you assign to your CEdit the same ID as to the CPanel: 1.
Second: you create CEdit inside CPanel constructor, I would try creating it inside CPanel WM_CREATE handler, because inside CPanel constructor HWND of CPanel is not yet assigned.
Your code to create the edit controls only runs once, because after the window class is registered the edit control is never be created.
You can't create a child window, when the parent window is created.
Create child windows on in the WM_CREATE handler of the parent.
If you always have an edit control, why do you use a pointer to it. Just create a simple member.
You should use WS_CHILD if the edit control is located inside the panel.
Related
I'm working on the library, that wraps some MFC classes and methods. I want the user to be able to dynamically create a CDialogEx using a template in memory. For the modal dialogs, I call CDialog::InitModalIndirect and then CDialog::DoModal. For the modeless dialogs, I call CDialog::CreateIndirect and then CWnd::Show.
The code looks something like this:
// inside my library
class MyDialog : public CDialogEx
{
public:
MyDialog(CWnd* parent) : CDialogEx()
{
parent_ = parent;
my_template_data_ = CreateSomeGenericTemplate();
// OnInitDialog should be preferably called here
}
void ShowModal()
{
InitModalIndirect(my_template_data_, parent_);
DoModal(); // but it's called here - too late
}
void ShowModeless()
{
CreateIndirect(my_template_data_, parent_);
Show(); // but it's called here - too late
}
MyButton* GetButton(int id)
{
// returns the instance of my MyButton, which is a subclassed CButton
}
private:
BOOL MyDialog::OnInitDialog() override
{
CDialogEx::OnInitDialog();
// CWnd::Create for the UI controls can only be called here
}
};
// user's code
// user creates the dialog - in the constructor it's not clear if modal or modeless
1. MyDialog user_dialog(some_parent); // here, I need the controls to be created
2. user_dialog.GetButton(42)->SetWindowText(L"new text"); // user wants to initialize his controls
// but he can't, because MyButton::Create was not called yet
3. user_dialog.ShowModal(); // and only then display the dialog
//by default, here the MFC calls OnInitDialog - too late,
//the SetText method needed to be set on line 2.
My problem is, that the dialog's controls (buttons etc.) can only be created inside the CDialog::OnInitDialog method, which is called automatically after DoModal (for modal)/Show (for modeless) methods. I need the controls to be created and properly initialized (with the CWnd::Create method) preferably inside the constructor. I thought about calling Show/DoModal directly inside the constructor but I don't yet know if it's going to be modal or modeless dialog. It there a solution to this? Many thanks in advance.
Why don't you refactor your code and put the common code in a InitUI() method and call it from both sides?
And don't init interface on the constructor. Do it in OnInitDialog for modal; and in Create, or OnCreate (which implies a ON_WM_CREATE() map entry) for modeless dialogs.
I create a new class extend CEdit to override some of the message handles.
My ultimate goal is when edit control is on focus, some of the toolbar buttons become available.
I created a bool variable in doc. then the pCmdUI->enable() set to this bool. The onfocus is overridden in new edit control class. I'm having trouble to update this bool vairbale from the onfocus message handle.
void CMFCDoc::OnUpdateTextColor(CCmdUI *pCmdUI)
{
// TODO: Add your command update UI handler code here
pCmdUI->Enable(shape_onfocus_);
}
class CMFCDoc : public COleServerDoc
{
...
bool shape_onfocus_;
}
//edit control
#include <afxwin.h>
class CEditControl :
public CEdit
{
public:
CEditControl();
~CEditControl();
DECLARE_MESSAGE_MAP()
afx_msg void OnEnSetfocus();
};
void CEditControl::OnEnSetfocus()
{
//----- I want to update shape_onfocus_ here. -----
this->SetWindowTextA(_T("Hello world"));
}
Assuming your CEditControl instance is a child of some sort of CView, you could go about it like this:
void CEditControl::OnEnSetfocus()
{
CView *view = static_cast<CView *>(GetParent());
CMFCDoc *doc = static_cast<CMFCDoc *>(view->GetDocument());
doc->shape_onfocus_ = true;
...
}
Assuming the edit-control is a child of a CView-derived class, you should better put the OnUpdateUI() handler in the view class, not the document one.
For example, if the view-class is CFormView-derived (dialog), you could simply write:
void CMyView::OnUpdateTextColor(CCmdUI *pCmdUI)
{
pCmdUI->Enable(GetFocus()==GetDlgItem(IDC_MYEDIT));
}
This piece of code works for both SDI and MDI applications.
If the view class is not CFormView-derived (the edit-box was created programmatically), the code above could be modified slightly, and instead of calling GetDlgItem() you should enumerate the view's children list (search your edit-box there).
If the only reason to override the edit-control was to capture the EN_SET/KILLFOCUS messages, sorry this wasn't worth the effort, as you could simply capture these in the view's code. Then the view's message-map would contain:
ON_EN_SETFOCUS(IDC_MYEDIT, &CMyView::OnEnSetfocusMyEdit)
ON_EN_KILLFOCUS(IDC_MYEDIT, &CMyView::OnEnKillfocusMyEdit)
and the view-class code:
void CMyView::OnEnSetfocusMyEdit()
{
// TODO: Add your control notification handler code here
}
void CMyView::OnEnKillfocusMyEdit()
{
// TODO: Add your control notification handler code here
}
These are generated by the wizard. Go to the Class View tab, select your class and then go to the Events page; in the Controls subtree you can find your control and add handlers for its events. But all this is not needed, as you can just use GetFocus()/GetDlgItem() as suggested above.
And as other members said, you can access the document class from any of its views by calling the GetDocument() function.
I tried to create a subclassed control for the first time, but I feel like I did something wrong. The control is a Button, which I placed in the designer. This is its class:
class TTTField : public CButton
{
public:
BEGIN_MSG_MAP_EX(TTTField)
MSG_WM_INITDIALOG(OnInitDialog);
END_MSG_MAP()
TTTField operator=(const CWindow& btn);
private:
const BOOL OnInitDialog(const CWindow wndFocus, const LPARAM lInitParam);
};
Nothing fancy so far.
However, I can't really achieve to receive windows messages in this control. This is bad, considering the main reason for trying to subclass a control was the fact that this should be a reusable class with reusable, custom Paint behaviour. I want to overwrite certain message handlers, while keeping those I didn't explicitely ask for to the usual CButton routine.
As you can see, I implemented a message map, but the messages are just not coming in.
This is how I tried to setup the instance of this class:
TTTField fld;
is a member variable of my main dialog class. In this class I added the following DDX_MAP:
BEGIN_DDX_MAP(TTTMainDialog)
DDX_CONTROL_HANDLE(IDC_BTN, fld)
END_DDX_MAP()
with IDC_BTN being the id of the button on the designer.
In the assignment operator overload for TTTField I have the following:
TTTField TTTField::operator=(const CWindow& btn)
{
Attach(btn);
return *this;
}
I feel like this operator overload might be the source of my problems, but I just can't manage to find a website which is properly explaining the whole topic without using code which seems outdated for like 20 years.
What am I doing wrong here? I am really lost right now.
The button class should be defined as follows:
class TTTField : public CWindowImpl<TTTField, CButton>
{
protected:
BEGIN_MSG_MAP_EX(TTTField)
MSG_WM_LBUTTONDOWN(OnLButtonDown)
END_MSG_MAP()
protected:
LRESULT OnLButtonDown(UINT, CPoint)
{
//Edit: this override is meant for testing the subclass only
//it's insufficient for handling button clicks
MessageBox(L"Testing override...");
return 0;
}
};
Override dialog box's OnInitDialog, call SubclassWindow to subclass the button:
class TTTMainDialog: public CDialogImpl<CMainDialog>
{
public:
enum { IDD = IDD_MYDIALOG };
BEGIN_MSG_MAP(TTTMainDialog)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
END_MSG_MAP()
TTTField fld;
LRESULT OnInitDialog(UINT nMessage, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
fld.SubclassWindow(GetDlgItem(IDC_BTN));
return 0;
}
};
Edit, for initialization
class TTTField : public CWindowImpl<TTTField , CButton>
{
public:
void Create(CWindow *wnd, int id)
{
SubclassWindow(wnd->GetDlgItem(id));
//add initialization here
}
...
}
Then to create the button:
//fld.SubclassWindow(GetDlgItem(IDC_BTN));
fld.Create(this, IDC_BTN); //<== use this instead
Perhaps the best example, or at least one of, of subclassing a button is right in the sources of WTL, at the top of atlctrlx.h:
template <class T, class TBase = CButton, class TWinTraits = ATL::CControlWinTraits>
class ATL_NO_VTABLE CBitmapButtonImpl : public ATL::CWindowImpl< T, TBase, TWinTraits >
{
public:
DECLARE_WND_SUPERCLASS(NULL, TBase::GetWndClassName())
...
You will also file external resources on this class: Using WTL's CBitmapButton.
That's not to mention WTL's comment on doing the controls:
// These are wrapper classes for Windows standard and common controls.
// To implement a window based on a control, use following:
// Example: Implementing a window based on a list box
//
// class CMyListBox : CWindowImpl<CMyListBox, CListBox>
// {
// public:
// BEGIN_MSG_MAP(CMyListBox)
// // put your message handler entries here
// END_MSG_MAP()
// };
More examples of simple and sophisticated custom WTL controls can be found at viksoe.dk.
A confusing thing about WTL control extension is that basic classes like CButton, CComboBox are thin wrappers over standard controls. They mostly translate methods into messages to be sent. You can often easily cast instances of such classes to HWND and back.
Standard controls themselves offer a level of customization through support of notification messages.
When you subclass a control, you are adding functionality on your side which somehow needs to interoperate with stock implementation, and control classes are no longer thin wrappers. Hence, you inherit from CWindowImpl and not CButton directly. Next challenge is to specifically subclass: you need to have original window created and after that, having a HWND handle, you modify it to route the messages through your message map. This is where you need SubclassWindow method. That is, you have the control created, you look it up its handle, e.g. with GetDlgItem and then you subclass the window using your class instance SubclassWindow call. Or, alternatively you can create the control using your new class Create method in which case CreateWindow and association with your message map will be done for you.
Some, more complicated, implementations of custom controls will also want you to reflect notification messages from parent window to the controls, so that they could handle them within the same custom control class. This will typically require that you add a line REFLECT_NOTIFICATIONS in your dialog class message map (see this related question on this).
I have created a dialog box and linked it to the menu item. In this case the menu item is Help -> Statistics. It all works. So when I run the program, click on the menu Help, then Statistics, a dialog box pops up.
I also have a static text box in the dialog box. How do you change the text of this static text box at runtime?
P.S: Though I have a dialog box up and running, I do not have the handle for the dialog box. If any of your solutions involve knowing the handle to the dialog box, please tell me how to retrieve it. Thanks.
EDIT:
class CStatisticsDlg : public CDialogEx
{
public:
CStatisticsDlg();
// Dialog Data
enum { IDD = IDD_STATISTICS };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
// Implementation
protected:
DECLARE_MESSAGE_MAP()
public:
};
CStatisticsDlg::CStatisticsDlg() : CDialogEx(CStatisticsDlg::IDD)
{
}
void CStatisticsDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CStatisticsDlg, CDialogEx)
END_MESSAGE_MAP()
In Class Wizard, create a CString member variable for the label. Note: by default, labels don't have a custom id so you have to give it one like IDC_MY_LABEL.
Somewhere before showing the dialog call m_strMyLabel.SetWindowText("blah");
If you need to do it while the dialog is open you have to call UpdateData(FALSE)
Edit: if you don't want to create a member variable you can
**corrected - typing from memory....
// Find the label
// if called from within CStatusDlg class
CWnd *label = GetDlgItem(IDC_MY_LABEL);
label->SetWindowText("blah");
// If called from elsewhere
CStatusDlg dlg..... // create the dialog
CWnd *label = dlg.GetDlgItem(IDC_MY_LABEL);
label->SetWindowText("blah");
I created a control's variable for CEdit:
class CGateDlg : public CDialog
{
...
public:
// here is my control's variable
CEdit m_edit_a;
// here I map variable to control
virtual void DoDataExchange(CDataExchange* pDX);
}
And this is how I map my variable to the control:
void CGateDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Control(pDX, IDC_EDIT_A, m_edit_a);
}
This is how it works: user types some text into the edit box. Then he presses the "Reset" button which clears the edit box. This is a piece of code responsible for clearing edit box after clicking Reset button:
void CGateDlg::OnBnClickedReset()
{
// clear edit box
m_edit_a.SetWindowTextW(L"");
}
Application starts without any errors. I type some text into EditBox and hit "Reset" button. Then I get an error which leads me to winocc.cpp, line 245 (ENSURE(this)):
void CWnd::SetWindowText(LPCTSTR lpszString)
{
ENSURE(this);
ENSURE(::IsWindow(m_hWnd) || (m_pCtrlSite != NULL));
if (m_pCtrlSite == NULL)
::SetWindowText(m_hWnd, lpszString);
else
m_pCtrlSite->SetWindowText(lpszString);
}
I think the problem is with the hWnd:
this 0x0030fa54 {CEdit hWnd=0x00000000} CWnd * const
but how to fix it ?
Everything works fine when I access my control's value using this:
CEdit *m_edit_a;
m_edit_a = reinterpret_cast<CEdit *>(GetDlgItem(IDC_EDIT_A));
m_edit_a->SetWindowTextW(L"");
What am I doing wrong ?
I can see two possibilities:
The control does not exist when the dialog starts. The first thing that CDialog::OnInitDialog will do is call DoDataExchange, so if you're creating the control later in the initialization process it's too late.
Your own OnInitDialog is not calling CDialog::OnInitDialog so DoDataExchange is not being called.
I think you should no use directly the meber of your control (in this case m_edit_a). Instead you should use a memeber variable, let's say CStrimg m_edit_data, and you should link it to the control:
DDX_Text(pDX, IDC_EDIT_A, m_edit_data); // as you did it in DDC_Cotrol
Now you can use directy the variable, but in order the control to be updated you should use the following code before using it:
UpdateData(true); // unlocks the control in a sense
m_edit_data = "this is my test";
UpdateData(false); // locks the control again (in a sense)
This is normal procedure in MFC :), hope I helped...
ohh... you should also add the control to String Table ... (let me know if you do not know)
I can not find something wrong with you. I Create a new project using VC6.0,and associate a variable to the Edit,just link you do. the exe operates normally.
class CEditTestDlg : public CDialog
{
// Construction
public:
CEditTestDlg(CWnd* pParent = NULL); // standard constructor
// Dialog Data
//{{AFX_DATA(CEditTestDlg)
enum { IDD = IDD_EDITTEST_DIALOG };
CEdit m_Edit;
//}}AFX_DATA
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CEditTestDlg)
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
//}}AFX_VIRTUAL
......
.cpp
void CEditTestDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CEditTestDlg)
DDX_Control(pDX, IDC_EDIT1, m_Edit);
//}}AFX_DATA_MAP
}
void CEditTestDlg::OnBnClickedReset()
{
// TODO: Add your control notification handler code here
m_Edit.SetWindowText("tttt");
}
so,I think it is not a code problem.You had better try again.
If your dialog starts off calling CDialog::OnInitDialog() and your DoDataExchange starts off calling CDialog::DoDataExchange but still you have null hWnd pointers and get CNotSupportedException, make sure your resource (rc) file's dialog template includes all the controls (IDC_) and such you have in DoDataExchange.
Check for overriding definitions if using a DLL that also provides resources.