vc++按钮应用大全

来源:互联网 发布:哪个软件可以租到豪车 编辑:程序博客网 时间:2024/06/08 19:57
 

本文需要的主要工具:

VC6.0

MSDN

MFC类库详解(网上可以下载,对于英文不过关的人很方便) 等等…

本例源代码:vc++按钮应用大全

I按钮应用大全

按钮是最常用的控件,使用也比较简单,因此介绍控件的用法就从按钮开始。本文计划通过四个阶段对按钮的使用进行详细阐述,即基础、进阶、高级、特殊。

一、基础应用

(一)在视图中创建按钮

示例程序:Button

1、普通按钮

在视图中创建按钮非常简单,需要以下步骤:

(1)在视图类中定义成员变量或CButton指针

在CButtonView类上点击右键,选择Add Member Variable…,添加成员变量m_Btn,*p_Btn;

(2)为按钮分配ID号

我们可以为按钮分配超过100以上的任意ID号,但是规范的做法是在资源视图中选择String Table,在其中添加资源ID及其注解或标题。

(图I-1)

(3)为视图类添加windows消息处理函数OnCreate(…)

一个窗口创建之后发送的第一个消息就是WM_CREATE消息,其消息响应函数为OnCreate(),因此在这个函数中创建按钮最合适。在CButtonView类上点击右键,选择Add Windows Message Handle…,在生成的对话框中选择WM_CREATE消息,生成函数。

(4)首先获取资源中按钮的标题(当然也可以用任意标题,例如第二个按钮),如果定义的是成员变量,则直接调用Create函数创建按钮;如果是指针,则先new一个对象,然后用Create函数创建按钮。

在OnCreate函数中添加如下代码(附注释):

//定义字符串,用来存储按钮标题

       CString str_push1;

       //获取按钮1标题

       str_push1.LoadString(IDC_BUTTON_PUSH1);

       //创建按钮1

       m_Btn.Create(str_push1,WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON ,CRect(10,10,200,60),

              this,IDC_BUTTON_PUSH1);

      

       //生成按钮对象

       p_Btn=new CButton();

       //创建按钮2

       p_Btn->Create(_T("第二个按钮"),WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON ,CRect(10,70,200,120),

              this,IDC_BUTTON_PUSH2);

Create函数具体用法这里就不赘述了,需要注意的是,按钮窗口风格包括以下内容:

BS_3STATE 与复选框一样本样式按钮可被单击变暗。变暗状态通常用于指示本样式的按键正处于禁用状态。

*.

BS_AUTO3STATE 与三状态的复选框一样当用户选中它本按钮样式状态外观会改变。

*.

BS_AUTOCHECKBOX 与复选框一样,除了在用户点控件后会出现一个选中标志,当用户在下一次点选时,该标志会消失。

*.

BS_AUTORADIOBUTTON 与单先框一样,不同的是,用户点选它时会高亮显示,同时,会把同一组的其它同样的按钮的高亮状态转移到自己身上。

*.

BS_BITMAP 指定按钮以一张位图显示。

*.

BS_BOTTOM 把按钮标题放置到按钮矩形区域的底部。

*.

BS_CENTER 按钮标题在按钮的矩形区域中央显示。

*.

BS_CHECKBOX 在按钮的右边创建一个小方块(此样式必须与BS_LEFTTEXT结合使用。)

*.

BS_DEFPUSHBUTTON 创建一个通用的默认按钮该按钮有一个厚重的黑色边框。用户可以通过按回车键来点选本按钮,该按钮可以实现用户通常要使用的功能(即默认执行动作功能)。

*.

BS_FLAT 指定按钮为2D按钮,不采用3D控件所使用的阴影。

*.

BS_GROUPBOX 创建一个分组框来给控件分组,如果使用了标题,则标题会出现在分组框的左上角位置。

*.

BS_ICON 指定按钮上显示一个图标。

*.

BS_LEFT 在控件的矩形区域内左对齐标题。如果按钮是一个没有 BS_RIGHTBUTTON 样式的复选框或单选框 ,那么文本居将在复选框或单选框的右边居左对齐(这话有些多余,意思文本在复选框或单选框的那个可选被小方框或圆圈的右边。)。

*.

BS_LEFTTEXT 当按钮是单选或是复选框时,标题文本将出现在单选或复选框的客户区(即复选框的矩形框,单选框的圆形框)的左边。

*.

BS_MULTILINE 如果标题文本太长,将在绘制区域内对文本进行折行处理。

*.

BS_NOTIFY 激活按钮,使之可对父窗口发送BN_DBLCLK, BN_KILLFOCUS,  BN_SETFOCUS 消息,注意:不管有没有使用本样式,按钮都有一个 BN_CLICKED 可发送消息。

*.

BS_OWNERDRAW 创建一个自绘风格的按钮。当按钮的外观发生改变时,框架会调用DrawItem成员函数。本样式在使用CBitmapButton类时必须设置。

*.

BS_PUSHBUTTON 创建一个按钮(即最常见的按钮),该按钮在点击时,将向父窗口发送一个WM_COMMAND 消息。

*.

BS_PUSHLIKE 把(多选框,三态多选框,单选框)以按钮的形式显示,该按钮在未选种状态时是浮起的,但在选中状态时是陷入状态的。

*.

BS_RADIOBUTTON 创建单选框,该按钮有一个圆形的客户区,(在本样式不与 BS_LEFTTEXT 样式结合使用的情况下)标题文本在其右方。单选框通常用于有相关联的多个可选项里面,但相互之间只有作一个选择的情况下。

*.

BS_RIGHT 在按钮的绘制区域内右对齐文本。但如果按钮是一个没有BS_RIGHTBUTTON样式的单选或复选框,标题文本将在单选或复选框可点选区的右边居右对齐。

*.

BS_RIGHTBUTTON 设定单选框的圆形可选区或复选框的方开形复选区出现在按钮的矩形区域的右边。与 BS_LEFTTEXT 的效果一样。

*.

BS_TEXT 指定按钮将显示文本标题。

*.

BS_TOP 将标题文本显示在按钮的绘制区域的顶边。

*.

BS_USERBUTTON 已废弃不用,只作为兼容16位系统版本的Windows,基于32位windows系统的请用 BS_OWNERDRAW 样式取代。

*.

BS_VCENTER 设定按钮的标题在绘制区域的垂直方向居中。

我们可以使用上述按钮风格的组合,创造出不同样式和种类的按钮。

(5)使用指针的情况下,在析构函数中释放对象。

delete p_Btn;

 

2、复选按钮

创建复选按钮的方法与普通按钮相同,只需要调整按钮风格即可,创建代码如下:

//创建三态复选按钮

m_BtnC.Create(_T("三态复选按钮"),WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTO3STATE,CRect(10,130,200,180),this,IDC_BUTTON_CHECK);

3、单选按钮

创建方法与前类似,但是由于单选按钮往往是成组出现的,需注意创建时第一个按钮必须加上windows风格WS_GROUP。创建代码如下:

//创建单选按钮1

m_BtnR1.Create(_T("单选按钮1"),WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTORADIOBUTTON | WS_GROUP,CRect(10,190,200,240),          this,IDC_BUTTON_RADIO1);

//创建单选按钮2

m_BtnR2.Create(_T("单选按钮2"),WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTORADIOBUTTON,CRect(10,250,200,300),

              this,IDC_BUTTON_RADIO2);

(二)为按钮添加消息响应函数

由于是动态创建的按钮,因此不能简单的使用ClassWizard添加消息响应函数,只能手动添加,以第一个按钮为例,添加单击消息响应函数步骤如下:

(1)在MESSAGE_MAP中添加响应函数。

ON_BN_CLICKED(IDC_BUTTON_PUSH1,OnBtnClk)

(2)在头文件中添加函数定义。

//消息响应函数定义

afx_msg void OnBtnClk();

(3)正常编写消息响应函数的实现部分。例如:

void CButtonView::OnBtnClk()

{

       MessageBox(_T("点击了普通按压式按钮!"));

}

运行效果如图I-2

二、进阶应用

前文已经对按钮的创建和使用方法进行了介绍,通过上文我们已经可以在自己的程序中使用按钮了,但是如果想要更好的运用按钮,还必须对其中的函数有比较深刻的了解。

(一)按钮的成员函数

CButton类的成员函数比较少,而且简单,主要包括获取按钮状态(或风格)和设置按钮状态(或风格)两个方面内容。另外还有一个DrawItem()函数,在高级应用中我们将重点讲到它。按钮的成员函数主要包括如下内容:

函数名称

用途

GetState

检索按钮控件的选中状态、加亮状态和获得焦点状态

SetState

设置按钮控件的加亮状态

GetCheck

检索按钮控件的选中状态

SetCheck

设置按钮控件的选中状态

GetButtonStyle

检索按钮控件的风格

SetButtonStyle

设置按钮控件的风格

GetIcon

检索此前调用SetIcon设置的图标句柄

SetIcon

指定一个在按钮上显示的图标

GetBitmap

检索此前调用SetBitmap设置的位图的句柄

SetBitmap

设置在按钮上显示的位图

GetCursor

检索此前调用SetCursor设置的光标图像的句柄

SetCursor

设置在按钮上显示的光标图像

1、首先我们先看一下获取和设置按钮风格(style)的函数。

GetButtonStyle

检索按钮控件的风格

SetButtonStyle

设置按钮控件的风格

(1)CButton::GetButtonStyle

UINT GetButtonStyle( ) const;

返回值:返回CButton对象的按钮风格。

说明:需注意的是,GetButtonStyle函数只返回BS_style的值,而不返回任何其它窗口风格的值。关于这一点可以查看msdn,另外有一点对于初学者很容易出现误解,BS_FLAT、BS_LEFT等之类的风格不包含在内,可以返回的仅限于如下风格:BS_AUTOCHECKBOX、BS_AUTORADIOBUTTON、BS_AUTO3STATE、BS_CHECKBOX、BS_DEFPUSHBUTTON、BS_GROUPBOX、BS_LEFTTEXT、BS_OWNERDRAW、BS_PUSHBUTTON、BS_RADIOBUTTON、BS_3STATE。

(2)CButton::SetButtonStyle

void SetButtonStyle( UINT nStyle, BOOL bRedraw = TRUE );

参数:

nStyle

指定按钮的风格。

bRedraw

指明按钮是否需要重绘。非零值表示需要重绘,0表示不需要重绘。缺省时需要重绘。

说明:本函数用于改变按钮的风格。同上,此函数只能改变上述风格。(改变其他风格可以使用ModifyStyle函数,具体方法下文示例详解)。

下面我们在Button例子中实现如下功能,即通过点击第一个按钮(普通下压式按钮),动态改变第二个按钮为复选、框单选框和普通下压式按钮。

步骤如下:

(1)首先注释掉原代码:MessageBox(_T("点击了普通按压式按钮!"));

(2)调用GetButtonStyle获取按钮2的风格,并进行条件判断。

(3)调用SetButtonStyle改变按钮2的风格。

代码如下:

void CButtonView::OnBtnClk()

{

       //MessageBox(_T("点击了普通按压式按钮!"));

       UINT style=p_Btn->GetButtonStyle();

       switch (style)

       {

       case BS_PUSHBUTTON:

              p_Btn->SetButtonStyle(BS_AUTO3STATE);

              break;

       case BS_AUTO3STATE:

              p_Btn->SetButtonStyle(BS_AUTORADIOBUTTON);

              break;

       case BS_AUTORADIOBUTTON:

              p_Btn->SetButtonStyle(BS_PUSHBUTTON);

              break;

                    

       }

}

如果我们想要为按钮设置其他风格怎么办呢?其实很简单,只需要改变一个函数,将SetButtonStyle函数改为ModifyStyle,该函数说明如下:

CWnd::ModifyStyle

BOOL ModifyStyle( DWORD dwRemove, DWORD dwAdd, UINT nFlags = 0 );

返回值:如果成功地修改了风格,则返回非零值;否则返回0。

参数:

dwRemove

指定了在修改风格时要清除的窗口风格。

dwAdd

指定了在修改风格时要加入的窗口风格。

nFlags

要传递给SetWindowPos的标志,如果不应调用SetWinowPos,则为0。缺省值为0。预设的标志列表参见说明部分。

说明:

调用这个成员函数以修改窗口的风格。要加入或清除的风格可以用位或操作符(|)来组合。有关可用窗口风格的信息参见《Win32 SDK程序员参考》中的“通用窗口风格”主题和::CreateWindow。

如果nFlags为非零值,则ModifyStyle调用Windows 的API函数::SetWindowPos,并将nFLags与下面的四个预定义值组合,以重画窗口:

SWP_NOSIZE

保持当前大小。

SWP_NOMOVE

保持当前位置。

SWP_NOZORDER

保持当前的Z轴顺序。

SWP_NOACTIVATE

不激活窗口。要修改窗口的扩展风格,参见ModifyStyleEx。

例如,我们可以为普通下压式按钮增加BS_FLAT(2D风格)属性,我们在OnCreate函数中完成这个任务,代码如下:

//改变第一个按钮为2D风格

Btn.ModifyStyle(0,BS_FLAT);

效果如图I-3所示

2、下面我们再来看看设置和获取按钮状态的函数:

GetState

检索按钮控件的选中状态、加亮状态和获得焦点状态

SetState

设置按钮控件的加亮状态

GetCheck

检索按钮控件的选中状态(只对单选和复选按钮有效)

SetCheck

设置按钮控件的选中状态 (只对单选和复选按钮有效)

为了节省篇幅,本文就不全部粘贴上述函数的相关说明了,只是对其中需要注意的事项进行一些说明,想要了解详情请自己去查msdn。

上述后3个函数使用起来都比较容易,唯一可能会造成困扰的就是GetState函数,因为该函数的返回值比较复杂,为了说明方便,我把其说明粘贴如下:

CButton::GetState

UINT GetState() const;

返回值:

返回按钮控件的当前状态。可以使用以下的掩码取得所需的具体状态信息。

掩码

含义

获取方法

0x0003

指定选中状态(选中与否,仅用于单选钮和复选框)。返回0表明未选中,1表明已选中。单选钮在选中时有一个黑圈。复选框选中时则有一个“ⅹ”号。返回2表明选中状态不定(仅用于有三种状态的复选框)。有三种状态的复选框如果处于半色调模式(halftone pattern),就是处于不确定状态

myButton.GetState() & 0x0003

0x0004

指定按钮的加亮状态。非零值表明按钮处于加亮状态。用户单击并且保持鼠标左键于按下状态时,按钮是加亮的。在用户放开鼠标按钮时,加亮状态就不复存在了

myButton.GetState() & 0x0004

0x0008

是否处于获得焦点状态。返回非零值表明按钮正获得焦点

myButton.GetState() & 0x0008

说明:本函数用于检索单选钮或者复选框的状态。

让我们继续完善前面的例子,即实现如下功能:当我们点击第一个按钮时,弹出对话框,说明复选按钮的选中状态。(首先注释掉前面的代码,不注释掉也可以,但会稍微有些干扰。)

代码如下:

//获取按钮状态

       UINT state=m_BtnC.GetState() & 0x0003;

       switch (state)

       {

       case 0:

              MessageBox(_T("未选中"));

              break;

       case 1:

              MessageBox(_T("选中"));

              break;

       case 2:

              MessageBox(_T("状态不确定"));

              break;

       }

3、最后,我们再来看看剩下的几个函数

GetIcon

检索此前调用SetIcon设置的图标句柄

SetIcon

指定一个在按钮上显示的图标

GetBitmap

检索此前调用SetBitmap设置的位图的句柄

SetBitmap

设置在按钮上显示的位图

GetCursor

检索此前调用SetCursor设置的光标图像的句柄

SetCursor

设置在按钮上显示的光标图像

这些函数作用都是用图片来取代按钮上的标题。函数定义请自行查msdn,都相对比较简单,需要注意的是位图如果过大,将会被裁剪。

下面的例子中我们要在Button例子的基础上,把第二个按钮增加一副位图,复选框增加一个图标,单选按钮增加一个光标。(图找的比较随意,别介意)

步骤如下:

(1)为Button工程添加位图、图标、光标资源。(在资源视图内点击相应资源项,添加即可。)

(2)为需要设置图片的按钮设置相应的属性。根据前文所述,为按钮设置新属性(如:BS_BITMAP)需要使用ModifyStyle函数。

(3)设置图片。

代码如下:

       //为按钮设置图片

       //设置按钮属性

       p_Btn->ModifyStyle(0,BS_BITMAP);

       m_BtnC.ModifyStyle(0,BS_ICON);

       m_BtnR1.ModifyStyle(0,BS_ICON);

       //设置图片

       p_Btn->SetBitmap(::LoadBitmap(AfxGetInstanceHandle(),

              MAKEINTRESOURCE(IDB_BITMAP_BTN)));

       m_BtnC.SetIcon(::LoadIcon(AfxGetInstanceHandle(),

              MAKEINTRESOURCE(IDI_ICON_BTN)));

       m_BtnR1.SetCursor(::LoadCursor(AfxGetInstanceHandle(),

              MAKEINTRESOURCE(IDC_CURSOR_BTN)));

上述SetBitmap()、SetIcon()、SetCursor()三个函数为API函数,用来获取位图、图标和光标的句柄。AfxGetInstanceHandle()获取当前应用程序的实例句柄。

(二)其他按钮类

按钮的大多数用法已经介绍完了,上文中通过在按钮上安放各种图片,我们可以得到具有个性的按钮,但是可能大家觉得这十分不方便,而且按钮和位图还要有良好的匹配,否则就会非常难看。不过还好,微软已经帮我们考虑到了这一点,因此他为我们设计了一个新的按钮类CBitmapButton,通过这个类我们可以实现个性化按钮的梦想。下面我们就将介绍这个类的用法。

其实CBitmapButton类使用非常简单,msdn中有比较详细的介绍,在这里我按照msdn中的内容为大家演示一下这个类的用法。

1、在视图中使用CBitmapButton:下面我们就在Button例子中加入一个位图按钮,它包括四个状态,即:未按下(或正常)、按下(或选中)、获得焦点和被禁止存取。为了演示方便,我绘制了四个简单的位图,并在上面添加了文本说明,表示按钮的四个状态。

如图I-4

 

步骤摘录如下:

(1)为按钮创建1到4个位图,导入资源,定义其ID。

(2)构造CBitmapButton对象。

(3)调用Create函数创建Windows按钮控件,并把它加到CBitmapButton对象上。这里需要注意的是在设置风格的时候一定要加上BS_OWNERDRAW风格,否则不会生效的哟!

(4)调用成员函数LoadBitmaps加载位图资源。

(5)最后别忘了调用SizeToContent函数使按钮大小和位图相匹配。

代码如下:

//生成CBitmapButton位图按钮

       m_BmpBtn.Create(NULL,WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON | BS_OWNERDRAW | BS_CENTER,CRect(210,10,390,60),

              this,IDC_BITMAPBUTTON_VIEW);

       m_BmpBtn.LoadBitmaps(IDB_BITMAP_U,IDB_BITMAP_D,IDB_BITMAP_F,IDB_BITMAP_X);

       //设置按钮大小,使之与位图相一致

       m_BmpBtn.SizeToContent();

另外,位图按钮的消息响应函数定义方法和CButton相同,这里就不再多说了,可以参考例子。

2、在对话框中使用CBitmapButton:一般情况下,按钮在对话框中的使用方法都比较简单,本来不想多介绍了,但是CBitmapButton按钮在对话框中的使用有些小复杂(很小很小哟!),不像上述方法通用性那么强,初次接触的人可能还会有些阻碍,因此本人也做了一个简单的例子,供大家学习。

首先在上述Button例子中建立一个模态对话框,具体建立方法离题有些远,本文就不多说了,然后在上面使用一个位图按钮。

我们还要把菜单调整一下,在最后面追加一个功能菜单,在其中放置一个演示按钮选项,用来生成对话框。

我们要在这个对话框上用两种方法建立两个按钮,一个将用到AutoLoad函数,另一个将用前文中使用的方法,请注意区别。

好了下面我们开始创建位图按钮了,和前文一样,我将msdn中的创建过程摘录如下,为了表述清楚,我对其中内容稍作改动,注意这是使用AutoLoad函数的方法:

在对话框控件中包含位图按钮的步骤如下:

(1)为按钮创建1到4个位图。为了和前面例子中的按钮相区别,本例中再次导入4幅位图,名称简化为:U、D、F、X。

(2)创建一个对话框模板,其中有一个自定义的按钮放在需要位图按钮的位置。模板中按钮的大小无关紧要。

(3)把按钮的标题(Caption)设置为BUTTON,并为按钮定义ID(此ID的名称没有关系)。

(4)这是十分关键的一步,先导入位图资源,然后分别为每个位图分配一个ID,ID的值为在步骤3中的标题(Caption)后加上如下的一个字母——U、D、F或X,分别代表正常、按下、获得焦点和禁止存取状态下的按钮。例如,标题设为BUTTON时,ID就应该是字符串:"BUTTONU"、"BUTTOND"、"BUTTONF"和"BUTTONX"。一定不要忘了加上引号,虽然这样做大家可能会觉得有些奇怪,因为不加引号的话,资源编辑器就会为资源赋上一个整数值,MFC在加载图像时就会失败。不要忘了,AutoLoad函数的作用是“自动”加载。

(5)在应用的对话框类(从类CDialog继承而来)中加上一个CBitmapButton成员对象。

(6)在CDialog对象的OnInitDialog例程中调用CBitmapButton对象的AutoLoad函数,参数是按钮的ID和CDialog对象的this指针。

为对话框添加初始化消息响应函数OnInitDialog()。

在其中添加代码创建位图按钮,代码如下:

//创建位图按钮

BmpDlg.AutoLoad(IDC_BMPBTN,this);

当然我们也可以直接用LoadBitmaps函数像在视图中一样的方法在对话框中创建位图按钮,其中需要注意的是CBitmapButton对象必须关联按钮,步骤如下:

(1)利用资源编辑器在对话框上添加按钮;

(2)在对话框类中创建CBitmapButton对象;

(3)为CBitmapButton对象关联前面添加的按钮;

(4)使用LoadBitmaps加载位图;

(5)使用SizeToContent调整按钮大小。

下面我们在对话框上再创建一个位图按钮,使用的位图为第一次我们所使用的4幅位图。代码可以放在对话框初始化函数OnInitDialog中,如下:

       //将m_BmpDlg2与第二个按钮相关联

       m_BmpDlg2.SubclassDlgItem(IDC_BMPBTN2,this);

       //为第二个位图按钮设置位图

       m_BmpDlg2.LoadBitmaps(IDB_BITMAP_U,IDB_BITMAP_D,IDB_BITMAP_F,IDB_BITMAP_X);

       m_BmpDlg2.SizeToContent();

很简单吧,利用这个类我们可以完成大部分的按钮创建工作,并制作出具有自己风格的按钮来,如果你还不满足的话,我再向你推荐一个类,名字叫CButtonST,在网上非常流行,网上有很多介绍,我就不再添乱了。下面我们开始按钮的高级应用……。

三、高级应用

(一)用DrawItem()实现自绘按钮

上面关于按钮的介绍都是比较简单的,实现的功能也比较单一,想要随心所欲的控制按钮就必须要掌握按钮的自绘,也就是学会使用DrawItem函数,说白了,就是自己用代码画出按钮在不同状态下的外观。

(在讲解DrawItem函数用法之前,首先需要说明一下。我看到网上有一个例子程序,名字好像叫做CXPButton,其后好像还写了个补遗,也能实现相同的功能,如果你能够看懂,那么后面的内容请无视。本文主要为了便于理解,因此不十分专业。)

我们继续,首先这个函数是虚函数,也就是说,我们可以在子类中对其进行实现,添加绘制代码,对按钮进行绘制。其次,本函数是当自定义按钮的可视属性发生变化时,由框架调用的。这就说明我们可以在需要对按钮进行重绘的时候使按钮区域无效,并发生重绘,我们可以使用InvalidateRect函数完成这一功能,具体使用方法稍后再说。

简单的说,我们可以首先绘出按钮正常状态下的样子,然后根据按钮的不同状态绘出按钮的样子。

好了,现在我们可以归纳一下自绘按钮的实现思路了:

1、首先我们要创建一个新的CButton派生类,这样我们就可以对其虚函数DrawItem进行重写了。

2、用新类创建一个按钮,注意加上BS_OWNERDRAW属性。

3、重写DrawItem虚函数,判断按钮状态,根据不同状态绘制按钮正常、按下、焦点、禁止时状态的样子。

下面我们可以开始实现这个按钮了。

1、点击Insert菜单,选择New Class…,增加一个新类,名字为CNewButton,其基类为CButton。

2、在新的类上点击右键,选择Add Virtual Function..,选择DrawItem。

在CXPButton的例子中,作者在加入了鼠标进入和离开按钮时按钮的变化,在一定程度上加大了代码理解的难度,本文就将上述功能进行分解实现,也就是说,先不实现鼠标进入和离开按钮的效果,并将代码精简到最简单的程度。

3、不考虑鼠标进入和离开按钮时产生效果的情况下,按钮会有4种状态,即正常、按下、焦点、禁用。对于这4种状态,我们可以利用DrawItem函数的参数LPDRAWITEMSTRUCT lpDrawItemStruct,方便的进行判断。因此我们首先要对这个参数有一定的了解。简单的说,这是一个指向DRAWITEMSTRUCT结构体的指针,关于这个结构体,我将网上的说明粘贴如下:

DRAWITEMSTRUCT

DRAWITEMSTRUCT结构具有如下形式:

typedef struct tagDRAWITEMSTRUCT

{

  UINT  CtlType;

  UINT  CtlID;

  UINT  itemlD;

  UINT  itemAction;

  UINT  itemState;

  HWND  hwndltem;

  HDC   hDC;

  RECT  rcItem;

  DWORD itemData;

} DRAWITEMSTRUCT;

注释:

DRAWITEMSTRUCT结构提供了控制属主决定如何绘制其控制所需要的信息。

该控制的属主通过WM_DRAWITEM消息的lParam参数来获取指向此结构的指针。成员:

CtlType

控件类型。控件类型的取值如下:

·

ODT_BUTTON

自画按钮

·

ODT_COMBOBOX

自画组合框

·

ODT_LISTBOX

自画列表框

·

ODT_MENU

自画菜单

·

ODT_LISTVIEW

列表视控件

·

ODT_STATIC

自画Static控件

·

ODT_TAB

Tab 控件

CtlID

组合框,列表框或按钮的控制ID。对菜单不使用这个成员。

itemID

菜单的菜单项ID或是组合框或列表框中的项的索引。对于空的列表框或组合框,这个成员是一个负值,这允许应用程序只在rcItem成员指定的位置画出焦点矩形,既使控件中没有项。这样用户就可以知道列表框或组合框是否具有输入焦点。itemAction成员中位的设置决定了该矩形是否应当画得就象列表框或组合框拥有输入焦点那样。

itemAction

定义了要求的绘图动作。它可以是下面位中的一个或多个:

·

ODA_DRAWENTIRE

当需要画出整个控件时设置该位。

·

ODA_FOCUS

当控件获得或失去输入焦点时设置该位。如果要确定控件是否拥有输入焦点,应该检查itemState成员。

·

ODA_SELECT

当选择项发生变化时设置该位。如果要确定新的选择状态,应该检查itemState成员。

itemState

指定了完成当前绘图动作后项的可视状态。如果要使菜单项无效,则会设置ODS_GRAYED标志。状态标志如下:

·

ODS_CHECKED

如果要标记菜单项则设置该位。仅对菜单使用。

·

ODS_DISABLED

如果要把该项画成禁止状态则设置该位。

·

ODS_FOCUS

如果该项拥有输入焦点则设置该位。

·

ODS_GRAYED

如果要使该项变灰则设置该位。仅对菜单使用。

·

ODS_SELECTED

如果该项被选中则设置该位。

·

ODS_COMBOBOXEDIT

绘图发生在自画组合框控件的选择区域(编辑控件)。

·

ODS_DEFAULT

该项为缺省项。

hwndItem

指定了组合框,列表框和按钮控件的窗口句柄。指定了包含菜单项的菜单的句柄(HMENU)。

hDC

标识了一个设备环境。在控件上进行绘图操作时必须使用这个设备环境。

rcItem

hDC成员指定的设备环境中的矩形,定义了将要画出的控件的边界。Windows自动将画出的任何东西裁剪在组合框,列表框和按钮的设备环境之内,但是它不裁剪菜单项。在画出菜单项的时候,拥有者不能在rcItem成员所定义的矩形之外绘图。

itemData

对于组合框或列表框,这个成员包含了下面的函数传递给列表框的值:

·

CComboBox::AddString

·

CComboBox::InsertString

·

CListBox::AddString

·

CListBox::InsertString

对于菜单,这个成员包含了下面的函数传递给菜单的值:

·

CMenu::AppendMenu

·

CMenu::InsertMenu

·

CMenu::ModifyMenu

判断按钮的状态,我们只要用到其中的itemState,下面我们开始重写DrawItem函数。为了简单起见,我用文本输出的方法表明了按钮的状态,如果你想绘制个性按钮,请自己添加更加复杂的代码。代码如下:

       CRect rc =  lpDrawItemStruct->rcItem;//获取按钮的大小

       CDC *pDC=CDC::FromHandle(lpDrawItemStruct->hDC);//临时设备环境指针

       int nSaveDC=pDC->SaveDC();//表明所保存的设备上下文的整数,如果出错,则返回0。调用RestoreDC恢复设备上下文时要用到这个返回值。

       //获取按钮状态

       UINT state = lpDrawItemStruct->itemState;

       //绘制普通状况下的按钮

       pDC->TextOut(2,2,_T("正常"));

       if (state & ODS_SELECTED)//按下按钮

       {

              pDC->TextOut(2,2,_T("按下"));

       }

       else if(state & ODS_FOCUS)//按钮有焦点

       {

              pDC->TextOut(2,2,_T("焦点"));

       }

       else if(state & ODS_DISABLED)//按钮被禁用

       {

              pDC->TextOut(2,2,_T("禁用"));

       }

       pDC->RestoreDC(nSaveDC);//恢复设备上下文

没有想象中复杂吧!DrawItem完成之后我们用前面的方法在OnCreate函数中把这个按钮创建出来,检查一下效果?也许不会出现理想中的效果,那是因为你可能忘了为按钮加上BS_OWNERDRAW属性。添加BS_OWNERDRAW属性的方法有很多,我们在创建按钮的时候添加也是可以的,但是通用的做法是在虚函数PreSubclassWindow中添加,我们可以添加虚函数,然后加入如下代码:

ModifyStyle(0,BS_OWNERDRAW);

4、好了,现在我们已经实现了一个我们自绘的按钮了,至少我们知道了实现的方法。下面我们来为按钮添加鼠标移入、离开时的效果。添加方法CXPButton的作者已经写的十分详细了,为了方便大家理解,这里我还是把它用我们的最简洁语句的方法再实现一下。

首先添加鼠标移动消息添加处理函数:OnMouseMove。这里大家要特别注意,鼠标移动到按钮上和离开按钮的消息是不能用普通方法(ClassWizard)添加的,我们采用的办法是通过OnMouseMove函数,根据鼠标移动情况发送上述两个消息,这两个消息的名字为WM_MOUSELEAVE和WM_MOUSEHOVER。然后我们在手工添加消息响应,添加方法同前为按钮手工添加消息响应的方法类似。为了便于阅读,在此将此方法在重新说一下,大概分3个步骤:

(1)在MESSAGE_MAP中添加响应函数。

ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave)

ON_MESSAGE(WM_MOUSEHOVER, OnMouseHover)

(2)在头文件中添加函数定义。

//消息响应函数定义

afx_msg LRESULT OnMouseLeave(WPARAM wParam, LPARAM lParam);

afx_msg LRESULT OnMouseHover(WPARAM wParam, LPARAM lParam);

(3)正常编写消息响应函数的实现部分。

如何发送上述消息呢?这里就要用到一个函数_TrackMouseEvent(),注意前面的下划线不要忘了。此函数的功能是当在指定时间内鼠标指针离开或盘旋在一个窗口上时,用此函数发送消息。由于此函数使用方便,用途广泛,特别将此函数的有关说明粘贴于此(下面的内容均为百度百科转载)。

  函数原型:BOOL TrackMouseEvent(LPTRACKMOUSEEVENT lpEventTrack);

  参数:

  lpEventTrack;指向结构TRACKMOUSEEVENT的指针。

  返回值:如果函数调用成功,返回非零值;如果函数调用失败,返回值是零。若想获得更多的错误信息,请调用GetLastError函数。

  此函数能发送如下消息:

  WM_MOUSEHOVER:在上次调用TrackMouseEvent指定的时间里,鼠标盘旋在窗口的客户区。当此消息产生时,盘旋跟踪停止。如果需要进一步的鼠标盘旋跟踪,应用程序应当再次调用TrackMouseEvent。

  WM_MOUSELEAVE:鼠标离开上次调用TrackMouseEvent时指定的窗口客户区。当此消息产生时,所有由TrackMouseEvent要求的跟踪都被取消。当鼠标再次进入窗口,并且要求进一步的鼠标盘旋跟踪时,应用程序必须调用TrackMouseEvent。

  该处使用自定义消息发送形式:

  ON_MESSAGE(WM_MOUSELEAVE,OnMouseLeave)

  ON_MESSAGE(WM_MOUSEHOVER,OnMouseHover)

  才能使用

  注意:WM_MOUSELEAVE响应函数为 void OnMouseLeave(WPARAM wp,LPARAM lp),若写成 void OnMouseLeave(),Release下运行会出现错误。

  备注:当鼠标指针在指定时间内停留在指定矩形内,就被认为是处于盘旋状态。调用函数SystemParameterslnfo并使用SPI_GETMOUSEAOVERWIDTH,SPI_GETMOUSEHOVERAEIGHT和SFI_GETMOOSEAOVERTIME值来取得矩形的大小和时间。

  速查:Windows NT 4.0及以上版本;Windows 98及以上版本;Windows CE:1.0及以上版本;头文件:winuser.h;输入库:user32.lib。

  ::TrackMouseEvent 要使用它,请在源码中包含#define _WIN32_WINNT 0x0400

  TRACKMOUSEEVENT 结构体

  TRACKMOUSEEVENT结构体在TrackMouseEvent函数中用到。

  其定义如下:

  typedef struct tagTRACKMOUSEEVENT {

  DWORD cbSize;

  DWORD dwFlags;

  HWND hwndTrack;

  DWORD dwHoverTime;

  } TRACKMOUSEEVENT, *LPTRACKMOUSEEVENT;

  几个成员的含义:

  cbSize 定义TRACKMOUSEEVENT结构体的大小;

  dwFlags 定义服务请求,可以是下列值的组合:TME_CANCEL 取消前一次的跟踪请求;使用该项时必须指定要取消跟踪的类型,例如要取消hover跟踪,就必须同时传送TME_CANCEL和 TME_HOVER(TME_CANCEL|TME_HOVER)。TME_HOVER hover通知。可以发送WM_MOUSEHOVER消息。如果在hover跟踪处于激活状态时再次请求hover跟踪的话,hover的定时器将被重 置。TME_LEAVE 鼠标离开。发送TME_MOUSELEAVE消息。当鼠标不在指定的窗口或区域上时,将立即产生一个leave通知,不再做任何跟踪。TME_QUERY 这一项不是作为跟踪请求的。选中这一项时,当结构体被传送给TrackMouseEvent函数时,即产生当前跟踪。唯一不同的是返回的消耗时间,是真实 的消耗时间而不是HOVER_DEFAULT,即使之前TrackMouseEvent函数所请求的是HOVER_DEFAULT。

  hwndTrack 待跟踪窗口的句柄

  dwHoverTime 定义hover事件的耗尽时间(如果在dwFlags中有定义TME_HOVER的话),单位毫秒。 可以使用HOVER_DEFAULT来使用系统默认的hover事件耗尽时间。系统默认的hover事件耗尽时间为菜单下拉时间,即400毫秒。可以调用SystemParameterInfo函数并使用 SPI_GETMOUSEHOVERTIME来获取默认的hover耗尽时间。默认的hover矩形区与双击区一致。可以调用 SystemParameterInfo并使用SPI_GETMOUSEHOVERWIDTH和SPI_GETMOUSEHOVERHEIGHT来获取鼠 标在上面停留可以产生WM_MOUSEHOVER的区域。

还有一个小问题。前面一直都说的是TrackMouseEvent函数,但实在上,在程序中,如果直接使用 TrackMouseEvent(&tme);时,会出现诸如“'TrackMouseEvent' : undeclared identifier”的错误。但如果你使用_TrackMouseEvent(&tme);就不会有错误了。为什么呢?

  这两个函数的区别,TrackMouseEvent函数的头文件是winuser.h,对应的库文件为user32.lib,而 _TrackMouseEvent函数则在commctrl.h里定义,而由COMCTRL32.DLL导出。

  使用 TrackMouseEvent函数必须用extern "C"导入此函数。如下:

  extern "C" WINUSERAPI BOOL WINAPI TrackMouseEvent (LPTRACKMOUSEEVENT

  lpEventTrack);

  NOTE:由上面说到的TrackMouseEvent函数的特点,通常都在OnMouseMove函数里添加该函数。另外,可以与 SetCapture函数联合使用,即使在鼠标移出窗口区时也能够响应。

下面我们就在OnMouseMove中使用这个函数来发送鼠标进入和离开按钮的消息吧。代码如下:

       TRACKMOUSEEVENT tme;//定义TRACKMOUSEEVENT结构体

       tme.cbSize = sizeof(tme);//获取结构体大小

       tme.hwndTrack = m_hWnd;//为句柄赋值为当前按钮窗口

       tme.dwFlags = TME_LEAVE | TME_HOVER;//发送进入和离开消息

       tme.dwHoverTime = 1;//鼠标在按钮上盘旋1毫秒时即发送消息

       _TrackMouseEvent(&tme);//发送消息

现在我们每当鼠标进入或者离开按钮时都会发出响应的消息了,下面我们就可以用前文的方法为这两个消息添加消息响应,具体添加方法请参照前文和例子,这里就不写了。

为了表示鼠标进入和离开按钮的状态,我们为CNewButton添加了一个成员变量m_bOver,初始值为假,鼠标在按钮之上时为真,鼠标离开按钮为假,我们可以在构造函数中为其赋初值。然后分别在WM_MOUSELEAVE和WM_MOUSEHOVER消息响应函数中为其赋上相应的值。别忘了用InvalidateRect函数通知窗口更新。

现在我们可以在DrawItem函数中进行判断了。添加代码如下:

//判断鼠标移入、移出,鼠标离开时按钮恢复原状态

       if(m_bOver)

       {

              //按钮在鼠标之上

              pDC->TextOut(2,2,_T("移入"));

       }

至于如何将按钮绘制的更加漂亮,并非本文所说之范围,另外这个例子还存在许多bug,例如添加鼠标移入状态之后,鼠标按下就无法显示了,不过本文只是为了让大家理解DrawItem的使用方法而已,相反,我认为这种带有缺陷的例子,更能反映出程序的本质(实际只是偷懒而已!),因此本人也不准备将这个例子做的很完善了。只要大家具备足够的绘图水平,相信一定会绘制出漂亮的按钮来。

(二)为按钮添加声音

当然为了按钮表现的更加完美,我们还要为其添上声音,当然这实在太简单了,不过是在相应的消息响应函数中加上如下代码:

PlaySound((LPCTSTR)IDR_WAVE1, AfxGetInstanceHandle(), SND_RESOURCE | SND_ASYNC);

至于PlaySound函数的用法,这里就不多说了,注意包含头文件:mmsystem.h,并连接上winmm.lib库就可以了,方法大家自己去学吧。

四、特殊应用

通过前面的文章,大家对于按钮已经有了比较深的了解了,而且也可以设计出具有自己个性风格的按钮来了,但是大家一定也发现了,前文介绍的按钮永远是一成不变的矩形,相信大家一定非常不满意,下面我就为大家介绍一下如何改变按钮的形状。

改变按钮的形状主要要用到一个类:CRgn,这个类可以为窗口设定一个有效区域,就好像是可以裁剪窗口一样,要详细说明这个类的用法,可能需要很长的内容,这里我只是简单的进行一下描述和比喻,其实使用这个类和我们制作石膏像非常相似,制作石膏像的过程有如下三个步骤:

1、准备材料。

2、制作模子。

3、把石膏扣在上面,多余的去掉。

使用CRgn也需要三个步骤:

1、生成CRgn对象。(准备好制造石膏像模子的材料)

CRgn rgn;

2、生成CRgn区域。(制造模子)

这个过程比较复杂,本文就不多说了,这里只用到CRgn的成员函数CreateEllipticRgn来制作一个椭圆按钮就可以了。当然我们可以深入学习一下CRgn的使用方法,制作出超级个性复杂的按钮,但是这并非本文内容,因此就不多说了。

3、把窗口扣上去就完成了。

这一步非常简单,只要知道一个函数就可以了:SetWindowRgn。

多说一句,实际上,多数改变窗口外形的操作都是运用上面所述的方法实现的,熟练掌握上面的方法是十分必要的。

现在我们可以来理清一下制作不规则按钮的思路了,简单的说就是把按钮作为一个窗口,然后扣到我们创造的CRgn区域中,就可以了。事实真的就是那么容易。这里我们一定要注意区分一下在对话框中(即使用对话框编辑器预先制作的按钮)和我们在视图中动态创建按钮这两种不同的情况,他们是不同的。

(一)首先我们来看一下在对话框中如何创建不规则按钮,当然前文关于CXPButton的补遗中有所描述,这里我们还是将它进一步精简,方便大家理解。为了说明方便,本文也使用了CXPButton类补遗中的说明方法,先把对话框涂上蓝颜色。这个操作在对话框中消息处理函数OnEraseBkgnd中进行。

现在我们可以开始在对话框中创建不规则按钮了,步骤如下:

1、首先我们在添加一个新的类:CRgnDlgBtn

2、使用对话框编辑器添加一个按钮。

3、为CRgnDlgBtn类添加虚函数DrawItem、PreSubclassWindow,以及OnEraseBkgnd消息处理函数。

4、DrawItem用来根据不同按钮状态绘图,这里我们将前面CNewButton中的代码粘贴过来,为了简单,去掉了鼠标经过按钮时的绘图。

5、在PreSubclassWindow函数中为按钮自绘属性,然后开始设定区域,代码如下:

//获取按钮控件举行区域大小

       CRect rc;

       GetClientRect(&rc);

       //定义区域

       CRgn rgn;

       //制作椭圆形的按钮区域,也就是模子

       rgn.CreateEllipticRgnIndirect(&rc);

       //把窗口扣到区域中

       SetWindowRgn((HRGN)rgn,TRUE);

6、这时按钮实际上已经被设定了区域,使用鼠标点击按钮的四个角,已经不会发挥作用了。之所以会出现白色的边角,是因为按钮的OnEraseBkgnd消息处理函数还在发挥作用,为按钮绘制底色。我们只需要响应按钮的OnEraseBkgnd消息处理函数,不调用CButton::OnEraseBkgnd(pDC),直接返回TRUE,就可以让OnEraseBkgnd消息处理函数失去作用,从而不再为按钮绘制底色了。

为对话框编辑器制作的按钮设定区域相对比较简单,这个例子和CXPButton的补遗中所描述的是一样的,大家可以结合着来看。相比较,在视图中(或者说动态)创建不规则按钮就相对比较麻烦了,下面我们就来在视图中动态创建一个不不规则按钮吧。

(二)在视图中动态创建不规则按钮。

有人会觉得,在视图中创建不规则按钮很简单,只要为上面建立的CRgnDlgBtn类生成一个对象,然后调用Create创建就行了。但是如果你那样做了,你会发现根本无法实现,甚至你的整个窗口都将失效,那么应该怎样做呢?此时我们不能将设置区域的操作放在PreSubclassWindow函数中,而是应该放在Create创建按钮之后,因为此时按钮才有了自己的实例。步骤和前面非常相似,过程如下:

1、再新建一个类CrgnViewBtn;

2、为CRgnViewBtn类添加虚函数DrawItem、PreSubclassWindow,以及OnEraseBkgnd消息处理函数。代码和前面基本一样,只是要把PreSubclassWindow代码中的有关设置区域的内容移动到CButtonView类中OnCreate()中,放在创建不规则按钮之后。OnCreate函数中添加代码如下:

       m_RVBtn.Create(NULL,WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,CRect(210,130,390,180),

              this,IDC_BUTTON_RGNVIEW);

       //获取按钮大小

       CRect rc;

       m_RVBtn.GetClientRect(&rc);

       //制作区域

       CRgn rgn;

       rgn.CreateEllipticRgnIndirect(&rc);

       m_RVBtn.SetWindowRgn((HRGN)rgn,TRUE);

       rgn.DeleteObject();

好了,现在编译一下吧,椭圆按钮已经出现了?

结束语:本文将有关按钮的大部分操作都做了详细的说明,通过前文,我们可以制作出任意形状样式的按钮,为我们的程序增光添彩。当然,有关按钮的操作还远不止上述内容,由于本人水平十分有限,就不能再过多的记述了。本人也是个初学者,写本文无非是为了激发自己的学习,不足之处一定不少,真心希望诸位老师朋友们批评指正。