MFC入门到精练
使用Windows标准控件
我们在前面曾提到过,控件是一些行为标准化了的窗口,一般用于对话框或其它窗口中充当与用户交互的元素。在Visual C++中,可以使用的控件分成三类:
(1) Windows标准控件
Windows标准控件由Windows操作系统提供,在Windows 95中还提供了一些新增的控件。所有这些控件对象都是可编程的,我们可以使用VisualC++提供的对话框编辑器把它们添加到对话框中。Microsoft基础类库(MFC)提供了封装这些控件的类,它们列于表6.1。
表6.1 Windows标准控件
控件 | MFC类 | 描述 |
动画 | CAnimateCtrl | 显示连续的AVI视频剪辑 |
按钮 | CButton | 用来产生某种行为的按钮,以及复选框、单选钮和组框 |
组合框 | CComboBox | 编辑框和列表框的组合 |
编辑框 | CEdit | 用于键入文本 |
标题头 | CHeaderCtrl | 位于某一行文本之上的按钮,可用来控制显示文件的宽度 |
热键 | CHotKeyCtrl | 用于通过按下某一组合键来很快的执行某些常用的操作 |
图象列表 | CImageList | 一系列图象(典型情况下是一系列图标或位图)的集合。图象列表本身不是一种控件,它常常是和其它控件一起工作,为其它控件提供所用的图象列表 |
列表 | CListCtrl | 显示文本及其图标列表的窗口 |
列表框 | CListBox | 包括一系列字符串的列表 |
进度 | CProgressCtrl | 用于在一较长操作中提示用户所完成的进度 |
多格式文本编辑 | CRichEditCtrl | 提供可设置字符和段落格式的文本编辑的窗口 |
滚动条 | CScrollBar | 为对话框提供控件形式的滚动条 |
滑块 | CSliderCtrl | 包括一个有可选标记的滑块的窗口 |
旋转按钮 | CSpinButtonCtrl | 提供一对可用于增减某个值的箭头 |
静态文本 | CStatic | 常用于为其它控件提供标签 |
状态条 | CStatusBarCtrl | 用于显示状态信息的窗口,同MFC类CStatusBar类似 |
续表6.1
控件 | MFC类 | 描述 |
选项卡 | CTabCtrl | 在选项卡对话框或属性页中提供具有类似笔记本中使用的分隔标签的外观的选项卡 |
工具条 | CToolBarCtrl | 具有一系列命令生成按钮的窗口,同MFC类CToolBar类似 |
工具提示 | CToolTipCtrl | 一个小的弹出式窗口,用于提供对工具条按钮或其它控件功能的简单描述 |
树 | CTreeCtrl | 用于显示一系列的项的继承结构 |
前面提到过,在MFC中,类CWnd是所有窗口类的基类,很自然的,它也是所有控件类的基类。Windows标准控件在以下环境下提供:
- Windows 95
- Windows NT 3.51及以后版本
- Win32s 1.3
- 注意:
- Visual C++ 4.2及以后版本不再支持Win32s。
(2) ActiveX控件
ActiveX控件可用于对话框中,也可用于HTML文档中。这种控件过去被称为OLE控件。本书将在专门的章节中来讲述关于ActiveX控件的知识。这里仅指出ActiveX控件使用了与标准控件完全不同的接口和实现方法。
(3) 其它MFC控件类
除了Windows标准控件和自己编写的或者来自于第三方软件开发商的ActiveX控件以外,MFC还提供了另外三种控件,它们由下面的三个类进行封装:
- 类CBitmapButton用于创建以位图作为标签的按钮,位图按钮最多可以包括四个位图图片,分别代表按钮的四种不同状态。
- 类CCheckListBox用于创建选择列表框,这种列表框中的每一项前面有一个复选框,以决定该项是否被选中。
- 类CDragListBox用于创建一种特殊的列表框,这种列表框允许用户移动列表项。
在本章我们仅讲述第一类控件,即Windows标准控件。所涉及的内容包括各个控件的使用及相应的技巧。
第一节 使用对话框编辑器和ClassWizard
对于大多数Windows标准控件,我们一般都使用对话框编辑器来将它们添加到对话框中。
图6. 1 在ResourceView中选择对话框
IDD_DIALOGDEMO_DIALOG
图6. 2 控件的Properties对话框
图6. 3 对话框编辑器的Controls工具窗口
在下面的过程中,我们将一个编辑框控件添加到在第四章创建的基于对话框的MFC框架应用程序的主对话框窗口中。
1. 首先,在Workspace窗口的ResourceView选项内双击DialogDemo resources\Dialog节点下的IDD_DIALOGDEMO_DIALOG图标。上面的操作如图所示。
2. 用鼠标选中标有“要做……”的静态文本控件。右击鼠标,从上下文菜单中选择Properties,打开如图6.2所示的对话框,在Caption文本框中输入新的控件文本:“在下面的文本框中输入一些字符”,然后将静态文本控件拖动到对话框的左上角。
3. 从Controls工具窗口(如图6.3所示,如果在你的资源编辑器中看不到该工具窗口,可以在工具条上右击鼠标,从上下文菜单中选择Controls)中选择编辑控件图标,在对话框中绘制一个编辑框控件,如图6.4所示。
在该编辑框控件的Properties窗口的General选项卡中输入其ID为IDC_EDIT。然后在Styles选项卡下将Multiline复选框划上勾,并消除Auto HScroll复选框前的勾。
4. 右击该编辑框控件,从上下文菜单中选择ClassWizard命令,打开ClassWizard对话框,该对话框看起来如图6.5所示。
图6. 4 向对话框中添加一个编辑框控件
图6. 5 ClassWizard对话框
单击Member Variables选项卡,确信在Project处选择了DialogDemo,在Class name处选择了CDialogDemoDlg。现在我们为刚才添加的编辑框控件IDC_EDIT添加一个数据映射入口。在Control IDs处选择IDC_EDIT,单击右边的Add Viable按钮。打开如图6.6所示的对话框。
在Membervariable name处链接变量名m_strEdit (这里m表示该变量为类CDialogDemoDlg的一个成员变量,str表明其类型为字符串,即类CString),在Category下拉列表中选择Value (另一种选择是Control,两种选择的不同将在后面的内容中讲述),在Variable type下拉列表中选择CString (还有其它很多数据类型可供选择,但由于这里编辑框中的内容为一字符串,因此CString是最恰当的选择)。单击OK关闭对话框。
图6. 6 为控件映射添加成员变量
5. 检查一下现在的ClassWizard对话框(图6.7)与图6.5相比有何不同。在图6.7所示的对话框中下方的Maximum characters文本框中输入50。由字面意思可以很容易猜出其含义,即将编辑框IDC_EDIT中可能的最长字符串的大小限制为50。单击OK关闭对话框。
图6. 7 使用ClassWizard设置数据验证方案
6. 从Workspace窗口的ClassView中双击类CDialogDemoDlg的OnInitDialog成员函数,使用下面的代码来代替位于语句
return TRUE;
前的// TODO注释:
m_strEdit="您好! 请在这里输入一些字符串。";
UpdateData(FALSE);
7. 在ClassView中双击类CDialogDemoApp的InitInstance成员函数,使用下面的代码来找替位于选择支
if (nResponse == IDOK)
下的//TODO注释:
AfxMessageBox(dlg.m_strEdit);
然后将同一成员函数中的下面的代码行删掉(或注释掉):
m_pMainWnd = &dlg;
8. 编译并运行该应用程序。显示如图6.8所示的对话框。
图6. 8 示例程序DialogDemo的运行结果
在图6.8所示的文本框中输入一些字符,单击“确定”。随即弹出如图6.9所示的消息框。该消息框复述了用户在图6.8所示的对话框中的输入。我们还发现,在图6.8所示的对话框中,当输入字符串达到一定的长度之后,我们不可以再输入更多的字符,这是我们在前面设置了Maximum characters为50的结果。
图6. 9 以消息框的形式反馈输入的字符串
下面我们来看在上面的步骤中都完成了什么。首先我们使用资源编辑器向对话框模板中添加这些标准控件,这一步的概念很清晰,因此并不难理解。
然后,我们打开了所绘制的编辑框的Properties (属性)对话框。先将其控件ID设置为IDC_EDIT。这时如果打开头文件Resource.h,就会发现宏IDC_EDIT被定义为常量1001。不过,事实上在很多情况下我们并不需要关心每一控件的ID的具体值,而只需要记住相应的助记符。对于这里的编辑框控件,我们只需要记住IDC_EDIT即可,而不需要关心它等于1001。接着,我们在Styles选项卡中设置了Multiline属性,同时清除了Auto HScroll属性,两者共同作用使用得编辑框IDC_EDIT支持多行文本,并且如文本行的长度超过编辑框宽度时自动回行。
下面的步骤是最重要的一步,我们动用了功能强大的工具ClassWizard。首先,我们将编辑框与一个CString对象相关联,这使用了一种被称为Dialog Data Exchange (DDX)的机制。在这种机制中,我们先在处理函数OnInitDialog或对话框类的构造函数中对对话框对象的成员变量进行初始化,在对话框显示之前,框架的DDX机制将成员变量的值传递给对话框中的控件。这个过程在成员函数DoModal或Create被调用的过程中发生。类CDialog中对OnInitDialog成员函数的默认实现调用了类CWnd成员函数UpdateData来初始化对话框中的控件。这时我们就可以看到前面的第6步还可在具有下面的几种变通方案:
1. 将代码行
m_strEdit="您好! 请在这里输入一些字符串。";
移到对基类的OnInitDialog成员函数的调用之前,即位于下面的代码之前:
CDialog::OnInitDialog();
2. 将代码
m_strEdit="您好! 请在这里输入一些字符串。";
移到类CDialogDemoDlg的构造函数中。
对于上面的两种方法,与前面第6步中使用的方法相比,我们没有必要调用类CWnd的成员函数UpdateData。因为该函数在类CDialog的成员函数OnInitDialog中将被调用。
这三种方法之间并没有明确的优劣之分,在很多情况下,它们分别适用于不同的场合。
这里我们说一下成员函数UpdateData。该函数带有一个布尔类型的参数,如果该参数为FALSE,函数UpdateData将成员变量的值传递给对话框的变量;而如果该参数为TRUE,函数UpdateData将进行相反的过程。
如果用户单击了对话框中ID为IDOK的按钮,或者以TRUE为参数调用函数UpdateData,DDX机制从控件中将值传递到成员变量,同时对话框数据验证(dialog data validation,DDV)机制根据设定的验证规则验证所有数据项。
在数据交换的过程中,成员函数UpdateData先创建一个CDataExchange对象,然后调用对话框对类CDialog成员函数DoDataExchange的重载版本。该CDataExchange对象将作为成员函数DoDataExchange的一个参数,该参数定义了数据交换的上下文。
在DoDataExchange中,我们为每一个数据成员指定了一个对DDX函数的调用。每一个函数定义了基于由成员函数UpdateData所提供的CDataExchange参数所确定的上下文而进行的双向数据交换。
下面的代码摘自实现文件DialogDemo.cpp中对函数DoDataExchange的定义:
void CDialogDemoDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CDialogDemoDlg)
DDX_Text(pDX, IDC_EDIT, m_strEdit);
DDV_MaxChars(pDX, m_strEdit, 50);
//}}AFX_DATA_MAP
}
在两行注释//{{AFX_DATA_MAP和//}}AFX_DATA_MAP之间的代码部分称作数据映射。函数DDX_Text使用CString对象m_strEdit与ID为IDC_EDIT的编辑框控件相关联。函数DDV_MaxChars设置与编辑框控件IDC_EDIT相关联CString对象m_strEdit的最大长度为50。
需要注意的是,如果用户在模式对话框中单击了“取消”(Cancel)按钮,DoModal函数将返回值IDCANCEL,在这种情况下,在对话框和对话框对象之前的数据交换不会发生。
由于这个原因,如果DoModal函数返回了值IDOK,我们可以使用下面的代码来复述用户在对话框中所输入的值:
AfxMessageBox(dlg.m_strEdit);
- 注意:
- 在前面的第7步中有一个乍看起来有一些费解的过程,这就是我们为什么要将下面的代码从函数OnInitDialog中删除:
- m_pMainWnd = &dlg;
这基于下面的一个事实:
类CWinThread的数据成员m_pMainWnd有一个有用的特征,如果由该成员所引用的窗口被关闭的话,MFC库将自动的终止CWinThread对象所代表的线程。这样,如果我们将指向dlg的指针赋予了成员变量m_pMainWnd,那么,无论我们单击了“确认”还是“取消”,应用程序的主线程都将被自动终止,之后的代码当然不会得到执行。而在本示例中,我们希望在对话框被关闭后程序继续运行(即弹出一个消息重述用户所输入的内容),因此不应该将dlg对象的指针赋予成员变量m_pMainWnd,从而需要将前面的代码从函数OnInitDialog中删除。
第二节 所有窗口类的基类:CWnd
在MFC中类CWnd是一个很重要的类,它封装了Windows窗口句柄HWND。在Windows编程中,窗口句柄唯一的标识了一个窗口。然而,尽管类CWnd的对象和窗口句柄之间有着如此紧密的联系,但两者并不是等同的概念。CWnd对象通过类CWnd的构造函数和析构函数创建和消毁,而Windows窗口是Windows内部的一种数据结构,在类CWnd中,它通过Create成员函数创建,通过其析构函数消毁。除此之外,成员函数DestroyWindow可以消毁Windows窗口,而不需要消毁CWnd对象。
传统的Windows应用程序中,消息是通过一个称作窗口过程(window procedure,通常具有WndProc之类的函数名)的回调函数来处理的。这种方式在MFC中仍然使用,但为CWnd类及其消息映射所隐藏。在类CWnd中,Windows通知消息会被自动的通过消息映射传递到类CWnd中合适的OnMessage成员函数(这里OnMessage是指这些函数具有的以On为前缀的函数名,如OnPaint和前面接触到的OnInitDialog等)进行处理。通常我们都在类CWnd的派生类中重载需要处理的特定消息所对应的OnMessage成员函数。除了直接从CWnd派生新的窗口类以外,我们更倾向于从MFC中定义的其它类,如CFrameWnd、CMIDFrameWnd、CMDIChileWnd、CView和CDialog以及CButton之类的控件类派生新的窗口类。在MFC中定义的这些类本身也是从CWnd派生的。
通常我们使用两个步骤来创建一个窗口:首先,调用类CWnd的构造函数来构造一个CWnd对象,然后调用其成员函数Create来创建窗口并将该窗口与所创建的CWnd对象相关联。
当用户终止该窗口时,消毁与之相关联的CWnd对象,或者调用CWnd对象的成员函数DestroyWindow删除窗口并消毁其数据结构。
大多数以HWND为参数的Win32 API函数都已作为类CWnd的成员函数进行了封装,事实上,很多时候我们通过类Wnd的派生类调用的成员函数并不是由派生类本身所提供的,而是在类CWnd中进行定义的。下面我们分类给出在CWnd类中定义的各类成员函数。完整而详尽的说明每一个成员函数在本书中是不现实的,这里我们仅给出对每一个成员函数的简短说明,以便读者在编程时能够很快的查找到所需的函数,这时再去查找有关于该函数的详细的说明就不是一件困难的事了。
1. 类CWnd的数据成员(表6.2):
表6. 2 类CWnd的数据成员
数据成员 | 描述 |
m_hWnd | 与该CWnd对象相关联的Windows窗口句柄(HWND) |
2. 构造函数/析构函数(表6.3):
表6. 3 类CWnd的构造函数和析构函数
成员函数 | 获得图标句柄 |
SetIcon | 设置句柄为一指定图标 |
GetWindowContextHelpId | 获得帮助上下文标识符 |
SetWindowContextHelpId | 设置帮助上下文标识符 |
ModifyStyle | 修改当前窗口样式 |
ModifyStyleEx | 修改当前窗口的扩展样式 |
5. 窗口大小和位置函数(表6.6):
表6. 6 类CWnd的窗口大小和位置成员函数
成员函数 | 描述 |
GetWindowPlacement | 获得显示状态和窗口的正常、最小化和最大化位置 |
SetWindowPlacement | 设置显示状态和窗口的正常、最小化和最大化位置 |
GetWindowRgn | 获得窗口的窗口区域的拷贝 |
SetWindowRgn | 设置窗口区域 |
IsIconic | 判断窗口是否被最小化(图标化) |
IsZoomed | 判断窗口是否被最大化 |
MoveWindow | 改变窗口的位置和度量 |
SetWindowPos | 改变子窗口、弹出式窗口或顶层窗口的大小、位置和顺序 |
ArrangeIconicWindows | 排列所有最小化的子窗口 |
BringWindowToTop | 将CWnd对象放到覆盖窗口栈的顶部 |
GetWindowRect | 获得CWnd对象的屏幕坐标 |
GetClientRect | 获得CWnd对象客户区的度量 |
6. 窗口访问函数:
表6. 7 类CWnd的窗口访问成员函数
成员函数 | 描述 |
ChildWindowFromPoint | 判断包含指定点的子窗口 |
FindWindow | 返回由其窗口名称和窗口类标识的窗口的句柄 |
GetNextWindow | 返回窗口管理器列表中的下一个(或上一个)窗口 |
GetOwner | 返回指向CWnd对象的所有者的指针 |
续表6.7
成员函数 | 描述 |
SetOwner | 改变CWnd对象的所有者 |
GetTopWindow | 返回属于CWnd对象的第一个子窗口 |
GetWindow | 返回与当前窗口有指定关系的窗口 |
GetLastActivePopup | 判断由CWnd对象所有的弹出窗口中最近激活的窗口 |
IsChild | 判断CWnd对象是否为一个子窗口 |
GetParent | 如果存在的话,获得CWnd对象的父窗口 |
GetSafeOwner | 获得给定窗口的安全的所有者 |
SetParent | 改变父窗口 |
WindowFromPoint | 标识包括给定点的窗口 |
GetDlgItem | 从指定的对话框获得标准符为指定ID的控件 |
GetDlgCtrlID | 如果CWnd为一子窗口,返回其ID值 |
SetDlgCtrlID | 当CWnd对象为一子窗口(不仅指对话框中的控件)时,为其指定控件ID或窗口ID |
GetDescendantWindow | 检查所有下级窗口(descendant window)并返回具有指定ID的窗口 |
GetParentFrame | 获得CWnd对象的父框架窗口 |
SendMessageToDescendants | 发送一条消息到窗口的所有下级窗口 |
GetTopLevelParent | 获得窗口的顶层父窗口 |
GetTopLevelOwner | 获得窗口的顶层所有者窗口 |
GetParentOwner | 返回指向子窗口的父窗口的指针 |
GetTopLevelFrame | 获得窗口的顶层框架窗口 |
UpdateDialogControls | 用来更新对话框按钮或其它控件的状态 |
UpdateData | 初始化对话框或从对话框中获取数据 |
CenterWindow | 相对于父窗口使窗口居中 |
7. 更新和绘制函数(表6.8)
表6. 8 类CWnd的更新和绘制函数
成员函数 | 描述 |
BeginPaint | 为重绘操作准备CWnd对象 |
EndPaint | 标记重绘操作的结束 |
续表6.8
成员函数 | 描述 |
| 在指定的设备上下文绘制当前窗口 |
PrintClient | 在指定的设备上下文(通常是打印机)绘制所有窗口 |
LockWindowUpdate | 禁止或重新允许绘制指定的窗口 |
UnlockWindowUpdate | 解除CWnd::LockWindowUpdate对窗口的锁定 |
GetDC | 获得客户区的显示上下文 |
GetDCEx | 获得客户区的显示上下文,并在绘制过程中允许裁剪 |
RedrawWindow | 在客户区中更新指定的矩形或区域 |
GetWindowDC | 获得整个窗口的显示上下文,包括标题条,菜单和滚动条 |
ReleaseDC | 释放客户区或窗口设备上下文,并使其可为其它程序所使用 |
UpdateWindow | 更新客户区 |
SetRedraw | 决定在CWnd对象中的改变是否被重绘 |
GetUpdateRect | 获得完全覆盖CWnd对象的更新区域的最小矩形坐标 |
GetUpdateRgn | 获得CWnd对象的更新区域 |
Invalidate | 使用整个客户区无效 |
InvalidateRect | 通过将给定矩形添加到当前更新区域来使包括在给定矩形内的客户区无效 |
InvalidateRgn | 通过将给定区域添加到当前更新区域来使包括在给定区域内的客户区无效 |
ValidateRect | 通过将给定矩形从当前更新区域中移出来使包括在给定矩形内的客户区有效 |
ValidateRgn | 通过将给定区域从当前更新区域中移出来使包括在给定区域内的窗户区有效 |
ShowWindow | 显示或隐藏窗口 |
IsWindowVisible | 判断窗口是否可见 |
ShowOwnedPopups | 显示或隐藏窗口拥有的所有弹出式窗口 |
EnableScrollBar | 允许或禁止滚动条上的一个或两个箭头 |
8. 坐标映射函数(表6.9)
表6. 9 类CWnd的坐标映射函数
成员函数 | 描述 |
MapWindowPoints | 从CWnd对象的坐标空间映射一系列点到另一窗口的坐标空间 |
续表6.9
成员函数 | 描述 |
ClientToScreen | 转换给定点的客户坐标或显示矩形到屏幕坐标 |
ScreenToClient | 转换给定点的屏幕坐标或显示矩形到客户坐标 |
9. 窗口文本函数(表6.10)
表6. 10 类CWnd的窗口文本函数
成员函数 | 描述 |
SetWindowText | 设置窗口文本或标题条(如果有的话)为指定文本 |
GetWindowText | 获得窗口文本或标题条 |
GetWindowTextLength | 返回窗口文本或标题条的长度 |
SetFont | 设置当前字体 |
GetFont | 获得当前字体 |
10. 滚动函数(表6.11)
表6. 11 类CWnd的滚动成员函数
成员函数 | 描述 |
GetScrollPos | 获得滚动框的当前位置 |
GetScrollRange | 拷贝给定滚动框中滚动块的当前最大和最小位置 |
ScrollWindow | 滚动客户区的内容 |
ScrollWindowEx | 滚动客户区内容。与ScrollWindowEx类似,但具有一些附加特性 |
GetScrollInfo | 获得关于某一滚动条的由SCROLLINFO结构维护的信息 |
GetScrollLimit | 获得滚动条的限制 |
SetScrollInfo | 设置关于滚动条的信息 |
SetScrollPos | 设置滚动条的当前位置,并在指定的情况下重绘滚动条以反映新的位置 |
SetScrollRange | 设置给定滚动条的最小和最大位置值 |
ShowScrollBar | 显示或隐藏滚动条 |
EnableScrollBarCtrl | 允许或禁止兄弟滚动条控件 |
GetScrollBarCtrl | 返回兄弟滚动条控件 |
RepositionBars | 在客户区中对控件条重定位 |
11. 拖放函数(表6.12)
表6. 12 类CWnd的拖放成员函数
成员函数 | 描述 |
DragAcceptFiles | 使窗口可以接受文件拖放 |
12. 插入符函数(表6.13)
表6. 13 类CWnd的插入符成员函数
成员函数 | 描述 |
CreateCaret | 新的插入符形状,并获得该插入符的所有权 |
CreateSolidCaret | 创建方块形状的插入符,并获得该插入符的所有权 |
CreateGrayCaret | 创建变灰方块形状的插入符,并获得该插入符的所有权 |
GetCaretPos | 获得插入符当前位置的客户坐标 |
SetCaretPos | 移动插入符到指定的位置 |
HideCaret | 隐藏插入符 |
ShowCaret | 在插入符的当前位置显示插入符 |
13. 对话框项函数(表6.14)“
表6. 14 类CWnd的对话框项函数
成员函数 | 描述 |
CheckDlgButton | 在按钮控件前放置选中标记或清除按钮控件的选中标记 |
CheckRadioButton | 选中指定的单选钮并清除指定给中其它所有单选钮的选中标记 |
GetCheckedRadioButton | 返回一组按钮中当前选中单选钮的ID |
DlgDirList | 使用文件或目录列表填充一列表框 |
DlgDirListComboBox | 使用文件或目录列表填充一组合框的列表框 |
DlgDirSelect | 从一列表框中获得当前选择 |
DlgDirSelectComboBox | 从一组合框的列表框中获得当前选择 |
GetDlgItemInt | 将给定对话框中某一控件的文本转换为一个整数值 |
GetDlgItemText | 获得与某一控件相关联的标题或文本 |
GetNextDlgGroupItem | 查找同一组中的下一个(或前一个)控件 |
续表6.14
成员函数 | 描述 |
GetNextDlgTabItem | 查找在指定控件之前(或之后)的第一个具有WS_TABSTOP样式的控件 |
IsDlgButtonChecked | 判断一个按钮控件是否选中 |
IsDialogMessage | 判断一个给定消息是否影响非模态对话框,如果是,处理该消息 |
SendDlgItemMessage | 向指定的控件发送一条消息 |
SetDlgItemInt | 使某一控件的文本为某一给定整数值 |
SetDlgItemText | 设置指定对话框中某一控件的标题或文本 |
SubclassDlgItem | 将一个Windows控件与CWnd对象相关联,并使其通过CWnd对象的消息映射传递消息 |
ExecuteDlgInit | 初始化对话框资源 |
RunModalLoop | 为一模态窗口获取、翻译或发送消息 |
ContinueModal | 使一窗口继续保持模态 |
EndModalLoop | 结束某一窗口的模态状态 |
14. 数据绑定函数(表6.15):
表6. 15 类CWnd的数据绑定成员函数
成员函数 | 描述 |
BindDefaultProperty | 将调用对象的默认简单绑定属性(该属性在类型库中标记)绑定至相关联的数据源控件的游标 |
BindProperty | 将数据绑定控件的游标绑定属性绑定至数据源控件,并使用MFC绑定管理器注册绑定关系 |
GetDSCCursor | 获得指向由数据源控件的数据源、用户名、密码和SQL属性定义的底层游标的指针 |
15. 菜单函数(表6.16)
表6. 16 类CWnd的菜单成员函数
成员函数 | 描述 |
GetMenu | 获得指向指定菜单的指针 |
SetMenu | 设置菜单为指定的菜单 |
DrawMenuBar | 重绘菜单条 |
GetSystemMenu | 允许应用程序访问控制菜单以进行复制和修改 |
续表6.16
成员函数 | 描述 |
HiliteMenuItem | 加亮顶层菜单项或移去顶层菜单项的加亮显示 |
16. 工具提示函数(表6.17)
表6. 17 类CWnd的工具提示函数
成员函数 | 描述 |
EnableToolTip | 允许工具提示控件 |
CancelToolTip | 禁止工具提示控件 |
FilterToolTipMessage | 获得对话框中与某一控件相关联的标题或文本 |
OnToolHitTest | 判断一个点是否在指定工具的绑定矩形内,并获得该工具的信息 |
17. 计时器函数(表6.18)
表6. 18 类CWnd的计时器成员函数
成员函数 | 描述 |
SetTimer | 安装系统计时器,计时器触发时发送WM_TIMER消息 |
KillTimer | 消除系统计时器 |
18. 提示函数(表6.19)
表6. 19 类CWnd的提示成员函数
成员函数 | 描述 |
FlashWindow | 闪烁窗口一次 |
MessageBox | 创建并显示一个包括应用程序提供的消息和标题的窗口 |
19. 窗口消息函数(表6.20)
表6. 20 类CWnd的窗口消息成员函数
成员函数 | 描述 |
GetCurrentMessage | 返回窗口正在处理的消息的指针。仅当在一个OnMessage消息处理函数中调用该成员函数。 |
Default | 调用默认窗口过程,该过程提供对所有应用程序未处理的消息的默认处理 |
PreTranslateMessage | 由CWinApp使用,在窗口消息被发送到TranslateMessage和DispatchMessage之前对其进行过滤 |
续表6.20
成员函数 | 描述 |
SendMessage | 将一条消息发送到CWnd对象,直至该对象处理该消息之后才返回 |
PostMessage | 将一条消息放入程序的消息队列,不等待窗口处理该消息就立即返回 |
SendNotifyMessage | 将指定消息发送到窗口,并尽可能快的返回,这依赖于调用线程如何创建窗口 |
20. 剪贴板函数(表6.21)
表6. 21 类CWnd的剪贴板函数
成员函数 | 描述 |
ChangeClipboardChain | 从剪贴板查看器链中移去CWnd对象 |
SetClipboardViewer | 添到CWnd对象到窗口链,这些窗口当剪贴板内容改变时会收到通知 |
OpenClipboard | 打开剪贴板。其它程序仅当Windows CloseClipboard函数被调用时才可以更改剪贴板 |
GetClipboardOwner | 获得剪贴板的当前拥有者的指针 |
GetOpenClipboardWindow | 获得指向当前打开剪贴板的窗口的指针 |
GetClipboardViewer | 获得指向剪贴板查看器链中第一个窗口的指针 |
21. OLE控件函数(表6.22)
表6. 22 类CWnd的OLE控件函数
成员函数 | 描述 |
SetProperty | 设置OLE控件属性 |
OnAmbientProperty | 实现环境属性值 |
GetControlUnknown | 获得指向一未知OLE控件的指针 |
GetProperty | 获得一OLE控件的属性 |
InvokeHelper | 调用OLE控件方法或属性 |
22. 可重载函数(表6.23)
表6. 23 类CWnd的可重载成员函数
成员函数 | 描述 |
WindowProc | 为CWnd对象提供一个窗口过程。默认的窗口过程通过消息映射发送消息 |
DefWindowProc | 调用默认窗口过程,该过程提供应用程序未处理的所有窗口消息的默认处理 |
PostNcDestroy | 在窗口被消毁后由OnNcDestroy函数调用 |
OnNotify | 由框架调用以通知父窗口某一事件在某一控件中发生或者该控件需要信息 |
OnChildNotify | 由父窗口调用以给通知控件一个响应控件通知的机会 |
DoDataExchange | 用于对话框数据交换和验证。由UpdateData调用 |
其余函数包括对各种窗口消息的消息处理函数,这些函数为数众多,这里我们限于篇幅不再一一介绍。类CWnd中定义的消息处理函数几乎都具有一致的命名方式,其格式为前缀On再加上相应的消息名,如WM_PAINT消息的处理函数在类CWnd中被命名为OnPaint。因此,只需知道所需处理的消息,就可以很快的推知该消息的处理函数名。
第三节 按钮
在本节中要讲述的实际包括四种控件:下压按钮、单选钮、复选框和组框,它们之间无论在外观还是在使用上都有较大的差异。在MFC中之所以使用一个类CButton来封装这四种不同控件纯粹出于历史的原因。这使得一些使用过Visual Basic之类的编程工具的程序员可能会有一点混淆,但相信只需要很短的时间就可以习惯这一点转变。
下面我们分别讲述这四种按钮控件:
6.3.1下压按钮
在基于对话框的应用程序中,下压按钮是最常见的控件之一,如图6.10所示。
图6. 10 下压按钮
下面的步骤讲述如何向对话框中添加下压按钮控件。
1. 在ResourceView中双击需要添加下压按按钮控件的对话框模板,Developer Studio将在资源编辑器中打开该对话框模板。如图6.11所示。
2. 在图6.3所示的控件工具窗口中选择图标,直接使用鼠标在对话框中绘制出一个下压按钮。
3. 右击所绘制的下压按钮,选择Properties命令打开其属性对话框,设置下压按钮的各项属性。下面详细描述这些属性的含义:
图6. 11 在资源编辑器中打开一对话框模板
图6. 12 在对话框中绘制下压按钮控件
一般属性:
ID: | 在头文件中定义的符号。类型:符号、整数或用引号括起来的字符串 |
Caption: | 控件标签文本。如果在标题中的某个字母前加上了“&”符号,该字母在显示时将被加上下划线,相应的“&”符不会被显示。在运行直接按下加有下划线的字母同单击按钮具有同样的效果。默认情况下,资源编辑器对按钮标题的命名依赖于控件的类型,如Button1、Button2等。 |
Visible: | 决定当应用程序第一次运行时控件是否可见。类型:布尔值 默认值为真 |
Disabled: | 决定当对话框创建时该控件是否显示为禁止状态。类型:布尔值 默认值为假 |
Group: | 指定一组控件中的第一个控件。在同组控件中用户可以使用箭头键在控件之间移动。以tab order为序,在该控件之后的所有该属性值为False的控件将被视为同一组控件,直到遇上Group属性标记为True的控件为止。类型:布尔值 默认值为假 |
Tabstop: | 决定用户是否可以使用TAB键来定位到该控件。类型:布尔值 默认值为假 |
HelpID: | 为控件指定一个帮助标识符。该标识符基于相应的资源标识符。类型:布尔值 默认值为假 |
样式:
Default button: | 该属性为真时,控件将作为对话框中的默认按钮,默认按钮在对话框第一次显示时具有粗的黑边,用户在对话框中按下ENTER键相当于单击该按钮。一个对话框中只允许有一个默认按钮。类型:布尔值 默认值为假 |
Owner draw: | 创建一个自绘按钮。使用自绘按钮可以定制按钮的外观。使用自绘按钮需要重载下面的两个函数或其中之一:CWnd::OnDrawItem和CButton::OnDraw。 |
Icon: | 在按钮显示时使用一个图标来代替文本。类型:布尔值 默认值为假 该按钮样式为Windows 95中新引入的按钮样式 |
Bitmap: | 在按钮显示时使用位图来代替文本。类型:布尔值 默认值为假 该样式为Windows 95中新引入的样式 |
Multi-line: | 当按钮文本太长时使用多行回绕的方式进行显示。类型:布尔值 默认值为假 |
Notify: | 按钮控件被单击或双击时通知父窗口。类型:布尔值 默认值为真 |
Flat: | 使用平面外观代替按钮默认的三维外观。类型:布尔值 默认值为假 |
Horizontal alignment: | 设置按钮标题文本的对齐方式(左对齐、右对齐、居中对齐或使用默认位置) |
Vertical alignment: | 设置按钮标题文本的对齐方式(向上对齐、向下对齐、居中对齐或使用默认位置) |
扩展样式
Client edge: | 使按钮看起来有下凹的感觉。类型:布尔值 默认值为假 |
Static edge: | 在按钮边缘创建边框。类型:布尔值 默认值为假 |
Modal frame: | 提供一个三维框架 |
Transparent: | 使控件透明。位于透明窗口下面的窗口不会被该窗口所覆盖。具有透明样式的窗口仅当所有底层兄弟窗口完成更新之后才会收到WM_PAINT消息。类型:布尔值 默认什为假 |
Accept files: | 是否接受文件拖放。如果在控件上放下文件时,控件将接收到WM_DROPFILES消息。类型:布尔值 默认值为假 |
No parent notify: | 指定子窗口不向父窗口发送WM_PARENTNOTIFY消息。类型:布尔值 默认值为假 |
Right aligned text: | 指定文本为右对齐。类型:布尔值 默认值为假 |
Right-to-left reading order: | 使用从右向左的阅读方式来显示文本。主要用于希伯来语系和阿拉伯语等。类型:布尔值 默认值为假 |
- 技巧:
- 如果需要在控件的标题文本中使用“&”符,可以使用双写的“&”符,如按钮文本“&File && Directory”在显示时将成为。
- 如果需要在控件标题中使用多行文本,可以将按钮控件的Multiline属性设置为真,然后在需要换行的地方使用转义字符 "\n" 或 "\r"。在Multiline属性值为真的情况下,如果文本行的宽度超过了控件的宽度,即使没有使用换行转义字符,文本也将会在合适的地方进行折行处理。但要注意,其它一些转义字符序列,如 "\t" 等不被控件所支持。
我们一般只处理按钮控件一种通知消息:BN_CLICKED,该消息表示用户单击了该按钮控件。按钮控件的另外一种通知消息是BN_DOUBLECLICKED,它表示用户双击了按钮控件,但是一般情况下我们不需要处理下压按钮的双击事件。
图6. 13 ClassWizard对话框:Message Maps选项卡
下面我们介绍如何为下压按钮的单击事件添加消息处理函数和消息映射,这里我们假设所添加的下压按钮ID为IDC_CLICKHERE,标题文本为“单击这里(&C)”,其余属性使用默认设置。
第一种方法如下:
1. 在资源编辑器右击按钮IDC_CLICKHERE,选择“ClassWizard”,打开如图6.13所示的窗口,单击Message Maps选项卡。
确信在Project处选择的工程为当前工程,Class name处为当前对话框模板所对应的类。Object IDs列表框中给出了当前对话框类中的所有对象标识符,从中选择IDC_CLICKHERE,即我们刚才添加的下压按钮,这里,在右边的Message列表框中给出了当前对象的消息,这里即BN_CLICKED和BN_DOUBLECLICKED,从中选择BN_CLICKED (它代表了按钮的单击事件),然后单击右边的Add Function按钮(注意:Add Function按钮仅当已选择了某一消息时才会出现)。
图6. 14 决定是否需要更改命令处理函数名
图6. 15 为控件通知消息添加处理函数
2. 在随后出现的对话框(如图6.14所示)中选择是否需要更改命令处理函数的函数名。ClassWizard的默认函数名遵从于下面的命令协议:
前缀On + 控件ID中除去IDC_前缀的剩余部分
这里我们接受默认的命令处理函数名OnClickhere。
3. 新添加的命令处理函数OnClickhere已经出现在图6.13所示的对话框中的下面的Member functions部分。同时,Edit Code按钮获得输入焦点。单击该按钮,ClassWizard将在Developer Studio的代码编辑器窗口中打开函数OnClickhere,并高亮度显示下面的// TODO注释:
// TODO: Add your control notification handler code here
我们使用下面的代码来替换上面的// TODO注释:
MessageBox
("您刚才单击了按钮 IDC_CLICKHERE, 因此相应的命令处理函数 OnClickhere 被调用!");
第二种方法:
1. 在资源编辑器中右击按钮IDC_CLICKHERE,选择Events命令,打开如图6.15所示的对话框:
2. 在Class or object to handle列表框中选择IDC_CLICKHERE,然后在New Windows messages/events列表框中选择BN_CLICKED,单击右边的Add and Edit,余下的步骤同第一种方法的第2步开始相同。
这时编译并运行上面的程序,单击标签为“单击这里”的下压按钮,弹出如图所示的消息框。
图6. 16 程序PushButton的运行结果
下面我们来看相应的消息映射。
首先,在类CPushButtonDlg的定义中添加了消息处理函数OnClickhere的原型:
afx_msg void OnClickhere();
函数OnClickhere的声明被放进了两行注释分隔符//{{AFX_MSG(CPushButtonDlg)和//}}AFX_MSG之间。前面我们提到过,ClassWizard将由它定义的消息处理函数的声明放入这两行注释分隔符之间。
下面我们来看相应的消息映射入口。它位于实现文件PushButtonDlg.cpp中的两个宏BEGIN_MESSAGE_MAP和END_MESSAGE_MAP之间:
ON_BN_CLICKED(IDC_CLICKHERE, OnClickhere)
其中第一个参数IDC_CLICKHERE为控件的标识符,第二个参数OnClickhere为相应的消息处理函数。
一旦弄清楚了由ClassWizard添加这些代码,我们就可以手动的添加命令消息处理函数的消息映射。但是,从上面的过程中我们可以很明显的看出一点,使用ClassWizard来完成这一点要简单得多。
下面我们介绍与下压按钮控件有关的几个技巧:
(1) 在运行过程中改变下压按钮的标题文本
有时候我们需要在程序运行的过程中改变按钮的标题文本。典型的,我们可能需要根据用户所输入的数据来决定按钮上应该写些什么。我们到前面去看一下表6.14,看一看有什么成员函数可以完成这种功能。
很好,类CWnd的成员函数SetDlgItemText可以由窗口或对话框所有的控件的标题文本。其原型如下:
void SetDlgItemText( int nID, LPCTSTR lpszString );
其中nID为控件标识符(ID),lpszString为控件的新标题文本。
成员函数SetDlgItemText事实上是向控件发送一条WM_SETTEXT消息,该消息的wParam参数必须为0,而lParam为指向窗口标题文本字符串的指针。
因此,SetDlgItemText等价于下面的函数调用:
CWnd::SendDlgItemMessage(nID, WM_SETTEXT, 0, LPARAM(lpszString));
或
::SendDlgItemMessage(GetSafeHwnd(), nID, WM_SETTEXT, 0,LPARAM(lpszString));
比如说,我们用以将下面的代码添加到OnClickhere中对MessageBox的调用之后:
SetDlgItemText(IDC_CLICKHERE,"此按钮已被单击过.");
(2) 使用按钮无效(或有效)
假设我们在上面的例子中希望用户只能单击按钮IDC_CLICKHERE一次。那么,按钮IDC_CLICKHERE被单击一次之后应该变灰,以禁止用户再次单它。这可以通过下面的步骤来实现:
首先调用对话框对象的成员函数GetDlgItem (该成员函数在类CWnd中定义),该成员函数获得一个指向对话框中的控件的CWnd指针,然后再通过该指针调用控件对象的成员函数EnableWindow (该成员函数在类CWnd中定义)。该成员函数允许或禁止调用它的CWnd对象对应窗口。整个过程可以使用一行语句来实现,如下所示:
GetDlgItem(IDC_CLICKHERE)->EnableWindow(FALSE);
其中GetDlgItem函数以控件的ID为参数,返回值的类型为CWnd *,如果需要通过该指针调用在类CButton所定义的成员函数,可以使用强制类型转换。EnableWindow以一个布尔值为参数,该参数为真时表示允许该窗口接受鼠标和键盘输入,为假时禁止该窗口接受鼠标和键签署输入。这里再一次强调,控件本身也是一种窗口。
将上面的代码放到命令处理函数OnClickhere的最后,这样,在单击一次按钮“单击这里”之后,对话框如图6.17所示。
图6. 17 处于禁止状态的控钮控件
此外,如果使用了ClassWizard为按钮建立了对话框的成员变量的数据映射,则可以通过对话框中的成员变量直接操纵控件。在本例中,如果我们已将下压按钮映射为类型为CButton的成员变量m_bnClickhere,则可以通过下面成员函数调用设置按钮的允许状态:
m_bnClickhere->EnableWindow(FALSE);
(3) 使按钮获得输入焦点
具有输入焦点的窗口将会得到所有的键盘输入消息。我们可以通过类CWnd的成员函数GetFocus来使对话框中的控件获得输入焦点。
试将下面的代码加到消息处理函数OnInitDialog的return语句前:
m_bnClickhere.SetFocus();
或
GetDlgItem(IDC_CLICKHERE)->SetFocus();
编译并运行程序。非常奇怪,输入焦点并没有被设置到下压按钮“单击这里”上。依然是按钮“确定”拥有当前输入焦点。
请注意这样的事实:
- 注意:
- 如果在消息处理成员函数OnInitDialog中将输入焦点设置到指定的控件,则函数应该返回FALSE,这是因为如果WM_INITDIALOG消息的处理函数返回真值,Windows会将输入焦点设置为对话框中的第一个控件。因此,如果在该处理函数中设置了控件的输入焦点,WM_INITDIALOG消息的处理函数应该返回假值。
将下面的代码
return TRUE;
修改为
return FALSE;
这时再编译并运行程序,则输入焦点将被正常地设置到下压按钮“单击这里”上。这时按下空格键相当于在按钮“单击这里”上单击鼠标左键。
(4) 使用图形代替文本
在一些应用程序,尤其是一些多媒体应用程序中,我们希望按钮的外观看起来更加的美观,比如说我们希望使用多变的图形代替单调乏味的纯文本。对于一般的按钮控件,我们可以使用两种方法来在按钮中使用图形来代替文本。
第一种方法是使用图标来代替文本。下面的示例说明了这种用法:
1. 使用资源编辑器或其它工作编辑一个图标资源,其ID为IDI_CLICKHERE,图案如图6.18所示。
2. 在希望使用图标图案的按钮控件的Properties属性框在Styles选项卡中设置Icon属性为真。并按图6.19修改对话框及其中控件的大小。
3. 在类CPushButtonDlg的消息处理成员函数OnInitDialog中添加下面的代码。这些代码应该在对基类的OnInitDialog成员函数的调用之后。
图6. 18 图标IDI_CLICKHERE
图6. 19 为使用图标按钮修改
对话框中控件的大小
HICON hIcon=AfxGetApp()->LoadIcon(IDI_CLICKHERE);
m_bnClickhere.SetIcon(hIcon);
编译该应用程序,运行结果如图6.20所示。
图6. 20 在按钮中使用图标的示例
这时单击按钮Click Here,图标图案会有向右和向下下压的效果。
第二种方法是使用位图来代替文本。步骤如下:
图6. 21 位图资源IDB_CLICKHERE
1. 向工程资源中添加如图6.21的位图资源,其ID为IDB_CLICKHERE。
2. 在希望使用位图图案的按钮控件的Properties属性框在Styles选项卡中设置Bitmap属性为真。我们注意到Icon属性和Bitmap属性是互斥的,即选择一属性的同时也清除了另一属性。并按图6.19修改对话框及其中控件的大小。同时参考最终运行结果(如图6.22)修改对话框及其按钮的大小。
3. 在OnInitDialog成员函数中添加如下的代码:
HBITMAPhBitmap=LoadBitmap(AfxGetApp()->m_hInstance,MAKEINTRESOURCE(IDB_CLICKHERE));
m_bnClickhere.SetBitmap(hBitmap);
在上面的代码中,我们使用Win32 API函数LoadBitmap (注意它不是类CWinApp的成员函数)来加载位图资源IDB_CLICKHERE,从而获得位图句柄hBitmap,最后以该句柄为参数调用类CButton的成员函数SetBitmap。
编译并运行上面的程序,得到如图6.22所示的运行结果。
图6. 22 在按钮中使用位图的示例
- 注意:
- 在上面的示例程序中我们使用了真彩色(24位)的位图。这样的位图不可以使用资源编辑器来进行编辑。上面的位图是使用其它专门的图形工具来进行编辑并保存到文件Clickhere.bmp中的。请按下面的步骤将该文件添加为工程的资源:
1. 选择Insert菜单下的Resource命令,打开如图所示的对话框。
图6. 23 插入新的资源
2. 从中单击Import按钮,从位图文件Clickhere.bmp中输入资源。注意在文件类型中选择“All files (*.*)”。
Developer Studio将弹出如图6.24所示的警告对话框。该对话框表明位图资源已被正确添加。但由于使用了多于256色的颜色数,因此该资源不可以在资源编辑器中打开。
图6. 24 试图添加使用了多于256色的位图资源时的警告消息框
3. 按正常的方法将所添加的位图资源的ID修改为IDB_CLICKHERE。必要时重新编辑资源文件或工程。
6.3.2位图按钮
位图按钮是由MFC提供的几种附加控件之一。在前一节的过程中,我们可以使用一个位图来代替文本作为下压按钮的标签。而在位图按钮中,我们可以使用多达四个位图来分别代表按钮处于四种不同的状态(凸起、按下、获得焦点或被禁止)下的显示。而且,使用位图按钮还可以去除掉令人讨厌的按钮黑边。而使用位图按钮并不复杂,但是相比起标准的按钮控件(它由Windows自身所提供)而言有一些特殊。下面的过程描述了位图按钮的使用,它们在MFC中使用类CBitmapButton封装。
1. 使用AppWizard创建新的基于对话框的MFC工程BitmapButton。
2. 使用资源编辑器绘制一个标准按钮,将其ID设为IDC_CLICKHERE,标题文本设为CLICKHERE,然后在Styles选项卡中将Owner draw属性设置为真。
3. 向工程中添加四个位图资源。
|
|
|
|
"CLICKHEREU" | "CLICKHERED" | "CLICKHEREF" | "CLICKHEREX" |
图6. 25 位图按钮IDC_CLICKHERE所使用的四个位图资源
所添加的四个位图资源的ID的设置取决于在第一步中的标题文本的设置:按钮未按下去时使用的位图添加了后缀"U";按钮按下去时使用的位图添加了后缀"D";按钮拥有焦点时使用的位图添加了后缀"F";按钮被禁止时使用的位图添加了后缀"X"。需要注意的是,由于这些位图资源的ID为字符串,因此在使用属性对话框设置其ID时一定要加了双引号,否则资源编辑器会将该ID值看作代表一个整型量的符号。
4. 在对话框类CBitmapButtonDlg(这里我们沿用上一节中的示例程序)中添加类型为CBitmapButton的新的成员变量m_bnClickhere。
5. 在OnInitDialog成员函数中的return语句前添加下面的代码:
m_bnClickhere.AutoLoad(IDC_CLICKHERE, this);
CRect rect1,rect2;
CButton *pClickhere=(CButton*)GetDlgItem(IDC_CLICKHERE);
GetClientRect(&rect1);
pClickhere->GetWindowRect(&rect2);
ScreenToClient(&rect2);
pClickhere->MoveWindow(rect2.left,(rect1.Height()-rect2.Height())/2,
rect2.Width(),rect2.Height());
其中第一个参数IDC_CLICKHERE是位图按钮的资源ID,第二个参数为指向该位图按钮的父窗口的CWnd对象的指针,这里即类CBitmapButtonDlg的this指针。类CBitmapButton的成员函数AutoLoad完成以下几步工作:
(1) 将按钮与CBitmapButton对象相关联;
(2) 自动加载按钮所使用的位图,条件是这些位图资源满足步骤2中的命名约定;
(3) 自动改变控件的大小以适合所加载的位图资源。
接下来的几行代码将位图按钮在对话框中进行垂直居中。首先类CWnd的成员函数GetClientRect返回了对话框的客户区矩形,接着,类CWnd的成员函数GetWindowRect返回了控件IDC_CLICKHERE的窗口矩形,然后使用类CWnd的成员函数ScreenToClient将rect2由屏幕坐标转换为对话框的客户坐标,这是因为类CWnd的成员函数MoveWindow在移动子窗口时将使用父窗口的客户区坐标,而不是使用屏幕坐标。
6. 按图6.26添加下面的下压按钮IDC_DISABLE,将其标题设置为“禁止使用(&X)”。
图6. 26 位图按钮示例程序对话框的设计
7. 将所有下压按钮的Tab stop属性(位于General选项卡中)设置为真。并按图6.26调整各控件的大小位置。其中按钮CLICKHERE的大小的无关紧要的,我们只需要保证对话框左边是否有足够的空间来显示按钮所使用的位图即可。
8. 为按钮IDC_DISABLE的BN_CLICKED命令编写下面的命令处理函数:
void CBitmapButtonDlg::OnDisable()
{
CButton *pClickhere=(CButton*)GetDlgItem(IDC_CLICKHERE);
static int bIsEnabled=pClickhere->IsWindowEnabled();
if (bIsEnabled)
{
pClickhere->EnableWindow(FALSE);
SetDlgItemText(IDC_DISABLE,"允许使用(&E)");
}
else
{
pClickhere->EnableWindow(TRUE);
SetDlgItemText(IDC_DISABLE,"禁止使用(&X)");
}
bIsEnabled=!bIsEnabled;
}
上面的代码实现两个功能,即当位图按钮的状态为允许时,单击按钮IDC_DISABLE将其状态设置为不允许;在相反的状态下,单击按钮IDC_DISABLE将其状态设置为允许。由于实现该过程的代码比较简单,因此我们在这里不作详细的讲述。
编译并运行上面的示例程序,其结果如图6.27所示。
反复单击位图按钮和禁止使用按钮,以观察位图按钮在不同状态下的外观的改变。还可以使用TAB键改变按钮的输入焦点,以观察位图按钮获得输入焦点和失去输入焦点时的不同外观。
图6. 27 位图按钮示例程序
6.3.3组框
组框也是一种按钮控件。它常常用来在视觉上将控件(典型情况下是一系列的单选钮和复选框)进行分组,从而使对话框中的各个控件看起来比较有条理。
图6. 28 组框(Group box)控件
相对于其它控件来说,组框的使用非常之简单。这里我们需要强调的是,组框仅仅是在视觉上将控件进行分组,事实上控件在编程上的分组依赖于其Group属性的设置。
组框也可以发送BN_CLICKED和BN_DOUBLECLICKED命令消息。但是在般情况下我们都不对这些命令作响应。此外,组框也可以设置Icon或Bitmap属性(注意它们之间的互斥的),即我们可以使用图标或位图来代替默认情况下的文本。但是在绝大多数情况下,我们仅使用纯文本来作为组框的标题。
与前面讲述的下压按钮类似,我们同样可以使用SetDlgItemText成员函数来设置组框控件的标题文本。此外,我们还可以使用GetDlgItem来获得与组框控件相关联的CWnd对象的指针,然后通过该指针调用成员函数SetWindowText来实现同样的功能。由于在程序中常常不需要频繁的操纵组框控件,因此大多数情况下我们不需要为组框控件进行成员变量的映射,但这种方法是完全可以的。
对于如何将控件进行分组的方法在讲述单选钮和复选框时再作介绍。
6.3.4单选钮
图6. 29 单选钮示例程序
单选钮用来表示一系列的互斥选项,这些互斥选项常常被分成若干个组。下面的示例程序说明了单选钮的使用。
1. 创建新的基于对话框的MFC应用程序,将工程名设置为RadioButton。
2. 按图6.29绘制应用程序的主对话框。其中在Control工具箱中单选钮对应的图标是,组框控件对应的图标是。
3. 单击Layout菜单下的TabOrder命令,按图6.30的顺序单击各控件以设置控件的TAB键顺序(Tab Order)。
4. 确信所有控件的Group属性都被设置为假。分别单击组框“性别”和组框“年龄”,将其Group属性设置为真。
图6. 30 设置控件的Tab Order
以TabOrder为序,从Group属性为真的控件开始(包括该控件),到下一个Group属性的真的控件结束(不包括该控件),所有的这些控件将组成一个组。对于单选钮,同一组内同时只能有(也应该有)一个处于被选中的状态。当其中一个控件被置于选中状态时,同组的其它单选钮应该清除其选中状态。对于由资源编辑器生成的单选钮控件,在默认情况由Windows自动处理同组控件之间的互斥关系。
下面我们简述一下特定于单选钮的一些属性及其含义,这些属性被列于Styles选项卡内:
Auto: | 在具有Auto属性的情况下,当用户单击了同一组的某个单选钮时,其余单选钮的选中属性被自动清除。当在一组单选钮中使用Dialog Data Exchange时,该属性必须被设置为True。类型:布尔值 默认值:真 |
Left text: | 将单选钮的标题文本显示于圆形标记的左边。类型:布尔值 默认值:假 |
Push-like: | 使一个复选框、三态复选框或单选项具有类似于下压按钮的外观和行为。该按钮在选中时显示为凸起,在不被选中时显示为凹下(参见图。类型:布尔值 默认值:假 |
Notify: | 决定在默认情况下当单选钮被单击或双击时向父窗口发送通知消息。类型:布尔值 默认值:真 |
图6. 31 具有Pusk-like样式的单选钮
5. 将性别框内的两个控件的ID按从上到下的顺序设置为IDC_SEX1和IDC_SEX2;将年龄框内的两个控件的ID按从上到下的顺序设置为IDC_AGE1、IDC_AGE2、IDC_AGE3、IDC_AGE4和IDC_AGE5。
在程序运行时可以调用CButton的成员函数SetCheck设置单选钮的选中状态。该成员函数带有一个类型为整型的参数,该参数为0表示清除选中按钮的选中状态,参数为1表示设置选中按钮的选中状态。
- 注意:
- 如果我们在程序中调用SetCheck设置同一组中某一单选钮的为选中状态,并不意味着同时清除同一组中其它单选钮的选中状态。以前面创建的工程来举例,请看下面的两行代码:
- ((CButton*)GetDlgItem(IDC_AGE1))->SetCheck(1);
- ((CButton*)GetDlgItem(IDC_AGE5))->SetCheck(1);
上面的代码将导致年龄组中的第一个按钮和第五个按钮在对话框第一次显示时同时处于选中状态。这是应该避免的。因此,如果我们通过代码改变了单选钮的选中状态,一定要记得同时清除同组的其它单选钮的选中状态。
对于单个的单选钮,我们可以调用类CButton的成员函数GetCheck,该函数的返回值为0、1或2,分别代表按钮处理未选中状态、选中状态或中间状态(对三态复选框而言)。但是,对于对话框中的单选锯而言,我们更感兴趣于同一组单选钮中哪一个被选中,因此,调用类CWnd的成员函数GetCheckedRadioButton要更为方便。该成员函数原型如下:
int GetCheckedRadioButton( int nIDFirstButton, int nIDLastButton );
第一个参数nIDFirstButton是同一组中的第一个单选钮控件的ID,nIDLastButton是同一组中最后一个单选钮控件的ID。成员函数GetCheckedRadioButton返回指定组中第一个所选中的单选钮(在正常情况下仅应当有一个单钮被选中)的ID,如果没有按钮被选中,则返回0。
这里需要注意的是,成员函数GetCheckedRadioButton被没有要求两个参数nIDFirstButton和nIDLastButton所指定的控件一定位于同一组中。
- 注意:
- 若干个单选钮是否属于同一组是以其Tab顺序来排定的,而GetCheckedRadioButton函数是以ID顺序来检查按钮的选定状态的。因此,如果传递给函数GetCheckedRadioButton的第一个参数的值大于第二个参数的值时,其返回值总是为0,而事实上,由这两个参数指定的单选钮的TAB顺序可能恰恰相反。因此,一般情况下我们应该尽量保证同一组单选钮的资源ID是连续递增的。通常这些资源ID是在头文件Resource.h中定义的。如果你同一组的单选钮不是一次创建的,那么它们的资源ID可能不是连续递增的,甚至可能是相反的。这时我们可以手动的修改资源头文件中的宏定义,以保证如GetCheckedRadioButton之类的成员函数得到正确的结果。
同时,这也说明一点,即使用GetCheck一个一个控件的检查各单选钮的选中状态要安全得多。
下面我们来完成应用程序RadioButton。
首先,使用ClassWizard重载类CDialog的OnOK成员函数,方法是重载ID为IDOK的按钮的BN_CLICKED命令处理函数。由ClassWizard生成的默认重载形式如下:
void CRadioBoxDlg::OnOK()
{
// TODO: 在此添加附加的验证
CDialog::OnOK();
}
这里特定的代码来替代前面的// TODO注释后得到如下的程序代码:
void CRadioBoxDlg::OnOK()
{
// 暂时隐藏主对话框
ShowWindow(SW_HIDE);
UINT nSex=GetCheckedRadioButton(IDC_SEX1,IDC_SEX2); // 获得性别选择
UINT nAge=GetCheckedRadioButton(IDC_AGE1,IDC_AGE5); // 获得年龄选择
CString msg="性别: "; // 保存输出消息字符串
// 根据用户的选择生成消息串
// 添加性别信息
switch (nSex)
{
case IDC_SEX1:
msg+="男\n";
break;
case IDC_SEX2:
msg+="女\n";
break;
default:
break;
}
// 添加年龄信息
msg+="年龄: ";
switch (nAge)
{
case IDC_AGE1:
msg+="18 岁以下";
break;
case IDC_AGE2:
msg+="18 - 25 岁";
break;
case IDC_AGE3:
msg+="25 - 35 岁";
break;
case IDC_AGE4:
msg+="35 - 45 岁";
break;
case IDC_AGE5:
msg+="45 岁以上";
break;
default:
break;
}
msg+="\n\n以上数据是否正确?";
// 显示输入消息框询问用户所输入的信息是否正确
if(MessageBox(msg,NULL,MB_YESNO|MB_ICONQUESTION)==IDNO)
{
// 当用户回答“否”时重新显示对话框以供便用户可以更改所作的选择
ShowWindow(SW_SHOW);
return;
}
// 否则退出应用程序
CDialog::OnOK();
}
以上应用程序的运行结果如图6.32所示:
按如图6.32所示进行选择,单击确定弹出如图6.33所示的对话框。
下一节中我们将讲述复选框的使用。
图6. 32 单选钮示例程序的运行结果
图6. 33 单击“确定”之后的确认消息框
6.3.5复选框
复选框与单选钮很相象,不同之处在于在同一组控件中,通常使用复选框来代表多重选择,即选项不是互斥的。从外观上来说,复选框所使用的选中标记是一个方框和方框里面的小叉,而不是单选钮所使用的小圆圈和里面的小点。
对于编程者来说,复选框和单选钮非常相似。我们通过SetCheck成员函数来设置某一复选框的选中状态,通过GetCheck成员函数来获取某一复选框的选中状态。一般来说,对于复选框,由于其选项不是互斥的,我们一般不通过GetCheckedRadioButton之类的函数来获得处于选中状态的按钮。
以下特定于复选框的样式可以Properties对话框的Styles属性页中进行设置:
Auto: | 对于Auto属性为真的复选框,在单击时将自动在“选中”和“不选中”之间进行切换。如果在一组复选框中使用了Dialog Data Exchange,则必须将该属性设置为真。类型:布尔值 默认值:真 |
Tri-state: | 创建三态复选框。除了处于“选中”和“不选中”状态外,三态复选框还可以处于变灰状态。通常,态复选框的变灰状态表示其选中状态不确定。在很多软件的安装程序中,变灰往往表示仅选中该组件中的一部分。 |
图6. 34 工程CheckBox的主对话框的设计
下面的应用程序举例说明了复选框的使用。
1. 使用默认选项创建一个基于对话框的MFC工程,设置工程名为CheckBox。
2. 按图6.34绘制对话框中的各个复选框(在Control工具箱中复选框所对应的图标为),并按表6.24设置各复选框的样式和属性。
表6. 24 工程CheckBox中各控件的属性设置
控件 | ID | 标题文本 | 其它 |
复选框 | IDC_PLACE1 | 在家里(&H) | Auto属性和Tri-state属性均为真 |
IDC_PLACE2 | 在公司办公室(&O) | ||
IDC_PLACE3 | 在学校公共机房(&S) | ||
IDC_OFTEN | 经常 | Auto属性为假,Tri-state属性为真 | |
IDC_SELDOM | 偶尔 | ||
IDC_NEVER | 从不 | ||
组框 | IDC_STATIC | 使用计算机的场所 |
|
3. 使用下面的代码替换类CCheckBoxDlg的成员函数OnInitDialog中的// TODO注释:
((CButton*)GetDlgItem(IDC_OFTEN))->SetCheck(1);
((CButton*)GetDlgItem(IDC_SELDOM))->SetCheck(2);
((CButton*)GetDlgItem(IDC_NEVER))->SetCheck(0);
由于三个复选框IDC_OFTEN、IDC_SELDOM、IDC_NEVER的Auto属性值为假,因此当用户单击这三个复选框时其状态不会发生改变。它们在本示例程序中起了图例的作用。
4. 在类CCheckBoxDlg中重载类CDialog的成员函数OnOK如下(关于对命令处理成员函数OnOK的重载我们已经在前一小节中作了讲述):
void CCheckBoxDlg::OnOK()
{
// 定义和初始化所用的变量
CString strMsg, // 消息字符串
strMsgA[3]; // 分别对应于三种不同时间频度的消息字符串
int iCount[3]; // 对应于每种时间频度的情况计数
// 初始化各变量
iCount[0]=iCount[1]=iCount[2]=0;
strMsgA[0]="从不在";
strMsgA[1]="经常在";
strMsgA[2]="偶尔在";
int i; // 用着循环变量或中间变量
// 检查各复选框的选中状态,并根据用户的选择生成对应于三种不同时间
// 频度的消息字符串
// 检查复选框 IDC_PLACE1
i=( (CButton*)GetDlgItem(IDC_PLACE1) )->GetCheck();
if ( (iCount[i]++)==0 )
strMsgA[i]+="家里";
else
strMsgA[i]+="、家里";
// 检查复选框 IDC_PLACE2
i=( (CButton*)GetDlgItem(IDC_PLACE2) )->GetCheck();
if ( (iCount[i]++)==0 )
strMsgA[i]+="公司办公室";
else
strMsgA[i]+="、公司办公室";
// 检查复选框 IDC_PLACE3
i=( (CButton*)GetDlgItem(IDC_PLACE3) )->GetCheck();
if ( (iCount[i]++)==0 )
strMsgA[i]+="学校开放机房";
else
strMsgA[i]+="、学校开放机房";
// 为了符合汉语的语气转折,判断是否需要在“从不……”分句前添加转折
// 连词“但”。如果用户对三种情况的选择都是“从不”,那么这个“但”
// 字是不应该要的。
if ( !(iCount[1]==0 && iCount[2]==0) )
strMsgA[0]=CString("但")+strMsgA[0];
// 如果用户对三种情况的选择都不属于某种时间频度,那么该时间频度所对应
// 的消息字符串应该为空。否则,在该分句的末尾加了字符串“使用计算机,”。
for (i=0;i<3;i++)
{
if ( iCount[i]==0 )
strMsgA[i]="";
else
strMsgA[i]+="使用计算机,";
}
// 生成最终显示的消息字符串
strMsg=CString("您")+strMsgA[1]+strMsgA[2]+strMsgA[0];
// 处理消息字符串的标点
strMsg=strMsg.Left( strMsg.GetLength()-2 )+"。";
// 弹出消息框询问用户所输入的数据是否正确
if ( MessageBox( strMsg,"确认",MB_YESNO|MB_ICONQUESTION )==IDNO )
{
// 如果用户选择“否”,则重新输入数据
return;
}
// 调用基类的 OnOK 成员函数,并关闭对话框
CDialog::OnOK();
}
上面的代码都加上了详细的注释,而且所用的函数也都是我们所熟知的,这里我们就不再重复讲述了。
到目前为止,我们已经讲述完了Windows标准控件中的按钮类控件:下压按钮、组框、单选钮和复选框。此外,我们还介绍了位图按钮,一般来说我们并不把它归入Windows标准控件中,而认为它是由MFC提供的少数几个控件之一。而位图按钮事实上是具有Owner draw属性的自绘制下压按钮,MFC类CBitmapButton封装了其内部实现的复杂性,而以简单的接口提供给程序员。
作为本节的结束,我们来讨论一样如何改变按钮标题文本的字体属性。在Developer Studio的资源编辑器中,我们可以统一的修改同一对话框中所有按钮的标题文本的字体属性。方法是打开对话框本身的属性(Properties)对话框,在General选项卡中单击Font按钮,从弹出Select Dialog Font对话框中选择对话框所用的字体。
图6. 35 设置对话框的字体属性
通过上面的方法设置的字体对整个对话框中所有的控件都有效。如果需要设置单个控件的字体,我们必须通过编写代码来实现。下面的示例程序ButtonFont演示了如何单独更改某个控件的字体。
图6. 36 示例程序ButtonFont的主对话框
按图6.36绘制应用程序主对话框中所有的各按钮控件。其中标签为“我爱你”的按钮ID为IDC_LOVE,标签为“改变字体”的按钮ID为IDC_CHANGEFONT。
在类CButtonFontDlg中添加类型为CFont的私有成员变量m_Font。
为按钮IDC_CHANGEFONT的BN_CLICKED事件编写下面的处理函数:
void CButtonFontDlg::OnChangefont()
{
// 获取按钮 IDC_LOVE 的当前所用字体
LOGFONT lf;
GetDlgItem(IDC_LOVE)->GetFont()->GetLogFont(&lf);
// 使用按钮的当前字体初始化字体对话框
CFontDialog dlgFontDlg(&lf);
// 显示字体选择对话框
if (dlgFontDlg.DoModal()==IDOK)
{
// 如果用户在字体选择对话框中单击了“确定”按钮,
// 则使用
dlgFontDlg.GetCurrentFont(&lf);
m_Font.DeleteObject();
m_Font.CreateFontIndirect(&lf);
GetDlgItem(IDC_LOVE)->SetFont(&m_Font);
}
}
编译并运行程序ButtonFont,单击“改变字体”按钮,在随后弹出的字体选择对话框中设置字体并单击“确定”按钮。对话框的显示可能如图6.37所示。
图6. 37 示例程序ButtonFont的运行结果
- 注意:
- 在示例程序中,如果不定义类CButtonFontDlg的成员变量m_Font,命令处理函数OnChangefont可以应该这样编写:
- void CButtonFontDlg::OnChangefont()
- {
- // 获取按钮 IDC_LOVE 的当前所用字体
- LOGFONT lf;
- GetDlgItem(IDC_LOVE)->GetFont()->GetLogFont(&lf);
- // 使用按钮的当前字体初始化字体对话框
- CFontDialog dlgFontDlg(&lf);
- // 显示字体选择对话框
- if (dlgFontDlg.DoModal()==IDOK)
- {
- // 如果用户在字体选择对话框中单击了“确定”按钮,
- // 则将按钮 IDC_LOVE 的标题文本字体设置为所选定的字体。
- static CFont font;
- dlgFontDlg.GetCurrentFont(&lf);
- font.DeleteObject();
- font.CreateFontIndirect(&lf);
- GetDlgItem(IDC_LOVE)->SetFont(&font);
- }
- }
按下面的方式编写命令处理函数OnChangefont不会得到正确的结果:
- void CButtonFontDlg::OnChangefont()
- {
- ...
- if (dlgFontDlg.DoModal()==IDOK)
- {
- CFont font;
- dlgFontDlg.GetCurrentFont(&lf);
- font.DeleteObject();
- font.CreateFontIndirect(&lf);
- GetDlgItem(IDC_LOVE)->SetFont(&font);
- }
- }
之所以会出现这种情况与用来设置字体的CFont变量的存活期有关。
第四节 静态控件
静态控件一般用来显示静态的文本、图标、位图或图元文件,它不能用来接受用户的输入,也很少用来显示输出,而在更多的情况下用作那些没有固定的标题文本属性的控件(如文本编辑控件、列表框等)的标签,或者用来进行控件的分组,或者用来显示一些提示性文本。
MFC类CStatic封装了标准的Windows静态控件。下面的示例程序StaticDemo演示了静态控件的使用。
1. 使用AppWizard创建一个基于对话框的MFC应用程序,设置其工程名为StaticDemo。
2. 按如图6.38绘制主对话框中的控件。其中标签为“静态控件”的静态控件ID为IDC_STATIC。需要注意是的,由资源管理器添加的静态控件在默认情况下其ID均为IDC_STATIC,因此,如果需要在程序中区分和操纵各个不同的静态控件,一般情况下我们都需要更改新添加的静态控件的ID值。这里我们将静态控件的ID值设置为IDC_STATICDEMO。
图6. 38 示例程序StaticDemo的主对话框
以下属性和样式没有在本章前面的内容中涉及,它们可以适用于静态控件。可以通过静态控件的Properties属性对话框的Styles选项卡进行这些属性或样式的设置。
Align text: | 决定静态文本控件中文本的横向对齐方式。可供选择的值为Left (向左对齐)、Center (居中对齐)和Right (向右对齐)。默认值:Left |
Center Vertically: | 在静态文本控件中将文本进行垂直居中。类型:布尔值 默认值:假 |
No prefix: | 不将控件文本中的“&”符解释为助记字符。在默认情况下,“&”符号在显示时会被去掉,取而代之的是紧接“&”符之后的字符被以加下划线的格式进行显示。我们早在前面说过,通过双写“&”符可以在控件文本中显示出实际的“&”符,但是,对于一些特殊的场合,如使用静态文本控件来显示文件名的时候,将No prefix属性设置为“真”要更方便。 |
No wrap: | 以左对齐的方式来显示文本,并且不进行文本的自动回行。超出控件右边界的文本将被裁去。需要注意的是,这时即使使用转义字符序列"\n"也不可以强制控件文本进行换行。类型:布尔值 默认值:假 |
Simple: | 禁止设置Text Align属性和No Wrap样式。在该属性为真的情况下,静态文本控件中的文本不会被自动回行,也不会被剪裁。类型:布尔值 默认值:假 |
Notify: | 决定控件在被单击时是否通知父窗口。类型:布尔值 默认值:假 |
Sunken: | 使用静态文本控件看上去有下凹的感觉。类型:布尔值 默认值:假 |
Border: | 为文本控件创建边框。类型:布尔值 默认值:假 |
4. 静态控件一般不用于输入,但是如果它的Notify属性设置为真,则当用户单击静态控件时,静态控件将向父窗口发送通知消息。但是,我们不可以使用前面所讲述的方法(即使用ClassWizard或从上下文菜单中选择Events命令)来为静态控件添加消息处理函数。而要以手动的方式来实现这一点。下面我们结合示例StaticDemo来说明如何为静态控件添加单击事件的命令处理程序。在进行下面的步骤之前,请确认静态控件IDC_STATICDEMO的Notify属性值为真。
在类CStaticDemoDlg的定义处添加下面的命令处理函数声明:
afx_msg void OnStaticDemo();
最好把成员函数OnStaticDemo的声明与其它命令处理函数的声明放在一起,但不要放到//{{AFX_MSG和//}}AFX_MSG之间。
然后,打开类CStaticDemoDlg的实现文件StaticDemoDlg.cpp,在宏
BEGIN_MESSAGE_MAP(CStaticDemoDlg, CDialog)
和宏
BEGIN_MESSAGE_MAP
之间添加如下的消息映射入口:
ON_BN_CLICKED(IDC_STATICDEMO, OnStaticDemo)
同样,不要把手动添加的消息映射入口项放到注释//{{AFX_MSG_MAP和//}}AFX_MSG_MAP之间。
手动添加成员函数OnStaticDemo或OnDoubleclickedStaticDemo的实现代码:
void CStaticDemoDlg::OnStaticDemo()
{
MessageBox("您刚才单击了“静态控件”!");
}
编译上面的示例程序,单击“静态控件”,命令处理函数OnStaticDemo将被调用,从而弹出相应的消息框。
下面我们来看一下如果在静态控件中使用图标和位图。
图6. 39 使用图标代替静态控件中的文本
首先介绍使用图标代替文本的例子,方法如下:
假设对话框类为CStaticDemoDlg,所需使用图标的静态控件ID为IDC_STATICDEMO,相应的图标的ID为IDR_MAINFRAME,则可用下面的代码代替类CStaticDemoDlg的成员函数OnInitDialog中的// TODO注释:
// 获得指向静态控件的指针
CStatic *pStaticDemo=(CStatic*)GetDlgItem(IDC_STATICDEMO);
// 加载图标
HICON hIcon=AfxGetApp()->LoadIcon(IDR_MAINFRAME);
// 设置静态控件的样式以使得可以使用图标,并使图标显示时居中
pStaticDemo->ModifyStyle(0xF,SS_ICON|SS_CENTERIMAGE);
// 设置静态控件图标
pStaticDemo->SetIcon(hIcon);
运行该程序,显示如图6.39。
接着我们来看如何使用位图代替文本,方法如下:
假设所用位图的资源ID为IDB_STATICDEMO,其余设置如上。用以下代码来代替成员函数OnInitDialog中的// TODO注释:
// 获得指向静态控件的指针
CStatic *pStaticDemo=(CStatic*)GetDlgItem(IDC_STATICDEMO);
// 获得位图句柄
HBITMAP hBitmap=::LoadBitmap(AfxGetApp()->m_hInstance,
MAKEINTRESOURCE(IDB_STATICDEMO));
// 设置静态控件的样式以使得可以使用位图,并使位图在显示时居中
pStaticDemo->ModifyStyle(0xF,SS_BITMAP|SS_CENTERIMAGE);
// 设置静态控件显示时使用的位图
pStaticDemo->SetBitmap(hBitmap);
编译并运行该程序,对话框显示如图6.40所示。
图6. 40 使用位图代替文本的静态控件
- 注意:
- 在使用位图的例子中,传递给ModifyStyle的第一个参数的值绝对不可以为0,否则将得不到正常的运行结果。
第五节 文本编辑控件
静态文本控件只能用来显示文本,而不可以用来输入文本。如果需要提供输入文本的功能应该使用文本编辑控件。文本编辑控件在Control工具箱中对应的图标为。对于文本编辑控件,除了我们在前面所涉及的一些外,还可以设置以下的一些属性样式:
Align text: | 决定当Multiline属性为真时文本的对齐方式。默认值为:Left |
Multi-line: | 创建一个多行文本编辑控件。当一个多行文本编辑控件具有输入焦点时,如果用户按下了ENTER键,以默认情况下的行为是选择对话框中的默认命令按钮,而不是向文本编辑控件中插入新行。将AutoHScroll属性或Want return属性设置为真可以将用户按下的ENTER键解释为插入新行,而不是选择默认命令按钮。 在选择了AutoHScroll属性时,如果插入点超过了控件的右边界,多行文本编辑控件自动进行水平滚动。用户可以使用ENTER键来开始新行。 如果没有选择AutoHScroll属性,多行文本编辑控件将视需要将文本进行自动折行。而仅当Want return属性为真时,用户才可以使用ENTER键来开始新行。 多行文本编辑控件也可以拥有自己的滚动条。具有滚动条的编辑控件处理自己的滚动条消息,而不具有滚动条的编辑控件也可以由父窗口发送的滚动条消息。 类型:布尔值 默认值:假 |
Number: | 用户不能输入非数字字符。类型:布尔值 默认值:假 |
Horizontal scroll: | 为多行控件提供水平滚动条。类型:布尔值 默认值:假 |
Auto HScroll: | 当用户输入的字符超过了编辑框的右边界时自动水平向右滚动文本。类型:布尔值 默认值:真 |
Vertical scroll: | 为多行控件提供垂直滚动条。类型:布尔值 默认值:假 |
Auto VScroll: | 在多行控件中,当用户在最后一行按下ENTER键时自动向上滚动文本 |
Password: | 当用户键入时将所有字符显示为星号(*)。该属性对于多行控件不可用。类型:布尔值 默认值:假 |
No hide selection: | 改变当编辑框失去和重新获得焦点时文本的显示方式。如果该属性为真,在编辑框中选中的文本在任何时候都显示为选中状态(即反白状态)。类型:布尔值 默认值:假 |
OEM convert: | 将键入的文本从Windows字符集转换为OEM字符集,再转换回Windows字符集。该操作确认应用程序在调用AnsiToOem函数将编辑框中的字符串转换为OEM字符串时进行正确的字符转换,因此该样式对于包括文件名的编辑控件特别有用。类型:布尔值 默认值:假 |
Want return: | 指定当用户在多行编辑控件中按下ENTER键时插入一个回车符,否则用户按下ENTER将被解释为选择了对话框中的默认命令按钮。该样式对于单行编辑框控件没有任何影响。类型:布尔值 默认值:假 |
Border: | 在编辑框边缘创建边框。类型:布尔值 默认值:真 |
Uppercase: | 将用户在编辑框中输入的字符转换为大写。类型:布尔值 默认值:假 |
Lowercase: | 将用户在编辑框中输入的字符转换为小写。 类型:布尔值 默认值:假 |
Read-only: | 防止用户编辑和更改编辑框中的文本。类型:布尔值 默认值:假 |
相比我们在前面所讲述的几个类CButton、CBitmapButton和CStatic而言,封装标准编辑控件的MFC类CEdit要复杂得多。表给出了在类CEdit中定义的成员函数:
表6. 25 类CEdit中定义的成员函数
成员函数 | 描述 |
CEdit | 构造CEdit控件对象 |
Create | 创建Windows编辑控件,并将其与CEdit对象相关联 |
GetSel | 获得编辑控件中当前选择的开始和结束字符的位置 |
ReplaceSel | 使用特定的文本来替换编辑控件中的当前选择 |
SetSel | 设置编辑控件中所选定的字符范围 |
Clear | 删除编辑控件中当前选定的字符 |
Copy | 使用CF_TEXT格式将编辑控件中当前选定的文本复制到剪贴板 |
Cut | 删除当前选定的字符,并将所删除的字符复制到剪贴板 |
Paste | 将剪贴板中格式为CF_TEXT的数据(如果有的话)插入到编辑框中的当前位置。 |
Undo | 撤销最后一次编辑操作 |
CanUndo | 决定编辑控件的操作是否可以被撤销 |
EmptyUndoBuffer | 重置编辑控件的undo标志 |
GetModify | 判断编辑控件中的内容是否被修改过 |
SetModify | 设置或清除编辑控件中的修改标志 |
SetReadOnly | 设置编辑控件的只读状态 |
GetPasswordChar | 当用户输入文本时获得编辑控件中显示的密码字符 |
SetPasswordChar | 设置或移去当用户输入文本时编辑控件中显示的密码字符 |
GetFirstVisibleLine | 获得编辑控件中最上面的可见行 |
LineLength | 获得编辑控件中一行的长度 |
LineScroll | 滚动多行编辑控件中的文本 |
LineFromChar | 获得包含指定索引字符的行的行号 |
GetRect | 获得编辑控件的格式矩形 |
LimitText | 限制用户可以在编辑控件中输入的文本的长度 |
GetLineCount | 获得多行编辑控件中行的数目 |
GetLine | 获得编辑控件中的一行文本 |
LineIndex | 获得多行编辑控件中一行的字符索引 |
FmtLines | 在多行编辑控件中设置是否包含软换行符的开关 |
续表6.25
成员函数 | 描述 |
SetTabStops | 在多行编辑控件中设置制表位 |
SetRect | 设置多行文本编辑控件的格式矩形,并更新控件 |
SetRectNP | 设置多行文本编辑控件的格式矩形,但不重绘控件窗口 |
GetHandle | 获得为多行编辑控件分配的内存的句柄 |
SetHandle | 设置供多行编辑控件使用的本地内存句柄 |
GetMargins | 获得当前CEdit对象的左右页边距 |
SetMargins | 设置当前CEdit对象的左右页边距 |
GetLimitText | 获得当前CEdit对象可以包括的最大文本量 |
SetLimitText | 设置当前CEdit对象可以包括的最大文本量 |
CharFromPos | 获得最接近于指定位图的行和字符的索引 |
PosFromChar | 获得指定字符索引的左上角的坐标 |
上面的成员函数涵盖了编辑控件在使用中的很多方面,可以满足我们在很多情况下的绝大部分需要。这里要注意的是,一些CWnd中定义的成员函数也是很重要的,比如说我们常用CWnd的成员函数GetWindowText和SetWindowText来获取和设置编辑控件的文本,使用成员函数GetFont和SetFont来获取和设置编辑控件显示文本时所使用的字体。
编辑控件可以向父窗口发送的通知消息也要比前面讲述的几种控件多。这些消息有:
|
| |
ON_EN_CHANGE:ON_EN_ERRSPACE: | 编辑控件不能按选定需要分配足够的内存 | |
ON_EN_HSCROLL: | 用户单击了编辑控件中的水平滚动条。父窗口在屏幕更新前获得此消息 | |
ON_EN_KILLFOCUS: | 编辑控件失去输入焦点 | |
ON_EN_MAXTEXT: | 当前插入内容超过了编辑控件中的指定的字符数,该插入内容已被裁剪。如果控件没有设置ES_AUTOHSCROLL样式,那么在插入的字符超出了编辑控件的宽度也发送该通知消息。同样,如果控件没有指定ES_AUTOVSCROLL样式,该通知也以插入操作导致总行数超过编辑控件的高度时发送。 | |
ON_EN_SETFOCUS: | 编辑按钮获得输入焦点 | |
ON_EN_UPDATE: | 控件已对文本作了格式化,但尚未显示文本。通常可以处理该消息以决定是否需要对窗口的大小作改变等。 | |
ON_EN_VSCROLL: | 用户单击了编辑控件的垂直滚动条。父窗口在屏幕更新前收到该消息。 |
示例程序EditDemo演示了编辑控件的一般使用方法。按如下步骤创建该工程:
1. 使用AppWizard创建基于对话框的工程EditDemo。
2. 向工程中添加菜单资源IDR_MAINMENU,该菜单资源包括两个顶层菜单项“文件(&F)”和“编辑(&E)”,“文件(&F)”下包括如图6.41所示的菜单命令。各菜单命令(不包括具有Separator样式的菜单项)的资源ID依次为ID_FILE_NEW和ID_FILE_EXIT。“编辑(&E)”菜单下包括如图6.42所示菜单命令。各菜单命令的资源ID依次为ID_EDIT_UNDO、ID_EDIT_CUT、ID_EDIT_COPY、ID_EDIT_PASTE、ID_EDIT_DEL、ID_EDIT_SELECTALL和ID_EDIT_SETFONT。
图6. 41 “文件”菜单下的菜单命令
图6. 42 “编辑”菜单下的菜单命令
3. 按图6.43在应用程序的主对话框上绘制编辑框(对应于Control工具箱中的图标为),设置其ID为IDC_EDIT,并将其Multiline属性、Auto VScroll属性和Want return属性设置为真,同时将Auto HScroll属性设置为假。这里,编辑框IDC_EDIT在大小和位置并不重要,我们将在程序中对其进行调整。
4. 删除原有的“确定”按钮和“取消”按钮。接着打开对话框本身的属性对话框,从Menu下拉列表框中选择IDR_MAINMENU。
图6. 43 设置主对话框的Menu属性
5. 在资源管理器中打开菜单资源IDR_MAINMENU,如图6.44所示。在任一菜单项上单击鼠标右键,选择命令ClassWizard。这时ClassWizard将弹出如图6.45所示的对话框,单击Cancel。在Object IDs处选择ID_FILE_EXIT,在Messages处选择COMMAND,单击And function按钮并接受默认的处理函数名OnFileExit,在函数OnFileExit中调用类CDialog的成员函数OnCancel,如下面的代码所示:
void CEditDemoDlg::OnFileExit()
{
// 调用基类成员函数 OnCancel 终止对话框
OnCancel();
}
按同样的方法为ID_FILE_NEW的COMMAND命令添加处理函数OnFileNew如下:
void CEditDemoDlg::OnFileNew()
{
// 将编辑控件中的文本初始化为零,
// 并清除其撤消缓冲区。
CEdit *pEdit=(CEdit*)GetDlgItem(IDC_EDIT);
pEdit->SetWindowText("");
pEdit->EmptyUndoBuffer();
}}
图6. 44 在Developer Studio的资源编辑器中打开菜单资源IDR_MAINMENU
为ID_EDIT_UNDO的COMMAND命令添加处理函数OnEditUndo如下:
void CEditDemoDlg::OnEditUndo()
{
// 直接调用类 CEdit 的成员函数 Undo
CEdit *pEdit=(CEdit*)GetDlgItem(IDC_EDIT);
pEdit->Undo();
}
图6. 45 询问是否将菜单IDR_MAINMENU与某一视类相关联
为ID_EDIT_CUT的COMMAND命令添加处理函数OnEditCut如下:
void CEditDemoDlg::OnEditCut()
{
// 直接调用类 CEdit 的成员函数 Cut
((CEdit*)GetDlgItem(IDC_EDIT))->Cut();
}
为ID_EDIT_COPY的COMMAND命令添加处理函数OnEditCopy如下:
void CEditDemoDlg::OnEditCopy()
{
// 直接调用类 CEdit 的成员函数 Copy
((CEdit*)GetDlgItem(IDC_EDIT))->Copy();
}
为ID_EDIT_PASTE的COMMAND命令添加处理函数OnEditPaste如下:
void CEditDemoDlg::OnEditPaste()
{
// 直接调用类 CEdit 的成员函数 Paste
((CEdit*)GetDlgItem(IDC_EDIT))->Paste();
}
为ID_EDIT_DEL的COMMAND命令添加处理函数OnEditDel如下:
void CEditDemoDlg::OnEditDel()
{
// 直接调用类 CEdit 的成员函数 Clear
((CEdit*)GetDlgItem(IDC_EDIT))->Clear();
}
为ID_EDIT_SELECT的COMMAND命令添加处理函数OnEditSelectall如下:
void CEditDemoDlg::OnEditSelectall()
{
int nStart,nEnd;
// 设置选定字符的开始
nStart=0;
// 设置选定字符的结尾。函数 GetWindowTextLength 返回编辑控件中文本的长度
nEnd=((CEdit*)GetDlgItem(IDC_EDIT))->GetWindowTextLength();
// 以 nStart 和nEnd 为参数调用类 CEdit 的成员函数 SetSel
((CEdit*)GetDlgItem(IDC_EDIT))->SetSel(nStart,nSel);
}
为ID_EDIT_SETFONT的COMMAND命令添加处理函数OnEditSetfont如下:
void CEditDemoDlg::OnEditSetfont()
{
LOGFONT lf;
static CFont font;
// 获得编辑框原来使用的字体信息,并使用该信息初始化字体对话框
CEdit *pEdit=(CEdit*)GetDlgItem(IDC_EDIT);
pEdit->GetFont()->GetLogFont(&lf);
CFontDialog dlg(&lf);
// 弹出字体对话框以供用户选择新的字体,
// 并在用户确认的情况下更改编辑控件所使用的字体。
if (dlg.DoModal()==IDOK)
{
dlg.GetCurrentFont(&lf);
font.DeleteObject();
font.CreateFontIndirect(&lf);
pEdit->SetFont(&font);
}
}
在成员函数OnEditSetfont中所使用的方法和技巧已在第三节的末尾讲述如何为按钮控件设置字体时进行了介绍。因此对于函数OnEditSetfont我们不进行详细的注解。
6. 考虑下面的情况:如果当前没有可供撤消的操作,“编辑”菜单下的“撤消”应该处于不可用(变灰)状态;同样的,如果当前编辑控件中没有选定任何文本,那么“剪贴”、“复制”以及“删除”命令也应该不可用;如果当前剪贴板中没有任何文本数据,“粘贴”命令应该不可用。我们通过为消息WM_INITMENUPOPUP添加消息处理函数来设置各菜单命令的可用状态。该消息在用户单击某菜单之后在菜单项弹出之前发送。
对于类CEditDemoDlg,我们不能使用ClassWizard来为消息WM_INITMENUPOPUP添加消息处理函数,但事实上,对话框也可以接收到消息WM_INITMENUPOPUP。这里,我们可以手动来添加相应的消息映射项。
第一步是在类CEditDemoDlg的定义中添加消息处理函数
afx_msg void OnInitMenuPopup( CMenu* pPopupMenu, UINT nIndex, BOOLbSysMenu );
可以把该处理函数的声明添加到由ClassWizard生成的消息处理函数的后面。由ClassWizard生成的消息处理函数位于两行注释标记//{{AFX_MSG和//}}AFX_MSG之间。同我们在此之前强调过的一样,不要将OnInitMenuPopup的声明添加到两行注释之间。以后如果再遇到与此相似的情况,我们将不再强调。
接着添加相应的消息映射入口,在类CEditDemoDlg的实现文件EditEemoDlg.cpp中找到宏BEGIN_MESSAGE_MAP(CEditDemoDlg, CDialog),在它之后,宏END_MESSAGE_MAP之前添加下面的宏代码:
ON_WM_INITMENUPOPUP()
我们仍应将上面的代码添加到注释标记//{{AFX_MSG_MAP和//}}AFX_MSG_MAP之外。同样的,以后如果再遇到这种情况我们将不再强调。
最后添加函数OnInitMenuPopup的定义:
void CEditDemoDlg::OnInitMenuPopup( CMenu* pPopupMenu, UINT nIndex, BOOLbSysMenu )
{
CEdit *pEdit=(CEdit*)GetDlgItem(IDC_EDIT);
// 当用户单击的是窗口的控制菜单时 bSysMenu 参数为真,否则为假
if (!bSysMenu)
{
// 检查编辑控件是否有可撤消的操作
if (pEdit->CanUndo())
{
pPopupMenu->EnableMenuItem(ID_EDIT_UNDO,MF_ENABLED);
}
else
{
pPopupMenu->EnableMenuItem(ID_EDIT_UNDO,MF_GRAYED);
}
// 检查编辑控件中是否有选定的文本
int nStart,nEnd;
pEdit->GetSel(nStart,nEnd);
if (nStart==nEnd)
{
pPopupMenu->EnableMenuItem(ID_EDIT_CUT,MF_GRAYED);
pPopupMenu->EnableMenuItem(ID_EDIT_COPY,MF_GRAYED);
pPopupMenu->EnableMenuItem(ID_EDIT_DEL,MF_GRAYED);
}
else
{
pPopupMenu->EnableMenuItem(ID_EDIT_CUT,MF_ENABLED);
pPopupMenu->EnableMenuItem(ID_EDIT_COPY,MF_ENABLED);
pPopupMenu->EnableMenuItem(ID_EDIT_DEL,MF_ENABLED);
}
// 检查剪贴板中是否有文本格式的数据可供粘贴
// 该过程通过调用 Win32 API 函数 IsClipboardFormatAvailable 来实现
if (IsClipboardFormatAvailable(CF_TEXT))
{
pPopupMenu->EnableMenuItem(ID_EDIT_PASTE,MF_ENABLED);
}
else
{
pPopupMenu->EnableMenuItem(ID_EDIT_PASTE,MF_GRAYED);
}
}
}
7. 最后我们希望一点,就是说用户可以改变对话框的大小,而且当用户改变对话框的大小时,编辑框自动的改变其大小以适应父窗口大小的变化。方法是为WM_SIZE添加消息处理函数。在进行这一步操作之前,打开对话框的DialogProperties对话框,在Styles选项卡中将其Border属性设置为Resizing (即可以改变大小),同时将Maximize box属性值设置为真。然后,使用ClassWizard为消息WM_SIZE添加消息处理函数OnSize,其定义如下:
void CEditDemoDlg::OnSize(UINT nType, int cx, int cy)
{
// 调用基类的 OnSize 成员函数
CDialog::OnSize(nType, cx, cy);
CRect rect;
// 获得父窗口的客户区矩形
GetClientRect(&rect);
CEdit *pEdit=(CEdit*)GetDlgItem(IDC_EDIT);
if (pEdit)
{
// 改变编辑控件的大小以适应父窗口大小的改变
pEdit->MoveWindow(&rect);
}
}
由于OnSize会在对话框第一次显示时被调用,因此使用if语句检查pEdit是否为NULL是必要的。出于同样的目的,我们还需要使用下面的代码来替换成员函数OnInitDialog中的// TODO注释:
CRect rect;
GetClientRect(&rect);
CEdit *pEdit=(CEdit*)GetDlgItem(IDC_EDIT);
if (pEdit)
{
pEdit->MoveWindow(&rect);
}
它在对话框第一次显示时完成与上面的OnSize成员函数同样的操作。以保证在第一次显示对话框时编辑框控件以正确的大小进行显示。
编译并运行上面的程序(如图6.46),并测试其各项功能是否正常。
图6. 46 示例程序EditDemo的运行结果
第六节 列表框控件
列表框控件通常用来列出一系列可供用户从中进行选择的项,这些项一般来说都在字符串的形式给出,但也可以采用其它的形式,如图形等。列表框可以只允许单一选择,也就是说用户同时只能选择所有列表项中的一项;除此之外,列表框也可以是多项选择的,用户可以在多项选择列表框中选择多于一项的列表项。当用户选择了某项时,该项被反白显示,同时列表框向父窗口发送一条通知消息。MFC类CListBox封装了Windows标准列表框控件,其成员函数(参见表6.26)提供了对标准列表框的绝大多数操作。
表6. 26 在类CListBox中定义的成员函数
成员函数 | 描述 |
AddString | 向列表框中添加字符串 |
CharToItem | 为不包含字符串的自绘制列表框提供对WM_CHAR的定制处理 |
CListBox | 构造一个CListBox对象 |
CompareItem | 由框架调用以决定新添加的项在有序自绘制列表框中的位置 |
Create | 创建一个Windows列表框控件,并将它与CListBox对象相关联 |
DeleteItem | 当用户从自绘制列表框中删除一项时由框架调用 |
DeleteString | 从列表框中删除字符串 |
Dir | 从当前目录向列表框中添加文件名 |
DrawItem | 当自绘列表框的可视部分改变时由框架调用 |
FindString | 在列表框中查询指定的字符串 |
FindStringExact | 查找与指定字符串相匹配的第一个列表框字符串 |
GetAnchorIndex | 返回列表框中当前“锚点”项的基于零的索引 |
续表6.26
成员函数 | 描述 |
GetCaretIndex | 在多重选择列表框中获得当前拥有焦点矩形的项的索引 |
GetCount | 返回列表框中字符串的数目 |
GetCurSel | 返回列表框中当前选择字符串的基于零的索引值 |
GetHorizontalExtent | 以象素为单位返回列表框横向可滚动的宽度 |
GetItemData | 返回下列表框项相关联的32位值 |
GetItemDataPtr | 返回指向列表框项的指针 |
GetItemHeight | 决定列表框中项的高度 |
GetLocale | 获得列表框使用的区域标识符 |
GetSel | 返回列表框项的选定状态 |
GetSelItems | 返回当前选定字符串的索引 |
GetSelCount | 在多重选择列表框中获得当前选定字符串的数目 |
GetText | 拷贝列表框项到缓冲区 |
GetTextLen | 以字节为单位返回列表框项的长度 |
GetTopIndex | 返回列表框中第一个可视项的索引 |
InitStorage | 为列表框项和字符串预先分配内存 |
InsertString | 在列表框中的指定位置插入一个字符串 |
ItemFromPoint | 返回与指定点最接近的列表框项的索引 |
MeasureItem | 当自绘列表框创建时由框架调用以获得列表框的尺寸 |
ResetContent | 从列表框中清除所有的项 |
SelectString | 从单项选择列表框中查找并选定一个字符串 |
SelItemRange | 在多重选择列表框中选中某一范围的字符串或清除某一范围的字符串的选定状态 |
SetAnchorIndex | 在多重选择列表框的设置扩展选定的起点(“锚点”项) |
SetCaretIndex | 在多重选择列表框中设置当前拥有焦点矩形的项的索引 |
SetColumnWidth | 设置多列列表框的列宽 |
SetCurSel | 在列表框中选定一字符串 |
SetHorizontalExtent | 以象素为单位设置列表框横向可滚动的宽度 |
SetItemHeight | 设置列表框中项的高度 |
SetItemRect | 返回列表框项当前显示的边界矩形 |
SetLocale | 为列表框指定区域标识符 |
续表6.26
成员函数 | 描述 |
SetSel | 在多重选择列表框中选定一列表框项或清除某一列表框项的选定状态 |
SetTabStops | 设置列表框的制表位 |
SetTopIndex | 设置列表框中第一个可视项的基于零的索引 |
VKeyToItem | 为具有LBS_WANTKEYBOARDINPUT样式的列表框提供定制的WM_KEYDOWN消息处理 |
以下是列表框可能向父窗口发送的通知消息及其说明:
ON_LBN_DLBCLK: | 用户双击了列表框中的字符串。仅当列表框具有LBS_NOTIFY样式时会发送该通知消息 |
ON_LBN_ERRSPACE: | 列表框不能按需要分配足够的内存 |
ON_LBN_KILLFOCUS: | 列表框失去输入焦点 |
ON_LBN_SELCANCEL: | 列表框中的当前选择被取消。仅当列表框具有LBS_NOTIFY样式时才会发送该通知消息 |
ON_LBN_SELCHANGE: | 列表框中的选择将被更新。需要注意的是,当使用成员函数CListBox::SetCurSel时不会发送该通知消息,同时,该消息也仅当列表框具有LBS_NOTIFY样式才会发送。对于多重选择列表框,当用户按下光标键时,即使所选择的内容没有改变,也会发送LBN_SELCHANGE通知消息。 |
ON_LBN_SETFOCUS: | 列表框获得输入焦点 |
ON_WM_CHARTOITEM: | 不包括字符串的列表框收到WM_CHAR消息 |
ON_WM_VKEYTOITEM: | 具有LBS_WANTKEYBOARDINPUT样式的列表框接收到WM_KEYDOWN消息 |
在资源编辑器中,对应于列表框的Control工具箱图标为。在绘制列表框的同时可以在Properties属性对话框中指定其属性。除了在前面几节中所讲述的以外,我们还可以为列表框设置以下的属性,这些属性可以在Styles选项卡中设置。
Selection: | 决定列表框的选择方式。可以设置的值如下: Single:用户同时只能选择列表框中的一项 Multiple:用户可以同时选择多于一个的列表框项,但不可以从开始项扩展选定内容。在鼠标单击时可以使用SHIFT键和CTRL键选定和取消选定,同时选定项不一定需要连续。单击或双击未选定项时将选定该项;单击或双击已选定项时将取消对该项的选定。 Extended:用户可以通过拖动来扩展选定内容。用户可以鼠标和SHIFT键和CTRL键进行选定或取消选定,选择成组的项或不连续的项。 默认值为Single。 |
Owner draw: | 控制列表框的自绘特性。可以设置的值如下: No:关闭自绘制样式,列表框中包含的内容为字符串。 Fixed:指定列表框的所有者负责绘制其内容,并且列表框中的项具有相同的高度。 Variable:指定列表框的所有者负责绘制其内容,并且列表框的项具有不同的高度。 当列表框创建时CWnd::OnMeasureItem将被调用;当列表框的可视部分改变时CWnd::OnDrawItem将被调用。 默认值为No。 |
Has strings: | 指定自绘制列表框包括由字符串组成的项。列表框为字符串维护内存和指针,因此应用程序可以使用LB_GETTEXT消息来获得特定项的文本。在默认情况下,除了自绘制按钮以外,所有的列表框都具有该项属性。由应用程序创建的自绘制列表框可以具有或不具有该样式。 该样式仅当自绘制属性被设置为Fixed或Variable时可用。如果自绘制属性被设置为No,列表框在默认情况下包括字符串。 类型:布尔值 默认值为假 |
Sort: | 以字母为序对列表框内容进行排序。 类型:布尔值 默认值为真 |
Notify: | 如果列表项被单击或双击时通知父窗口。 类型:布尔值 默认值为真 |
Multi-colume: | 指定多列列表框,多列列表框可以在水平方向上进行滚动。消息LB_SETCOLUMNWIDTH用来设置列宽。 类型:布尔值 默认值为假 |
Horizontal scroll: | 创建具有水平滚动条的列表框。类型:布尔值 默认值为假 |
Vertical scroll: | 创建具有垂直滚动条的列表框。类型:布尔值 默认值为真 |
No redraw: | 指定当发生改变时列表框外观不进行更新。可以通过发送WM_SETREDRAW消息或调用CWnd::SetRedraw函数改变该属性。 类型:布尔值 默认值为假 |
Use tabstops: | 允许列表框在绘制字符串辨认和扩展制表符。默认的制表位为32个对话框单位(DLU)。类型:布尔值 默认值为假 |
Want key input: | 指定当用户有按键动作并且列表框具有输入焦点时列表框的所有者收到WM_VKEYTOITEM和WM_CHARTOITEM消息,以允许应用程序在使用键盘输入时进行特定的处理。如果列表框具有了Has Strings样式,列表框将接收到WM_VKEYTOITEM消息;如果列表框不具有WM_CHARTOITEM消息,则列表框将接收到WM_CHARTOITEM消息。 类型:布尔值 默认值为假 |
Disable no scroll: | 当列表框不具有足够多的项时显示不可用的滚动条。如果不使用该属性,在这种情况下将不使用滚动条。类型:布尔值 默认值为假 |
No integral height: | 设置对话框的大小严格等于创建对话框时由应用程序指定的大小。一般情况下,Windows改变列表框的大小以使得它不会只显示某一项的一部分,即列表框客户区的高度为项高的整数倍。 类型:布尔值 默认值为真 |
下面的示例程序演示了列表框控件的使用。
1. 使用AppWizard创建名为ListBoxDemo的基于对话框的MFC应用程序工程。
2. 按图6.47设计应用程序的主对话框。各控件的属性值如表6.27所示。
图6. 47 应用程序ListBoxDemo的主对话框
3. 单击Insert菜单下的Resource命令,插入ID为IDD_INPUT的对话框,按图6.48添加对话框的各个控件。
图6. 48 应用程序ListBoxDemo的IDD_INPUT对话框
各控件的属性如表6.28所示。
4. 为对话框IDD_INPUT创建新的对话框类CInputDlg。方法是在资源编辑器中打开对话框IDD_INPUT,此时按下Ctrl+W键打开ClassWizard,由于尚没有类与对话框IDD_INPUT相关联,因此ClassWizard将弹出如图6.49所示的对话框,询问是否为对话框创建新的类。选择Create a newclass (这也是默认选项),单击OK,弹出如图6.50所示的New class对话框。在Name处输入CInputDlg,其余采用默认设置,单击OK即为对话框IDD_INPUT创建了新类CInputDlg。这时就可以使用ClassWizard的Member Variables选项卡为对话框进行如表6.28所示的成员变量映射了。
表6. 27 应用程序ListBoxDemo主对话框各控件的属性设置
控件类型 | 资源ID | 控件标题 | 其他 |
列表框 | IDC_LISTSELECTABLE |
| 位于图6.47左边的列表框,其Selection属性为Extended。对应的DDX变量映射(使用ClassWizard的Member Vari-ables选项卡进行设置)为CListBox类型变量m_lsSelectable。 |
IDC_LISTSELECTED |
| 位于图6.47右边的列表框,其Selection属性为Extended。对应的DDX变量映射为CListBox类型变量m_lsSelected。 | |
静态控件 | (无需更改) | 待选择的文件 |
|
已选择的文件 |
| ||
下压按钮 | IDC_BTNCHANGEDIR | <- 改变目录(&H) |
|
IDC_BTNADD | 添加到(&A) -> |
| |
IDC_BTNDEL | 删除(&D) <- |
| |
IDC_BTNCLEAR | 全部清除(&L) <- |
|
表6. 28 对话框IDD_INPUT各控件的属性设置
控件类型 | 资源ID | 控件标题 | 其他 |
静态控件 | IDC_PROMPT | 提示字符串 | 对应的DDX变量映射为CString类型成员变量m_strPrompt |
编辑框 | IDC_INPUT |
| 对应的DDX变量映射为CString类型成员变量m_strInput |
接着为类添加类型为CString的保护成员变量m_strTitle。然而在类CInputDlg中添加成员函数GetInput的声明:
CString GetInput(LPCTSTR lpszTitle="输入",
LPCTSTR lpszPrompt="请在下面的文本框中输入字符串: ");
该函数显示对话框IDD_INPUT (如图6.51所示),并返回用户在对话框中输入的字符串,如果用户单击了输入对话框的“取消”按钮,则函数返回空字符串,参数lpszTitle为输入对话框的标题,lpszPrompt为输入对话框的提示字符串。其实现如下:
图6. 49 询问是否为对话框IDD_INPUT创建新类
图6. 50 为对话框IDD_INPUT创建新类
图6. 51 输入对话框
CString CInputDlg::GetInput(LPCTSTR lpszTitle, LPCTSTR lpszPrompt)
{
// 设置标题字符串和提示字符串
m_strTitle=lpszTitle;
m_strPrompt=lpszPrompt;
// 显示输入对话框并返回用户输入的字符串
if (DoModal()==IDOK)
{
return m_strInput;
}
else
{
return CString("");
}
}
为类CInputDlg重载OnInitDialog成员函数
BOOL CInputDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// TODO: 在这里添加额外的初始化代码
SetWindowText(m_strTitle);
GetDlgItem(IDC_INPUT)->SetFocus();
// 由于为控件 IDC_INPUT 设置了输入焦点,因此函数 OnInitDialog 应该返回 FALSE
return FALSE;
}
成员函数OnInitDialog的重载版本设置输入对话框的标题文本和提示字符串。
5. 用下面的代码替代类CListBoxDemoDlg的OnInitDialog成员函数中的// TODO注释:
m_lsSelectable.ResetContent();
m_lsSelectable.Dir(0x17,"*.*");
上面的代码先调用成员函数ResetContent清除列表框IDC_LISTSELECTABLE中的所有项,再调用成员函数Dir使用当前目录下的文件名来填充该列表框。第一个参数0x17是文件类型屏蔽位,它等于0x01|0x02|0x04|0x10,它包括了所有常规属性文件、只读文件、系统文件和目录名,第二个参数为所显示的文件名,在参数中可以使用通配符。
为按钮IDC_BTNCHANGEDIR的BN_CLICKED命令添加下面的处理函数OnBtnChangeDir:
void CListBoxDemoDlg::OnBtnChangeDir()
{
CInputDlg dlg;
CString str=dlg.GetInput("输入目录","输入新的目录名:");
if (str!="" && str.Left(1)!="\\")
{
str+="\\";
}
if (str!="")
{
m_lsSelectable.ResetContent();
int iResult=m_lsSelectable.Dir(0x17,str+"*.*");
if (iResult==LB_ERR)
{
MessageBox("添加文件名出错!");
}
else if (iResult==LB_ERRSPACE)
{
MessageBox("无法为列表框分配足够的内存!");
}
}
}
上面的代码首先定义一个类型为CInputDlg的成员变量,然后调用其成员函数GetInput (我们已在前面讨论过该成员函数)获得用户输入的列表目录名,如果用户输入的目录名不为空字符串,则调用类CListBox的成员函数将指定目录下的文件名添加到列表框IDC_LISTSELECTABLE中,如果添加失败,则弹出相应的出错信息。
为按钮IDC_BTNADD的BN_CLICKED命令添加下面的处理函数OnBtnAdd:
void CListBoxDemoDlg::OnBtnAdd()
{
CString str;
for (int i=0; i<m_lsSelectable.GetCount(); i++)
{
if (m_lsSelectable.GetSel(i))
{
m_lsSelectable.GetText(i, str);
m_lsSelected.AddString(str);
}
}
}
其中,类CListBox的成员函数GetCount返回了列表框中项的数目,然后使用GetSel成员函数获得每一项的选定状态,这里要注意列表框中项的索引是基于零的。如果该项的已被选定(即GetSel成员函数返回真值),则使用GetText成员函数获得该项的文本,并将它放到CString类型的变量str中,接着,调用类CListBox中定义的成员函数AddString将字符串str添加到列表框IDC_LISTSELECTED中。
为按钮IDC_BTNDEL的BN_CLICKED命令添加如下的处理函数OnBtnDel:
void CListBoxDemoDlg::OnBtnDel()
{
for (int i=m_lsSelected.GetCount()-1; i>-1; i--)
{
if (m_lsSelected.GetSel(i))
{
m_lsSelected.DeleteString(i);
}
}
}
上面的代码从最末一项开始,检查列表框IDC_LISTSELECTED中每一项的选定状态,如果发现该项被选定,则将它从列表框中删除。从列表框中删除一项使用类CListBox的成员函数DeleteString,其参数为所删除项的索引值。
- 注意:
- 我们在上面的代码中使用的for循环为
- for (int i=m_lsSelected.GetCount()-1; i>-1; i--)
- {
- ...
- }
而不是
- for (int i=0; i<m_lsSelected.GetCount(); i++)
- {
- ...
- }
这是因为成员函数DeleteString的使用将导致所删除项之后的所有项的索引值发生改变,这里,如果所删除的项的下一项仍被选定的话,该项将不会被删除。与此相反,删除一项并不会导致此项之前的项的索引值发生改变,因此,从最末一项开始进行检查是可行的。
按钮IDC_BTNCLEAR的BN_CLICKED命令的处理成员函数OnBtnClear具有最简单的结构,它直接调用类CListBox的成员函数ResetContent删除列表框IDC_LISTSELECTED中的所有项。
void CListBoxDemoDlg::OnBtnClear()
{
m_lsSelected.ResetContent();
}
图6. 52 示例程序ListBoxDemo的运行结果
编译并运行上面的示例程序,其运行结果如图6.52所示。单击“改变目录”按钮,输入一个新的目录名,查看左边列表框中项的改变情况。从左边列表框中选定若干项,单击“添加到”,将所选定的项添加到右边列表框(注意列表框中可以包括相同字符串的项)。再从右边列表框中选定若干项,验证按钮“删除”和“全部清除”是否正常工作。
第七节 组合框
组合框(combo box)可以看作是一个编辑框或静态文本框与一个列表框的组合,组合框的名称也正是由此而来。当前选定的项将显示在组合框的编辑框或静态文本框中。如果组合框具有下拉列表(drop-down list)样式,则用户可以在编辑框中键入列表框中某一项的首字母,在列表框可见时,与该首字母相匹配的最近的项将被加亮显示。
组合框对应于Controls工具箱内的按钮为。在绘制组合框的同时可以使用控件的Properties对话框设置控件的各种属性样式。一些样式已在前面的几节中作了介绍,因此这里不再重复,下面给出一些在前面的内容中没有进行说明的样式及其含义:
Type: | 指定组合框的类型。可以使用的类型如下: Simple:创建包括编辑框控件和列表框的简单组合框,其中编辑框控件用来接受用户的输入。 Dropdown:创建下拉组合框。该类型与简单组合框类似。但仅当用户单击了编辑框控件部分右边的下拉箭头时组合框的列表框部分才被显示。 Drop List:该类型类似于下拉样式(drop-down),只是使用静态文本项代替编辑框控件来显示列表框中的当前选择。 默认值为Dropdown。 |
Uppercase: | 将选择域或列表中的所有文本转换为大写。 类型:布尔值 默认值为假 |
Lowercase: | 将选择域或列表中的所有文本转换为小写。 类型:布尔值 默认值为假 |
与列表框不同的是,在绘制组合框的同时可以预先为组合框添加一些可选项,方法是单击Properties对话框中的Data选项卡(如图所示),直接在Enterlistbox items处键入组合框中的可选项,每一行为一个选项,使用Ctrl+Enter键开始新的一行。在运行时这些选项将出现在组合框的列表框中。
图6. 53 为组合框预置选项
MFC类CComboBox封装了Windows标准组合框,其成员函数提供了对组合框控件的常见操作的实现。表给出了对在类CListBox中定义的成员函数的描述。
表6. 29 在类CListBox中定义的成员函数
成员函数 | 描述 |
CComboBox | 构造一个CComboBox对象 |
Create | 创建一个组合框并将它与CComboBox对象相关联 |
InitStorage | 为组合框的列表框部分的项和字符串预先分配内存块 |
GetCount | 获得组合框中列表框项的数目 |
GetCurSel | 如果存在的话,返回组合框中列表框的当前选定项的索引 |
SetCurSel | 选择组合框中列表框内的一条字符串 |
GetEditSel | 获得组合框中编辑控件的当前选定的起始和终止字符位置 |
SetEditSel | 在组合框的编辑控件中选定字符 |
SetItemData | 设置与组合框中指定项相关联的32位值 |
SetItemDataPtr | 将与组合框中指定项相关联的32位值设置为指定的void指针 |
GetItemData | 获得由应用程序提供的与指定组合框项相关联的32位值 |
GetItemDataPtr | 以void指针的形式返回由应用程序提供的与指定组合框项相关联的32位值 |
GetTopIndex | 返回组合框中列表框部分的第一个可视项的索引 |
SetTopIndex | 在组合框中的列表框部分的顶部显示指定索引对应的项 |
SetHorizontalExtent | 以象素为单位指定组合框的列表框部分可以横向滚动的宽度 |
GetHorizontalExtent | 以象素为单位获得组合框中列表框部分可以横向滚动的宽度 |
SetDroppedWidth | 为组合框的下拉列表框部分设置最小允许宽度 |
GetDroppedWidth | 获得组合框的下拉列表框部分的最小允许宽度 |
Clear | 如果存在的话,删除编辑控件中当前选定的内容 |
Copy | 如果存在的话,将当前选定以CF_TEXT格式复制到剪贴板 |
Cut | 如果存在的话,删除编辑控件中当前选定的内容,并将其以CF_TEXT格式复制到剪贴板 |
Paste | 当剪贴板包括CF_TEXT格式的数据时,从剪贴板复制数据到编辑控件的当前插入位置 |
LimitText | 设置用户可以在组合框的编辑控件中输入的文本的长度限制 |
SetItemHeight | 设置组合框中列表项的高度或编辑控件(或静态文本控件)部分的高度 |
GetItemHeight | 获得组合框中列表项的高度 |
GetLBText | 从组合框中的列表框获取字符串 |
续表6.29
成员函数 | 描述 |
GetLBTextLen | 获得组合框的列表框中某一字符串的长度 |
ShowDropDown | 对于具有CBS_DROPDOWN或CBS_DROPDOWNLIST属性的组合框,显示或隐藏其列表框 |
GetDroppedControlRect | 获得下拉组合框的可视(下拉)列表框的屏幕坐标 |
GetDroppedState | 判断下拉组合框的列表框是否可见(处理下拉状态) |
SetExtendedUI | 对于具有CBS_DROPDOWN或CBS_DROPDOWNLIST样式的组合框,选择默认用户界面或扩展用户界面 |
GetExtendedUI | 判断组合框具有默认用户界面还是扩展用户界面 |
GetLocale | 获得组合框的区域标识符 |
SetLocale | 设置组合框的区域标识符 |
AddString | 向组合框的列表框添加一字符串,对于具有CBS_SORT样式的组合框,新增加的字符串将被排序并插入到合适的位置,否则将被添加到列表框框的末尾 |
DeleteString | 从组合框的列表框中删除字符串 |
InsertString | 向组合框的列表框中插入一字符串 |
ResetContent | 清除组合框的列表框和编辑控件中的所有内容 |
Dir | 添加文件名列表到组合框的列表框中 |
FindString | 在组合框的列表框中查找包括指定前缀的第一个字符串 |
FindStringExact | 在组合框的列表框中查找与指定字符串匹配的字符串 |
SelectString | 在组合框的列表框中查找字符串,如果找到的话,在列表框中选择该字符串,并将字符串复制到编辑控件中 |
DrawItem | 当一个自绘制组合框的可视部分改变时由框架调用 |
MeasureItem | 在创建自绘制组合框时,由框架调用以判断组合框的尺寸 |
CompareItem | 当将一新项插入到排序的自绘制框中时由框架调用以判断项的相对位置 |
DeleteItem | 当一列表项被从自绘制组合框中删除时由框架调用 |
下面的示例程序演示了自绘制组合框的使用。
1. 使用AppWizard创建名为ComboDemo的基于对话框的工程,按图6.54添加工程的主对话框(IDD_COMBODEMO_DIALOG)中的各个控件。每个控件的属性如表6.30所示。
2. 在ClassView中用鼠标右击ComboDemo classes,选择New Class命令。上面的操作将弹出如图6.55所示的对话框,确认在Class type下拉列表框[注] 中选择了MFC Class。然后在Name处输入新的类名CClrComboBox,在Base class下拉列表框中选择CComboBox。如果需要修改新类的头文件或实现文件的文件名,可以单击Change按钮,这里,我们接受默认的文件名ClrComboBox.cpp和ClrComboBox.h。
图6. 54 工程ComboDemo的主对话框
表6. 30 对话框IDD_COMBODEMO_DIALOG的控件属性设置
控件类型 | ID | 属性值 |
组合框 | IDC_CLRCOMBO | Type:Dropdown Owner draw:Fixed Sort:真 Vertical scroll:真 Has string:假 |
下压按钮 | IDC_ADDCLR | Caption:添加颜色(&A) |
IDC_CHGCLR | Caption:改变颜色(&C) | |
静态控件 | IDC_STATICCLR | Caption属性值为空 |
3. 使用ClassWizard的Message Map选项卡在类CClrComboBox中重载基类的MeasureItem成员函数,其重载版本的代码如下:
void CClrComboBox::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
// 由于组合框具有 CBS_OWNERDRAWFIXED 样式,因此以 0 为参数调用成员函数
// GetItemHeight 获得每一项的固定高度
lpMeasureItemStruct->itemHeight=GetItemHeight(0);
}
图6. 55 从CComboBox派生新类CClrComboBox
函数MeasureItem在自绘制样式的组合框创建时由框架调用。该函数将每一项的高度放入MEASUREITEMSTRUCT结构的成员中。如果对话框以CBS_OWNERDRAWVARIABLE样式创建,框架将为列表框中的每一项调用一次该成员函数,否则,该成员函数只被调用一次。
接着,在CClrComboBox的重载基类的DrawItem成员函数,其代码如下:
void CClrComboBox::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
CDC* pDC=CDC::FromHandle(lpDrawItemStruct->hDC);
COLORREF cr=(COLORREF)lpDrawItemStruct->itemData;
// 注意到在出错的情况下,GetCurSel 和 GetItemData 返回 CB_ERR,而常量
// CB_ERR 被定义为 -1,这时不应把它视为一种系统颜色。
if (cr==CB_ERR)
cr=GetSysColor(COLOR_WINDOW);
if (lpDrawItemStruct->itemAction & ODA_DRAWENTIRE)
{
// 需要重绘整个项
// 以该项所对应的颜色填充整个项
CBrush br(cr);
pDC->FillRect(&lpDrawItemStruct->rcItem, &br);
// 反色居中显示该颜色的 RGB 组成
CString str;
str.Format("R: %d G: %d B: %d", GetRValue(cr), GetGValue(cr), GetBValue(cr));
CSize size;
size=pDC->GetTextExtent(str);
CRect rect=lpDrawItemStruct->rcItem;
COLORREF tcr;
tcr=~cr & 0x00FFFFFF; // 获得背景色的反色,不能简单的使用 ~cr
pDC->SetTextColor(tcr);
pDC->SetBkColor(cr);
pDC->TextOut(rect.left+(rect.Width()-size.cx)/2,
rect.top+(rect.Height()-size.cy)/2, str);
}
if ((lpDrawItemStruct->itemState & ODS_SELECTED) &&
(lpDrawItemStruct->itemAction & (ODA_SELECT | ODA_DRAWENTIRE)))
{
// 选中状态由未选中变为选中,其边框被加亮显示
COLORREF crHilite=~cr & 0x00FFFFFF;
CBrush br(crHilite);
pDC->FrameRect(&lpDrawItemStruct->rcItem, &br);
}
if (!(lpDrawItemStruct->itemState & ODS_SELECTED) &&
(lpDrawItemStruct->itemAction & ODA_SELECT))
{
// 选中状态由选中变为非选中,清除其边框的加亮显示
CBrush br(cr);
pDC->FrameRect(&lpDrawItemStruct->rcItem, &br);
}
}
对于自绘制组合框来说,成员函数DrawItem是需要重载的一个很重要的成员函数。该函数在自绘制组合框的可视部分发生改变时由框架调用。在默认情况下,该成员函数不做任何操作。其参数lpDrawItemStruct所指向的DRAWITEMSTRUCT结构包括了重绘制所需要的各种信息,如所需重绘的项、其设备上下文以及所执行的重绘行为等。在该成员函数终止前,应用程序应该恢复由该DRAWITEMSTRUCT结构所提供的为该显示上下文所选定图形设备接口。
由表6.30可知在本示例程序中所使用的自绘组合框中的可选项是有序的,而它们都是一些颜色值,框架如何知道当一个新的颜色值被添加到组合框的列表框中时,它应该处于哪个颜色值之前,哪个颜色值之后呢?这时通过调用成员函数CompareItem成员函数来实现的。如果在创建组合框时指定了LBS_SORT样式,则必须重载该成员函数以提供足够的理由来帮助框架对新添加入组合框的列表框中的颜色项进行排序。这里,我们首先根据颜色亮度的大小来对颜色进行排序,对于亮度相同的颜色,我们依次以从蓝色到红色的优先级来判定其相对位置。这个操作是以下面的代码来实现的:
int CClrComboBox::CompareItem(LPCOMPAREITEMSTRUCT lpCompareItemStruct)
{
// TODO: 添加判断指定项的排序顺序的代码
// 当项 1 在项 2 之前时返回 -1
// 当项 1 和项 2 顺序相同时返回 0
// 当项 1 在项 2 之后时返回 1
// 获得项 1 和项 2 的颜色值
COLORREF cr1 =(COLORREF)lpCompareItemStruct->itemData1;
COLORREF cr2 =(COLORREF)lpCompareItemStruct->itemData2;
if (cr1 == cr2)
{
// 项1 和项 2 具有相同的颜色
return 0;
}
// 进行亮度比较, 亮度低的排列顺序在前
int intensity1 = GetRValue(cr1) + GetGValue(cr1) +GetBValue(cr1);
int intensity2 = GetRValue(cr2) + GetGValue(cr2) +GetBValue(cr2);
if (intensity1 < intensity2)
return -1;
else if (intensity1 > intensity2)
return 1;
// 如果亮度相同, 按颜色进行排序, (蓝色最前, 红色最后)
if (GetBValue(cr1) > GetBValue(cr2))
return -1;
else if (GetGValue(cr1) > GetGValue(cr2))
return -1;
else if (GetRValue(cr1) > GetRValue(cr2))
return -1;
else
return 1;
}
上面的代码同时也说明了CompareItem成员函数的不同返回值所代表的不同含义。这里要注意的是,由于同亮度的不同颜色给人的眼睛的亮度感觉是不一样的(这好比人耳对声音的高频段和低频段的听觉灵敏度要比对中频段的听觉灵敏度要小一样),上面的排序结果给人感觉并不象我们所想象的那样,是通过颜色的亮度来进行的。但我们没有必要在这个问题上过分的纠缠而浪费时间。另外我们解释一下,为什么要使用~cr & 0x00FFFFFF来代替~cr。很多人会认为直接将颜色值按位取反就可以得到其对比色,但事实不是这样的。这是因为如果32位颜色值的高位字节不为零的话,该颜色值将不被当作一个RGB颜色值,而使用某个32位值与0x00FFFFFF按位与恰可以使其高位字节为零,而其它位则不变。
到目前为止我们完成了自绘制组合框对应的类CClrComboBox的设计,下面我们来看如何在程序中将类CClrComboBox的对象与对话框模板中的现存组合框相关联,这是使用函数SubclassDlgItem来实现的。首先在类CComboDemoDlg中添加一个类型为CClrComboBox的成员变量m_clrCombo,其访问限制在本工程中是不重要的,可以将它设置为protected。然后,在类CComboDemoDlg的OnInitDialog成员函数中的// TODO注释之后添加下面的一行代码,这行代码将对象m_clrCombo与ID为IDC_CLRCOMBO相关联,第一个参数为控件的父窗口的指针。
m_clrCombo.SubclassDlgItem(IDC_CLRCOMBO, this);
这样就可以通过CWnd的消息映射机制和消息传递路径在类CClrComboBox中处理IDC_CLRCOMBO中事件了。比如当组合框中的项需要重绘时,在CClrComboBox中定义的DrawItem成员函数将被调用,在正确的绘制组合框中的内容。
4. 这里,我们在初始时没有为组合框添加任何选择项,用户可以单击如图6.54的对话框中所示的“添加颜色”按钮向组合框的列表框中添加新颜色项。单击该按钮首先将弹出一个颜色选择对话框,用户如果从颜色选择对话框中选择了一种具体的颜色,该颜色将被添加到组合框的列表框中以供选择。基于这个要求,我们为按钮IDC_ADDCLR的BN_CLICKED事件添加如下的命令处理成员函数OnAddClr:
void CComboDemoDlg::OnAddClr()
{
CColorDialog dlg(0, 0, this);
int iRes=dlg.DoModal();
if (iRes==IDOK)
{
COLORREF cr=dlg.GetColor();
m_clrCombo.AddString( (LPCTSTR)cr );
}
else
{
}
}
虽然使用颜色对话框在本书的前面内容中没有讲述过,但即使是对初学者而言,上面的代码也是非常之简单的,我们这里就不过多的作讲解了。
下面来看如何为“改变颜色”按钮(IDC_CHGCLR)添加单击命令处理成员函数。我们希望用户在单击该按钮时,改变静态文本控件IDC_CLRSTATIC的颜色以反映用户所选择的颜色。如果改变静态文本控件的颜色呢?我们这里使用了将自绘制静态文本控件的办法。这并不是最简单的方法。最简单的方法是处理对话框的WM_CTLCOLOR消息,该消息在控件将要被重绘前发送给该控件的父窗口。这里我们舍近而求远,主要是为了附带讲述一下自绘制静态文本控件的用法,它和自绘制组合框的用法存在一些区别。但是,在资源编辑器中我们不可以设置一个静态文本控件的自绘制样式,不过这并不意味将不可以使用自绘制静态控件。方法并不复杂。首先,为静态文本控件添加自绘制样式,将下面的代码添加到类CComboDemoDlg的OnInitDialog成员函数中的// TODO注释之后:
GetDlgItem(IDC_CLRSTATIC)->ModifyStyle(0, SS_OWNERDRAW);
上面的代码将静态文本控件修改为具有SS_OWNERDRAW样式的自绘制静态控件。下面我们来看如何在需要的时候重新绘制静态文本控件IDC_CLRSTATIC,方法是在类CComboDemoDlg中为消息WM_DRAWITEM添加处理函数OnDrawItem,而使用ClassWizard很容易办到这一点。重载版本的OnDrawItem成员函数的定义如下:
void CComboDemoDlg::OnDrawItem(int nIDCtl,LPDRAWITEMSTRUCT lpDrawItemStruct)
{
CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);
if (nIDCtl==IDC_CLRSTATIC)
{
CDC *pDC=CDC::FromHandle(lpDrawItemStruct->hDC);
CBrush br(m_crClrStatic);
CRect rc=lpDrawItemStruct->rcItem;
pDC->FillRect(&rc, &br);
}
}
要使上面的代码正常工作,我们还需要在类CComboDemoDlg中定义类型为COLORREF的成员变量m_crClrStatic,该成员变量保存了静态文本控件应该具有的颜色。我们注意到了在重载版本的OnDrawItem成员变量调用了基类的OnDrawItem成员函数,这是必要的,不要忘记在我们的对话框中还有一个自绘制组合框,基类的OnDrawItem成员函数调用自绘制组合框所对应的CComboBox的派生类的DrawItem成员函数来绘制组合框中的各个项。
下面来看下压按钮IDC_CHGCLR的BN_CLICKED事件的处理函数OnChgClr,其代码如下:
void CComboDemoDlg::OnChgClr()
{
int nSel=m_clrCombo.GetCurSel();
COLORREF cr=(COLORREF)m_clrCombo.GetItemData(nSel);
if (cr!=-1)
{
DRAWITEMSTRUCT drawItemStruct;
drawItemStruct.CtlID=IDC_CLRSTATIC;
drawItemStruct.hwndItem=GetDlgItem(IDC_CLRSTATIC)->GetSafeHwnd();
drawItemStruct.hDC=::GetDC(drawItemStruct.hwndItem);
GetDlgItem(IDC_CLRSTATIC)->GetClientRect(&(drawItemStruct.rcItem));
m_crClrStatic=cr;
OnDrawItem(IDC_CLRSTATIC, &drawItemStruct);
}
}
该成员函数将当前选中的颜色值(如果不为CB_ERR的话)放入成员变量m_crClrStatic中,然后构造一个DRAWITEMSTRUCT结构变量,并对它进行必要的初始化,最后调用该结构对象调用OnDrawItem成员函数重绘对话框。以后在需要重绘时,OnDrawItem成员函数会由框架自动调用。
这时即可编辑并运行上面的示例程序了,其运行结果如图6.56所示。单击“添加颜色”向组合框的列表框中添加几种颜色选项,再来调试程序的各项功能是否正常。还可以不同的窗口之前进行切换和相互覆盖或移开,以观察自绘制组合框和自绘制静态文本控件是否正确的绘制了自身。
图6. 56 示例程序ComboDemo的运行结果
相比较标准的组合框而言,自绘制组合框要复杂得多,我们得自己考虑很多特殊的问题,但是如示例程序所示,它的确可以实现一些很有趣的特性,因此在很多程序中得到广泛的使用。而掌握了自绘制控件的使用,就可以使你所编写的应用程序界面更加的缤纷多彩,但是,要注意一切事物的使用都有一个“度”,不适宜的将应用程序的用户界面做得过分的“花哩呼哨”,很多时候只会适得其反。
第八节 滚动条控件
滚动条(如图6.57所示)本身也可以作为一种控件,通常我们使用这种控件来进行如定位之类的操作。滚动条控件分为水平滚动条和垂直滚动条两种,它们对应于Controls工具箱中的图标分别为和。
图6. 57 滚动条控件
对于滚动条控件,可以在Properties对话框的Styles选项卡内设置的属性只有一种:即Align属性,该属性可以为三种值之一:None、Top/Left和Bottom/Right。其中,Top/Left表示将滚动条控件的左上边与由CreateWindowEx函数的参数定义的矩形的左上边对齐,而Botton/Right则表示以右下边进行对齐。该属性的默认值为None,即不进行任何对齐操作。
Windows标准滚动条的行为由MFC类CScrollBar封装。表中列出了在类CScrollBar中定义的成员函数及其说明。
表6. 31 在类CScrollBar中定义的成员函数
成员函数 | 描述 |
CScrollBar | 构造一个CScrollBar对象 |
Create | 创建一个Windows滚动条,并将它与CScrollBar对象相关联 |
GetScrollPos | 获得滚动条的当前位置 |
SetScrollPos | 设置滚动条的当前位置 |
GetScrollRange | 获得给定滚动条的当前最大和最小位置 |
SetScrollRange | 设置给定滚动条的当前最大和最小位置 |
ShowScrollBar | 显示或隐藏滚动条 |
EnableScrollBar | 允许或禁止滚动条上的一个或两个箭头 |
SetScrollInfo | 设置关于滚动条的信息 |
GetScrollInfo | 获得滚动条的信息 |
GetScrollLimit | 获得滚动条的限制 |
当用户单击了滚动条时,父窗口将收到WM_HSCROLL或WM_VSCROLL消息,在CWnd类的定义了处理该消息的成员函数为OnHScroll和OnVScroll。成员函数OnHScroll的原型如下:
afx_msg void OnHScroll( UINT nSBCode, UINT nPos, CScrollBar* pScrollBar );
第一个参数nSBCode指定如下之一的滚动条代码,这些代码代表用户所作的滚动请求:
SB_LEFT: | 向左滚动较远距离 |
SB_ENDSCROLL: | 结束滚动 |
SB_LINELEFT: | 向左滚动 |
SB_LINERIGHT: | 向右滚动 |
SB_PAGELEFT: | 向左滚动一页 |
SB_PAGERIGHT: | 向右滚动一页 |
SB_RIGHT: | 向右滚动较远距离 |
SB_THUMBPOSITION: | 滚动到绝对位置。当前位置由nPos参数指定 |
SB_THUMBTRACK: | 拖动滚动条到指定的位置。当前位置由nPos参数指定 |
通常,SB_THUMBTRACK滚动条代码由应用程序使用,以便在滚动条被拖动时给以反馈。如果应用程序滚动了由滚动条控制的内容,它必须使用SetScrollPos来重置滚动条的位置。
传递给函数OnHScroll的参数反映了当收到消息时由框架获得的值,如果在重载版本的函数中调用了基类的实现,该实现将使用最初由消息传递的参数,而不是向函数提供的参数。
消息WM_VSCROLL的处理函数OnVScroll与OnHScroll类似,我们这里就不再重复讲述了。下面我们来看一个例子:
1. 创建一个名为ScrollDemo的基于对话框的MFC工程,按图设置对话框的各控件。其中水平滚动条控件的ID为IDC_SCROLL,编辑框控件的ID为IDC_CURPOS。
图6. 58 示例程序ScrollDemo的主对话框的设计
2. 使用ClassWizard为编辑框控件IDC_CURPOS映射类型为int的成员变量m_iCurPos,并设置其最大值为100,最小值为-100。
3. 使用ClassWizard在类CScrollDemoDlg中为消息WM_HSCROLL添加处理函数OnHScroll,其代码如下:
void CScrollDemoDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar*pScrollBar)
{
// 获得原有的滚动条位置
int iPos=pScrollBar->GetScrollPos();
// 根据不同的拖动方式设置新的滚动条位置
switch (nSBCode)
{
// 向右滚动一行
case SB_LINERIGHT:
iPos+=1;
break;
// 向左滚动一行
case SB_LINELEFT:
iPos-=1;
break;
// 向右滚动一页
case SB_PAGERIGHT:
iPos+=10;
break;
// 向左滚动一页
case SB_PAGELEFT:
iPos-=10;
break;
// 直接拖动滚动块
case SB_THUMBTRACK:
iPos=nPos;
break;
default:
break;
}
// 滚动条的最大位置不超过 100, 最小位置不小于 -100
if (iPos<-100) iPos=-100;
if (iPos>100) iPos=100;
// 必须手动的更新滚动条的当前位置
pScrollBar->SetScrollPos(iPos);
// 在编辑框中显示滚动条的当前位置
SetDlgItemInt(IDC_CURPOS, iPos);
CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
}
上面的代码暗示了一点,这就是在被拖动时,滚动条不会自动更新其位置,我们必须自己在程序中做到这一点,即通过分析不同的滚动方式来改变并设置新的滚动条位置,上面的代码演示了这一过程。
编译上面的程序代码,我们发现滚动条不能正常工作!这是因为在默认情况下,滚动条的滚动范围为从0到0。这时,我们根本不可能对滚动条进行有意义的操作。因此,我们需要将下面的代码添加到OnInitDialog成员函数:
CScrollBar *pScroll=(CScrollBar*)GetDlgItem(IDC_SCROLL);
pScroll->SetScrollRange(-100, 100);
pScroll->SetScrollPos(0);
SetDlgItemInt(IDC_CURPOS, 0);
上面的代码设定了滚动条的滚动范围和默认的滚动条位置,然后,将当前滚动条位置显示在编辑控件IDC_CURPOS中。
4. 最后我们来实现一个功能,这就是我们希望当编辑控件中的文本发生改变时,滚动条上的滑块的位置也相应的变化。要实现这一点,使用ClassWizard为控件IDC_CURPOS的通知消息EN_CHANGE添加消息处理函数OnChangeCurPos。
void CScrollDemoDlg::OnChangeCurPos()
{
CString str;
GetDlgItemText(IDC_CURPOS, str);
str.TrimLeft();
str.TrimRight();
int iPos=0;
if (str!="-" && str!="")
{
if (!UpdateData())
{
return;
}
iPos=m_iCurPos;
}
CScrollBar *pScroll=(CScrollBar*)GetDlgItem(IDC_SCROLL);
pScroll->SetScrollPos(iPos);
}
由于需要检验用户输入数据的有效性,上面的代码比较长。首先,如果用户只输入一个负号“?”或刚将原有的数据删除,此时不应该报错。这里我们可以将滚动条的位置设置为0。由于用户可能在所输入的数据之前或之后插入一些空格,这种情况下我们也不应该报错,因此,我们使用了一些额外的代码来避免了这种情况。最后,我们使用了UpdateData函数来使用控件IDC_CURPOS的值更新成员变量m_iCurPos,这样的目的是便于使用MFC提供的对话框数据检验机制。但有个不好的地方是,如果用户输入的数据有错,出现的报错消息是英文的。如果我们需要的是一个完全中文化的软件,这不能不算是一个瑕疵,这时,我们应该编写自己的数据检验代码。但是在本示例程序中,并不需要这样要求,这里使用MFC的对话框数据检验机制是很有效的。回到程序代码中去,如果用户在编辑控件中输入的值有效的话,使用这个值去更新滚动条的当前位置,这是通过类CScrollBar的成员函数SetScrollPos来实现的。
其它的一些控件,如CSliderCtrl类所封装的滑块控件等,与滚动条控件的使用有很大的共通之处,读者完全可以根据本章中所讲述的内容通过举一反三来用于其它的场合。
- 注意:
- 由于篇幅有限,在本章中我们不打算介绍更多的Windows控件。事实上,Windows控件的使用的有规律可寻的。只需要弄清楚几种控件的用法,以及MFC在处理控件时的机制,就很容易借助Visual C++所提供的丰富的联机文档来学习其它控件的使用。本章中所介绍的控件,还只是所有控件中很小的一个部分,而且,即使是对所介绍的几种控件的讲述也不是面面俱到的。我们的目的不再于详尽的罗列各种控件的使用方法,而在于起到一种“抛砖引玉”的作用。