在工程中添加一个新类CMyButton,基类选择CButton。
在新类中重载 DrawItem事件,自绘的按钮需要设置OwnerDraw风格,则会自动调用DrawItem函数。这是因为在MFC中,要想激活控件的自绘功能,要求该控件的属性中必须包含属性值BS_OMNERDRAW,这一步我们可以通过类向导为CXPButton类添加PreSubclassWindow()函数,在该函数中完成属性值的设置。当激活控件的自绘功能之后,每次控件状态改变的时候都会运行函数DrawItem(),该函数的作用就是绘制控件在各种状态下的外观。
typedef struct tagDRAWITEMSTRUCT {
UINT CtlType;
UINT CtlID;
UINT itemID;
UINT itemAction;
UINT itemState;
HWND hwndItem;
HDC hDC;
RECT rcItem;
DWORD itemData;
} DRAWITEMSTRUCT;
CtlType:指定参数类型。例:ODT_BUTTON、ODT_COMBOBOX、ODT_LISTBOX等。CtlID:combo box, list box, 或者 button的控件ID。菜单项不需要此参数。
itemID:菜单项的ID,也可以表示列表框或者组合框中某项的索引值。对于一个空的列表框或组合框此值为-1.这时应用程序只绘制焦点矩形(该矩形的坐标有rcItem成员给出)。虽然此时控件没有需要显示的项,但绘制焦点矩形还是很有必要的。因为这样能够提示用户该控件是否具有输入焦点。当然也可设置itemAction的值,使得无需绘制焦点。
itemAction:指定绘制行为,可取多值。ODA_DRAWENTIRE:当整个控件都需要被绘制时,设置该值。ODA_FOCUS:控件在获得或是去焦点时被绘制。此时应该检查itemState成员,以确定控件是否具有输入焦点。ODA_SELECT:控件在选中状态改变时被重绘。此时应检查itemState成员,以确定控件是否被选中。
itemState:指定当前绘制操作完成后,所绘项的可见状态。
ODS_CHECKED:菜单项被选中。该值只对菜单项有用。
ODS_COMBOBOXEDIT:在自绘组合框控件中只绘制选择区域。
ODS_DEFAULT:默认值。
ODS_DISABLED:控件被禁止,则设置该值。
ODS_FOCUS:控件需要输入焦点,则设置该值。
ODS_GRAYED:控件被灰色显示。该值只在绘制菜单时使用。
ODS_HOTLIGHT:Windows XP: 如果鼠标指针位于控件之上,则设置该值,这时控件会显示高亮颜色。
ODS_INACTIVE: Windows XP: 表示没有激活的菜单项。
ODS_NOACCEL:Windows XP: 控件是否有快速键盘。
ODS_NOFOCUSRECT:Windows XP,不绘制捕获焦点的效果。
ODS_SELECTED:选中的菜单项。
hwndItem:指定了组合框、列表框和按钮等自绘控件的窗口句柄;如果自绘的对象是菜单项,则表示包含该菜单项的菜单句柄。
hDC:指定了绘制操作所使用的设备环境。
rcItem:指定了将被绘制的矩形区域。这个矩形区域就是上面hDC的作用范围。系统会自动裁剪组合框、列表框或按钮等控件的自绘制区域以外的部分。也就是说rcItem中的坐标点(0,0)指的就是控件的左上角。但是系统不裁剪菜单项,所以在绘制菜单项的时候,必须先通过一定的换算得到该菜单项的位置,以保证绘制操作在我们希望的区域中进行。
在窗口中拖入一个Button控件。在Dlg的头文件中加入成员函数CMyButton m_MyButton(注意要加入头文件)。然后在此窗口的OnInitDialog函数中调用m_MyButton.SubClassDlgItem(IDC_BUTTON1,this).此函数可以动态的子类化一个由此窗口创建的控件并将此控件关联至此CWnd.
如果你已经有了一个窗口的指针,或者你的工作在一个CView或者其他CWnd的派生类中且里面的控件被动态创建,或者你不想用上边的函数,你可以使用下述方法。
CWnd* pWnd = GetDlgItem(IDC_BUTTON1); m_btnMyButton.SubclassWindow(pWnd->GetSafeHwnd());
PreSubclassWindow是在CWnd::SubclassWindow中调用的,其目的是允许程序在动态子类化控件或窗口之前做一些其它所需要的动作,见下面代码。
BOOL CWnd::SubclassWindow(HWND hWnd)
{
if (!Attach(hWnd))
return FALSE;
// allow any other subclassing to occur
PreSubclassWindow();
// now hook into the AFX WndProc
WNDPROC* lplpfn = GetSuperWndProcAddr();
WNDPROC oldWndProc = (WNDPROC)::SetWindowLongPtr(hWnd, GWLP_WNDPROC,
(INT_PTR)AfxGetAfxWndProc());
ASSERT(oldWndProc != AfxGetAfxWndProc());
if (*lplpfn == NULL)
*lplpfn = oldWndProc; // the first control of that type created
#ifdef _DEBUG
else if (*lplpfn != oldWndProc)
{
TRACE(traceAppMsg, 0, "Error: Trying to use SubclassWindow with incorrect CWnd\n");
TRACE(traceAppMsg, 0, "\tderived class.\n");
TRACE(traceAppMsg, 0, "\thWnd = $%08X (nIDC=$%08X) is not a %hs.\n", (UINT)(UINT_PTR)hWnd,
_AfxGetDlgCtrlID(hWnd), GetRuntimeClass()->m_lpszClassName);
ASSERT(FALSE);
// undo the subclassing if continuing after assert
::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (INT_PTR)oldWndProc);
}
#endif
return TRUE;
}
在DrawItem中写代码:
void CMyButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
// TODO: 添加您的代码以绘制指定项
//LPDRAWITEMSTRUCT lpDrawItemStruct;
CDC *pDC=CDC::FromHandle(lpDrawItemStruct->hDC);
UINT state=lpDrawItemStruct->itemState;
CRect rect=lpDrawItemStruct->rcItem;
CString str;
GetWindowText(str);
if(state & ODS_SELECTED)
{
pDC->DrawFrameControl(&rect,DFC_BUTTON,DFCS_BUTTONPUSH | DFCS_PUSHED);
}
else
{
pDC->DrawFrameControl(&rect,DFC_BUTTON,DFCS_BUTTONPUSH);
}
CSize Extent=pDC->GetTextExtent(str);
CPoint pt=CPoint(rect.CenterPoint().x-Extent.cx/2,rect.CenterPoint().y-Extent.cy/2);
//如果被选中
if (state & ODS_SELECTED)
{
//向一个点中添加x,y坐标
pt.Offset(1,1);
}
pDC->TextOut(pt.x,pt.y,str);
}
代码实现: