mfc 按钮自绘



MFC  按钮自绘

author:songyanwu

            如果你是大神就没必要看这个文章了!


说明:此文章简单的自绘一个按钮,同时也介绍自绘;(绝对适合初学者入门,我也Goolgle 很多资料,介绍相当模糊为此也花了两天时间整理和学习!)虽然做的很简单,却包含了 自绘的原理。



源码下载:mfc 按钮自绘


先说说自己的一些想法:我就想把按钮封装成一个类,每次在使用的时候会很方便,当然在自己的类中去重载也可以!


此文章可借鉴学习:MFC基础,MFC自绘控件学习总结. (我也主要研究了自绘控件的子类化方法  ),看完前面推荐的文章,你似乎有何种感觉呢?

 

先实际操作吧;原理在后面介绍:


1 新建一个对画框 应用程序
2 新添加一个CMyButton继承CButton
3 为你自己添加的类 添加消息WM_LBUTTONDOWN,WM_LBUTTONUP消息,并重写DrawItem虚函数(函数从绘调用),
4 在你添加的类中 添加Bool  变量 m_BtnDown来判断按钮的状态
5 添加消息代码如下
<span style="font-size:14px;">void CMyButton::OnLButtonDown(UINT nFlags, CPoint point)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值
	m_BtnDown = true;
	Invalidate();
	CButton::OnLButtonDown(nFlags, point);
}</span>


<span style="font-size:14px;">void CMyButton::OnLButtonUp(UINT nFlags, CPoint point)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值
	m_BtnDown=false;

	Invalidate();

	CButton::OnLButtonUp(nFlags, point);
}</span>

注:Invalidate(BOOL bErase=TRUE)函数的作用是使整个窗口的客户区无效。客户区无效就意味着需要重绘。例如,如果一个被其它窗口遮住的窗口变成了前台窗口,那么原来被遮住的部分就是无效的,需要重绘。这时Windows会在应用程序的消息队列中放置WM_PAINT消息。MFC为窗口类提供了WM_PAINT的消息处理函数OnPaintOnPaint负责重绘窗口。视图类有一些例外,在视图类的OnPaint函数中调用了OnDraw函数,实际的重绘工作由OnDraw来完成。参数bEraseTRUE时,重绘区域内的背景将被擦除,否则,背景将保持不变。

             UpdateWindow的区别是UpdateWindow是使窗口立即重绘。Invalidate只是发送一个WM_PAINT消息,WM_PAINT消息的优先级很低,需要等到消息队列中其他消息处理完成后才能被处理,所以窗口不会立即刷新。

             窗口重绘时会重新生成按钮,按钮生成时会查看其风格,如果为OwnerDraw风格,则会调用DrawItem函数。关于OwnerDraw风格的设置,后边会讲。


6 重载DramItem

<span style="font-size:14px;">void CMyButton::DrawItem(LPDRAWITEMSTRUCT 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);  

	}  


}
</span>

注:

1. DrawItem函数用于重绘单个控件。其参数为指向DRAWITEMSTRUCT的一个长指针。

DRAWITEMSTRUCT为需要自绘的控件或者菜单项提供了必要的信息。在需要绘制的控件或者菜单项对应的WM_DRAWITEM消息函数中得到一个指向该结构的指针。

2

<span style="font-size:14px;"> typedef struct tagDRAWITEMSTRUCT {

         UINT CtlType;

         UINT CtlID;

           UINT itemID;

         UINT itemAction;

         UINT itemState;

         HWND hwndItem;

         HDC hDC;

         RECT rcItem;

         DWORD itemData;

} DRAWITEMSTRUCT;

</span><span style="font-size:14px;">
</span>




CtlType:指定参数类型。例:ODT_BUTTONODT_COMBOBOXODT_LISTBOX等。

CtlIDcombo 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_HOTLIGHTWindows XP: 如果鼠标指针位于控件之上,则设置该值,这时控件会显示高亮颜色。

       ODS_INACTIVE Windows XP: 表示没有激活的菜单项。

             ODS_NOACCELWindows XP: 控件是否有快速键盘。

             ODS_NOFOCUSRECTWindows XP,不绘制捕获焦点的效果。

             ODS_SELECTED:选中的菜单项。

hwndItem:指定了组合框、列表框和按钮等自绘控件的窗口句柄;如果自绘的对象是菜单项,则表示包含该菜单项的菜单句柄。

hDC:指定了绘制操作所使用的设备环境。

rcItem:指定了将被绘制的矩形区域。这个矩形区域就是上面hDC的作用范围。系统会自动裁剪组合框、列表框或按钮等控件的自绘制区域以外的部分。也就是说rcItem中的坐标点(00)指的就是控件的左上角。但是系统不裁剪菜单项,所以在绘制菜单项的时候,必须先通过一定的换算得到该菜单项的位置,以保证绘制操作在我们希望的区域中进行.

3.       DC(设备上下文):设备上下文是一种包含有关某个设备(如显示器或打印机)的绘制属性信息的 Windows 数据结构。所有绘制调用都通过设备上下文对象进行,这些对象封装了用于绘制线条、形状和文本的 Windows API设备上下文允许在 Windows 中进行与设备无关的绘制。设备上下文可用于绘制到屏幕、打印机或者图元文件

hDCDC的一个DC的句柄。CDC是所有DC的基类。

4.       FromHandleHANDLE h)从网上查找的解释是,先进行查找,如果h与某个CWnd(窗口)对象相关联,就返回此CWnd对象的指针;如果h没有与任何CWnd对象关联,则new一个新的CWnd对象返回。

7.      自定义类与标准控件的关联

方法一:设置按钮的属性为Owner Drawn.

方法二:在程序中,重载PreSubClassWindow。此函数在CWnd::CreateDDX_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());



前面有代码资源可以下载哟!后面我还会继续去学习自绘的其他应用。



---------------------------------------------------------------------------------网络整理--------------------------------------------------------------

我在学习中经常遇到要重写DrawItem()的情况,但又有一个WM_DRAWITEM消息,它们是什么样的关系呢。
如果我们要重写一个CButton取名为CMyButton,我们可以重写CMyButton的DrawItem()函数来实现我们的需求,
但CMyButton::DrawItem()是在什么时候调用呢?它是在它的宿主类的OnDrawItem()中被调用,
OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct )正是对WM_DRAWiTEM的相应函数。
宿主类可以根据nIDCtl来判定是哪个子控件。其实我们可以在OnDrawItem函数里对子控件进行绘制,但是有很多
的子控件看起来不好,所以我们应该在子类的DrawItem对子类绘制,例如CMyButton::DrawItem。所以可以
这样理解,OnDrawItem是画窗口中的子控件的,因为它的入口参数 LPDRAWITEMSTRUCT带入不同子控件的相
关参数,而且,你得把子控件设置成“自画”类型,才会调用到OnDrawItem。
    当自绘按钮(owner-draw button),下拉列表框(combo box),列表框(list box)视觉属性,或者菜单发生变化时,
框架为他们的owner调用OnDrawItem(发送WM_DRAWITEM),在宿主类调用子类的DrawItem(发送WM_DRAWITEM消息)。
我们可以重载子类的DrawItem可以绘制自己需要的控件,不是所有设置成自画类型的控件都会调用父窗口的OnDrawItem,
例如ListBox的自画,你就必须重载CListBox的DrawItem方法和MeasureItem方法才可以,但象菜单,按钮等的自画则会调用
OnDrawItem。在SDK中,子类是不可能受到WM_DRAWITEM,在MFC中可以,这是类的设计者设计的(反射),这的确不错。
    在学习中还有一个消息也是由宿主类被调用的,它就是WM_CTRCOLOR。这个消息是在子控件将要绘画时,向宿主
类发送,宿主类利用发射机制让子类自己又一个处理的机会。OnCtlColor (CDC* pDC, CWnd* pWnd,  UINT  nCtlColor)
pDC,pWnd都是于子类相关的,在这里可以设置,前景颜色,背景颜色,画刷类型,字体等等,但不能改变元素的界面框架,
这是DrawItem 所能干的。
   如果同时有DrawItem(子类),OnDrawItem(宿主类),OnCtlColor(宿主类),它们的调用顺序是:
OnCtlColor,OnDrawItem,DrawItem。
    如果我们同时又相应的子类的WM_PAINT消息,这也许OnPaint在内部进行了一些处理,判断是否自绘来决定是否向宿主类
发送WM_DRAWITEM,所以如果响应了WM_PAINT子类就不会向宿主类发送WM_DRAWITEM消息,你要完成子类的全部绘
制工作,如果子类是一个列表框,就很麻烦。这时调用顺序是OnCtlColor,OnPaint。
  在发送一个WM_PAINT消息前,总会先发送一个WM_ERASEBACK消息,我们在这里在一个背景图片。
   对于我们平时对控件的绘制,上面介绍的差不多了,还有一个CView的问题,也就是OnPaint和Ondraw的关系,其实这个很简单,CView::OnPaint()的源码如下:
view plaincopy to clipboardprint?
void CView::OnPaint()    
{    
     CPaintDC dc(this);          
     OnPrepareDC(&dc);          
     OnDraw(&dc)    
}   
 
本文来自CSDN博客,转载请标明出处: http://blog.csdn.net/highfly4008/archive/2011/01/19/6152485.aspx

转载二:

WM_DRAWITEM消息

当具有自绘风格的按钮、组合框、列表框或者菜单的可见部分发生改变时,就会发送WM_DRAWITEM消息给自绘控件所在的窗体。

1 在控件创建时肯定被调用

DrawItem()的情况,但又有一个WM_DRAWITEM消息,它们是什么样的关系呢。

如果我们要重写一个CButton取名为CMyButton,我们可以重写CMyButton的DrawItem()函数来实现我们的

需求,但CMyButton::DrawItem()是在什么时候调用呢?它是在它的宿主类的OnDrawItem()中被调用,

OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct )正是对WM_DRAWiTEM的相应函数。

宿主类可以根据nIDCtl来判定是哪个子控件。其实我们可以在OnDrawItem函数里对子控件进行绘制,但是有很多

的子控件看起来不好,所以我们应该在子类的DrawItem对子类绘制,例如CMyButton::DrawItem。所以可以

这样理解,OnDrawItem是画窗口中的子控件的,因为它的入口参数LPDRAWITEMSTRUCT带入不同子控件的相

关参数,而且,你得把字控件设置成“自画”类型,才会调用到OnDrawItem





评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值