1. 新建一个基于对话框的MFC程序。
2. 在工程中添加一个新类CMyButton,基类选择CButton。
3. 然后转至新生成的MyButton.h头文件中,将鼠标放在类名CMyButton上点击一下,打开“属性”窗口,在“消息”按钮下为此类添加 WM_LBUTTONDOWN,WM_LBUTTONUP消息,并重写DrawItem虚函数(这个函数是重绘按钮时要调用的,在这里面可以自定义一些绘制按钮的操作,必须设置控件属性为 OWNERDRAW才会调用此函数。)。
4. 在MyButton.h头文件中添加一个BOOL型变量m_BtnDown,来判断按钮是否被按下,并在构造函数中初始化为false.
5. 鼠标左键事件的代码:
void CMyButton::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
m_BtnDown=true;
//引起按钮重绘
Invalidate();
CButton::OnLButtonDown(nFlags, point);
}
void CMyButton::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
m_BtnDown=false;
Invalidate();
CButton::OnLButtonUp(nFlags, point);
}
注:Invalidate(BOOL bErase=TRUE)函数的作用是使整个窗口的客户区无效。客户区无效就意味着需要重绘。例如,如果一个被其它窗口遮住的窗口变成了前台窗口,那么原来被遮住的部分就是无效的,需要重绘。这时Windows会在应用程序的消息队列中放置WM_PAINT消息。MFC为窗口类提供了WM_PAINT的消息处理函数OnPaint,OnPaint负责重绘窗口。视图类有一些例外,在视图类的OnPaint函数中调用了OnDraw函数,实际的重绘工作由OnDraw来完成。参数bErase为TRUE时,重绘区域内的背景将被擦除,否则,背景将保持不变。
和UpdateWindow的区别是UpdateWindow是使窗口立即重绘。Invalidate只是发送一个WM_PAINT消息,WM_PAINT消息的优先级很低,需要等到消息队列中其他消息处理完成后才能被处理,所以窗口不会立即刷新。
窗口重绘时会重新生成按钮,按钮生成时会查看其风格,如果为OwnerDraw风格,则会调用DrawItem函数。关于OwnerDraw风格的设置,后边会讲。
- 6. DrawItem事件重载:
void CMyButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
// TODO: 添加您的代码以绘制指定项
//通过DRAWITEMSTRUCT结构获取句柄,并且类型转换,这个FromHandle很常用
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);
}
if (m_BtnDown)
{ //填充矩形
pDC->FillSolidRect(rect,RGB(255,255,0));
}
else
{
pDC->FillSolidRect(rect,RGB(0,0,255));
}
if (!str.IsEmpty())
{
//以下是当按钮按下时要稍微移动按钮上文字的位置
//使用当前字体计算一行文字的宽度和长度以确定尺寸
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);
}
//设置背景样式,值有两个:
//OPAQUE 在文字,画刷,铅笔绘画之前,背景由当前背景颜色填充。这时默认的背景模式。
//TRANSPARENT 在绘制前背景不变
int nMode=pDC->SetBkMode(TRANSPARENT);
if (state & ODS_DISABLED)
{
pDC->DrawState(pt,Extent,str,DSS_DISABLED,TRUE,0,(HBRUSH)NULL);
}
else
{
pDC->TextOut(pt.x,pt.y,str);
}
pDC->SetBkMode(nMode);
}
}
注:
- 1. DrawItem函数用于重绘单个控件。其参数为指向DRAWITEMSTRUCT的一个长指针。
DRAWITEMSTRUCT 为需要自绘的控件或者菜单项提供了必要的信息。在需要绘制的控件或者菜单项对应的WM_DRAWITEM消息函数中得到一个指向该结构的指针。
- 2. 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)指的就是控件的左上角。但是系统不裁剪菜单项,所以在绘制菜单项的时候,必须先通过一定的换算得到该菜单项的位置,以保证绘制操作在我们希望的区域中进行。
- 3. DC(设备上下文):设备上下文是一种包含有关某个设备(如显示器或打印机)的绘制属性信息的 Windows 数据结构。所有绘制调用都通过设备上下文对象进行,这些对象封装了用于绘制线条、形状和文本的 Windows API。设备上下文允许在 Windows 中进行与设备无关的绘制。设备上下文可用于绘制到屏幕、打印机或者图元文件。
hDC是DC的一个DC的句柄。CDC是所有DC的基类。
- 4. FromHandle(HANDLE h)从网上查找的解释是,先进行查找,如果h与某个CWnd(窗口)对象相关联,就返回此CWnd对象的指针;如果h没有与任何CWnd对象关联,则new一个新的CWnd对象返回。
- 7. 自定义类与标准控件的关联
方法一:设置按钮的属性为Owner Drawn.
方法二:在程序中,重载PreSubClassWindow。此函数在CWnd::Create和DDX_Contorl之后调用,即在窗口子类化创建后和窗口显示前调用。在此函数中添加ModifyStyle(0,BS_OWNERDRAW);此函数用于改变控件的类型。
- 8. 创建子类化的自绘按钮
方法一、在创建按钮时可以给这个按钮绑定一个CMyButton类对象,但是这个时候在classwizard中还不能看到这个类: 可以删掉当前目录下的clw文件,然后重新打开程序,按下ctrl+w,然后一般选择add all,点击ok,这个时候再添加类对象,就会发现这个类了
方法二、上述方法不适合动态添加,下面用程序的方法动态添加
在窗口中拖入一个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());