单选框有:BS_RADIOBUTTTON、BS_AUTORADIOBUTTON两种显示风格。具有BS_AUTORADIOBUTTON风格的单选框,只要选中一个子项,同组的其它子项就会自动变成非选中状态。本文主要讨论后者的绘制。
绘制难点:
确定同组中的所有子项
解决方法:
以WS_GROUP标识为界,遍历所有窗口,我们可以筛选出同组的子窗口集合。
这样做逻辑上合理,但是,如果在一组单选框中,存在复选框时;则复选框也会被误当成子项。要解决这个问题,我们可以把同组中的子项都加上标识;这样,即使存在复选框,也不会对子窗口集合产生干扰。
我测试了下,子项标识任意选取,都能实现我们需要的效果。但是,为了与标准控件保持一致,我们用Spy++查看标准单选框,发现如下信息:
据MSDN说明,WM_GETDLGCODE消息发送给窗口控件后,控件返回自身想要处理的输入类型。从消息返回值中,我们发现刚好有DLGC_RADIOBUTTON(单选框)。于是,我们选取DLGC_RADIOBUTTON作为子项标识。
主要源码如下:
LRESULT CRadioBoxEx::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
// TODO: Add your specialized code here and/or call the base class
if (message == BM_GETCHECK)
{
return m_nCheck ;
}
else if (message == BM_SETCHECK)
{
// 保存状态
m_nCheck = int(wParam) ;
// 强制重绘
Invalidate() ;
// 设置同组中其他单选框的状态
if (((m_dwStyle & BS_AUTORADIOBUTTON) == BS_AUTORADIOBUTTON) && (wParam == 1))
{
HWND hWndCtrl = NULL;
// 向前遍历,设置状态
hWndCtrl = GetSafeHwnd() ;
while (hWndCtrl != NULL && !(GetWindowLong(hWndCtrl, GWL_STYLE) & WS_GROUP))
{
hWndCtrl = ::GetWindow(hWndCtrl, GW_HWNDPREV);
// 如控件输入类型匹配,则处理
if (::SendMessage(hWndCtrl, WM_GETDLGCODE, 0, 0) & DLGC_RADIOBUTTON)
{
::SendMessage(hWndCtrl, BM_SETCHECK, BST_UNCHECKED, 0);
}
}
// 向后遍历,设置状态
hWndCtrl = ::GetWindow(GetSafeHwnd(), GW_HWNDNEXT) ;
while(hWndCtrl != NULL && !(GetWindowLong(hWndCtrl, GWL_STYLE) & WS_GROUP))
{
// 如控件输入类型匹配,则处理
if (::SendMessage(hWndCtrl, WM_GETDLGCODE, 0, 0) & DLGC_RADIOBUTTON)
{
::SendMessage(hWndCtrl, BM_SETCHECK, BST_UNCHECKED, 0);
}
hWndCtrl = ::GetWindow(hWndCtrl, GW_HWNDNEXT);
}
}
return 0;
}
else if (message == WM_GETDLGCODE)
{
// 返回自身想要处理的输入类型
if ((m_dwStyle & BS_AUTORADIOBUTTON) == BS_AUTORADIOBUTTON)
{
return DLGC_RADIOBUTTON;
}
}
return CButton::WindowProc(message, wParam, lParam);
}
void CRadioBoxEx::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
if (!m_bLBtnDown)
{
m_bLBtnDown = TRUE ;
}
CButton::OnLButtonDown(nFlags, point);
}
void CRadioBoxEx::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
if (m_bLBtnDown)
{
m_bLBtnDown = FALSE ;
CRect rcClient ;
GetClientRect(rcClient) ;
if (rcClient.PtInRect(point))
{
// 设置当前单选框的状态
if ((m_dwStyle & BS_AUTORADIOBUTTON) == BS_AUTORADIOBUTTON)
{
SetCheck(1) ;
}
}
}
CButton::OnLButtonUp(nFlags, point);
}
效果图如下: