某些情况下,我们需要在窗口上进行新添控件的操作,但在团队软件开发中,如果直接在已有界面进行控件拖放式添加,这操作必会改变资源文件,资源文件的更改可导致整个项目的重编译,对其它参与者的干扰较大,为尽量减小这种干扰,我们尽量用动态创建控件的方式来添加特例控件,但是动态创建的控件中,除了诸如STATIC或EDIT这类基本不对其事件进行额外响应的控件外,有些需要我们对其事件响应有其它的处理,如ComboBox控件等,这就需要我们在动态创建控件的同时也必须指定好其相应的消息响应处理。
直接在主窗口上new出的控件较为简单,只需要将其响应函数写出,然后将其ID与此响应函数关联即可,按照MFC的控件响应函数约定,我们将其函数定义为afx_msg void memFun(...)的形式,为避免对MFC ClassWizzard自身创建响息响应函数的影响,我们将其放于
//{{
AFX_MSG(CLASSNAME)
//}}AFX_MSG
之后即可,然后再在
BEGIN_MESSAGE_MAP(ClassName, ParentClassName)
END_MESSAGE_MAP()
里添加上影射关联,同样为避免对MFC自身函数的干扰,我们将其放于END_MESSAGE_MAP() 之前
//{{AFX_MSG_MAP(ClassName)
//}}AFX_MSG_MAP
之后即可。形如
ON_CBN_SELCHANGE(IDC_COMBOBOX_AREASORT, OnSelChangeComboAreaSort)
至此,动态创建的控件消息响应关联便完成。
此法简单易行,但仅限于直接在主界面创建控件的情况,如果要在更深级界面关系中创建控件,如在主界面的TAB控件中添加子窗口,再在子窗口上创建控件,那么新创建的控件就属于主界面的子窗口的子窗口的子窗口,此时再用以上方法进行消息映射,你会发现建立的消息处理不再响应,这是为什么呢?层次太深,消息转发不到主界面了,这种情况如何办呢?
一、将层级关系不弄那么深,还是在主界面相应位置进行动态创建。此法优点是可以直接用上述方法来响应消息应射,缺点是需要对坐标进行相对计算,在需要处理消息响应的控件与不需要处理消息响应的控件动态创建时需要两套坐标体系,另外还需要在TAB控件点击时对控件的显示/隐藏进行额外处理。
二、将控件子类化,不再需要以上消息响应的入口添加。此法优点是不需要在界面类里再去管消息响应,只需要将子类化控件的相应方法重载即可。缺点是需要额外添加一个类,另外需要跟界面交互的地方处理不是很方便。
三、将控件的消息响应转发到一个WNDPROC函数,由它来决定如何处理,它跟我们最初所说的方法结合,就可以既在理想的父从窗口上建立控件,又可在主窗口正常响应其消息处理。以ComBox控件的动态创建为例,我们在主窗口有一个Tab控件,在Tab控件的一个子窗口创建一个ComboBox控件:
先动态创建一个ComboBox控件
然后重定义消息响应函数:
SetMessageTransfer(m_pCombAreaSort->GetSafeHwnd(), (long)ComboBoxWndProc, (long&)wprcComboSort);
SetMessageTransfer展开:
然后编写相应的消息转发代码:
如此即可将消息转发至主界面,由主界面指定的函数进行处理,此法不需要额外添加一个子类,也可以调用主界面的处理函数,于主界面数据交互更是不成问题,个人倾向使用此法。
相对而言,如对new 出的控件想添加变量值关联(如int, CString型变量), 则可于DoDataExchange内手动添加控件关联调用, 形如
DDX_Text(pDX, IDC_EDIT_PAR_NUM, m_iPARNum);
但同样要注意, 如果位于子窗口的子窗口控件, 则直接使用此语句会出错, 解决办法, 仿照CWnd::UpdateData(), 于对话框DoDataExchange函数里面自行构建一CDataExchange类, 传入new 出控件的父窗口指针即可.
直接在主窗口上new出的控件较为简单,只需要将其响应函数写出,然后将其ID与此响应函数关联即可,按照MFC的控件响应函数约定,我们将其函数定义为afx_msg void memFun(...)的形式,为避免对MFC ClassWizzard自身创建响息响应函数的影响,我们将其放于
//{{
ON_CBN_SELCHANGE(IDC_COMBOBOX_AREASORT, OnSelChangeComboAreaSort)
此法简单易行,但仅限于直接在主界面创建控件的情况,如果要在更深级界面关系中创建控件,如在主界面的TAB控件中添加子窗口,再在子窗口上创建控件,那么新创建的控件就属于主界面的子窗口的子窗口的子窗口,此时再用以上方法进行消息映射,你会发现建立的消息处理不再响应,这是为什么呢?层次太深,消息转发不到主界面了,这种情况如何办呢?
一、将层级关系不弄那么深,还是在主界面相应位置进行动态创建。此法优点是可以直接用上述方法来响应消息应射,缺点是需要对坐标进行相对计算,在需要处理消息响应的控件与不需要处理消息响应的控件动态创建时需要两套坐标体系,另外还需要在TAB控件点击时对控件的显示/隐藏进行额外处理。
二、将控件子类化,不再需要以上消息响应的入口添加。此法优点是不需要在界面类里再去管消息响应,只需要将子类化控件的相应方法重载即可。缺点是需要额外添加一个类,另外需要跟界面交互的地方处理不是很方便。
三、将控件的消息响应转发到一个WNDPROC函数,由它来决定如何处理,它跟我们最初所说的方法结合,就可以既在理想的父从窗口上建立控件,又可在主窗口正常响应其消息处理。以ComBox控件的动态创建为例,我们在主窗口有一个Tab控件,在Tab控件的一个子窗口创建一个ComboBox控件:
先动态创建一个ComboBox控件
NewControItem((void**)&m_pCombAreaSort, rc, m_TabPage[4], TYPE_COMBOBOX, IDC_COMBOBOX_AREASORT) ;
NewControlItem展开:
BOOL CExpBaseDialog ::NewControItem (void **pControl , RECT & rc ,
CWnd *pParent /* = NULL */,
UINT nType /* = TYPE_STATIC */,
UINT nCtrlID /* = 0xFFFF */,
char *strName /* = */)
{
if (*pControl )
{
AfxMessageBox ("控件已存在, 请勿复创建.");
return FALSE ;
}
if (rc .right - rc .left <= 0 || rc .bottom - rc .top <= 0)
{
AfxMessageBox ("控件范围错误, 请正确给出控件的RECT 值.");
return FALSE ;
}
if (!pParent )
{
AfxMessageBox ("请给出父窗口指针");
return FALSE ;
}
switch (nType )
{
case TYPE_STATIC :
*pControl = (void *)new CStatic ();
((CStatic *)*pControl )->Create (strName , WS_CHILD |WS_VISIBLE , rc , pParent );
break ;
case TYPE_BUTTON :
*pControl = (void *)new CButton ();
((CButton *)*pControl )->Create (strName , WS_CHILD |WS_VISIBLE , rc , pParent , nCtrlID );
break ;
case TYPE_EDIT :
*pControl = (void *)new CEdit ();
((CEdit *)*pControl )->Create (WS_CHILD |WS_VISIBLE , rc , pParent , nCtrlID );
break ;
case TYPE_COMBOBOX :
*pControl = (void *)new CComboBox ();
((CComboBox *)*pControl )->Create (WS_CHILD |WS_VISIBLE |CBS_DROPDOWNLIST , rc , pParent , nCtrlID );
((CComboBox *)*pControl )->SetWindowPos (NULL , rc .left , rc .top , rc .right -rc .left , (rc .bottom -rc .top )*5, 0);
break ;
}
return TRUE ;
}
BOOL
CWnd
UINT
UINT
char
{
if
{
AfxMessageBox
return
}
if
{
AfxMessageBox
return
}
if
{
AfxMessageBox
return
}
switch
{
case
*pControl
((CStatic
break
case
*pControl
((CButton
break
case
*pControl
((CEdit
break
case
*pControl
((CComboBox
((CComboBox
break
}
return
}
SetMessageTransfer(m_pCombAreaSort->GetSafeHwnd(), (long)ComboBoxWndProc, (long&)wprcComboSort);
BOOL CExpBaseDialog ::SetMessageTransfer (HWND hWnd , const long pWndProc , long & pOldWndProc )
{
pOldWndProc = ::SetWindowLong (hWnd , GWL_WNDPROC , pWndProc );
return TRUE ;
}
{
pOldWndProc
return
}
然后编写相应的消息转发代码:
LRESULT
CALLBACK
ComboBoxWndProc
(
HWND
hWnd
,
UINT
uMsg
,
WPARAM
wParam
,
LPARAM
lParam
)
{
/ // 取得控件ID
UINT
ctlID
=
GetDlgCtrlID
(
hWnd
);
/ // 为ComboBox选项更改时, 发送消息到界面
if
(
WM_COMMAND
==
uMsg
&&
CBN_SELCHANGE
==
HIWORD
(
wParam
))
{
:: PostMessage (
GetParent
(
GetParent
(
GetParent
(
hWnd
))),
uMsg
,
MAKEWPARAM (
ctlID
,
CBN_SELCHANGE
),
lParam
);
}
/ // 其它情况用默认处理方式
return
::
CallWindowProc
(
IDC_COMBOBOX_AREASORT
==
ctlID
?
wprcComboSort
:
wprcComboOne
,
hWnd ,
uMsg
,
wParam
,
lParam
);
}
{
/ // 取得控件ID
/ // 为ComboBox选项更改时, 发送消息到界面
{
:: PostMessage
MAKEWPARAM
}
/ // 其它情况用默认处理方式
hWnd
}
如此即可将消息转发至主界面,由主界面指定的函数进行处理,此法不需要额外添加一个子类,也可以调用主界面的处理函数,于主界面数据交互更是不成问题,个人倾向使用此法。
相对而言,如对new 出的控件想添加变量值关联(如int, CString型变量), 则可于DoDataExchange内手动添加控件关联调用, 形如
DDX_Text(pDX, IDC_EDIT_PAR_NUM, m_iPARNum);
但同样要注意, 如果位于子窗口的子窗口控件, 则直接使用此语句会出错, 解决办法, 仿照CWnd::UpdateData(), 于对话框DoDataExchange函数里面自行构建一CDataExchange类, 传入new 出控件的父窗口指针即可.
/// 添加对新添控件的关联
if (m_pWndResult )
{
CDataExchange dx (m_pWndResult , pDX ->m_bSaveAndValidate );
DDX_Text (&dx , IDC_EDIT_AAR_NUM , m_iAARNum );
DDX_Text (&dx , IDC_EDIT_AAR_NUM_PER , m_fAARNumPer );
DDX_Text (&dx , IDC_EDIT_PAR_NUM , m_iPARNum );
DDX_Text (&dx , IDC_EDIT_PAR_NUM_PER , m_fPARNumPer );
DDX_Text (&dx , IDC_EDIT_PAR_LATENCY , m_fPARLatency );
DDX_Text (&dx , IDC_EDIT_PAR_LTC_PER , m_fPARLatencyAvg );
DDX_Text (&dx , IDC_EDIT_ERROR_NUM , m_iErrorNum );
}
{
CDataExchange
DDX_Text
DDX_Text
DDX_Text
DDX_Text
DDX_Text
DDX_Text
DDX_Text
}