第六课——创建和使用控件
一、何为控件
- 控件:在系统内部定义的用于和用户交互的基本单元。
- 分类:Windows普通控件(如编辑框、列表框、组合框等)、MFC扩展控件、ActiveX控件
- 常用控件:静态控件、按钮、编辑框、列表框、组合框、滚动条、旋转按钮控件、日期时间控件
- 控件的优势:简化编程、完成常用的功能
二、创建控件
情形一:在对话框模板中用编辑器指定控件(常用)
情形二:调用MFC相应控件类的成员函数Create来创建
- 补充:常用控件类有CStatic、CButton、CEdit、CListBox、CComboBox等。
下面以情形二来创建一个按钮:基于对话框的应用程序Ex_Create→ClassView选项卡(右击CEx_CreateDlg类名)→Add Member Variable(变量类型为CButton,变量名为m_btnWnd)→双击OnInitDialog函数名转至其实现代码处→添加如下代码。
BOOL CEx_CreateDlg::OnInitDialog() { CDialog::OnInitDialog(); ... // TODO: Add extra initialization here m_btnWnd.Create("你好", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP, CRect(20, 20, 120, 40), this, 201); //创建 CFont *font = this->GetFont(); //获取对话框的字体 m_btnWnd.SetFont(font); //设置控件字体 return TRUE; // return TRUE unless you set the focus to a control }
分析一:
①由于OnInitDialog函数在对话框初始化时被调用,因此将对话框中的一些初始化代码都添加在此函数中。
②CButton类成员函数Create用来创建按钮控件。其第一个参数用来指定按钮的标题;第二个参数用来指定按钮控件的样式,其中BS_PUSHBUTTON(以BS_开头的)是按钮类封装的预定义样式,表示创建的是按键按钮;而WS_CHILD(子窗口)、WS_VISIBLE(可见)、WS_TABSTOP(可用Tab键选择)等都是CWnd类封装的预定义窗口样式,它们都可以直接引用;第三个参数用来指定它在父窗口中的位置和大小;第四个参数用来指定父窗口指针;最后一个参数指定该控件的标识值。
③由于按钮是作为对话框的一个子窗口来创建的,因此WS_CHILD样式是必不可少的,且还要使用WS_VISIBLE使控件在创建后显示出来。
④由于Windows操作系统使用的是图形界面,因此在MFC中,对于每种界面元素的几何大小和位置常使用CPoint类(点)、CSize类(大小)和CRect类(矩形)来描述。
分析二:
①控件编程创建方法是使用各自封装的类(即各控件类)的Create成员来创建。其优点是能动态创建(即运行时才创建控件),但它涉及的编程内容比较复杂,且不能发挥对话框编辑器可视化的优点。
②在MFC中,每一种类型的控件都用相应的类来封装。
三、控件的消息
- 通知消息:当控件的状态发生改变时,控件就会向其父窗口发送消息,这个消息称为“通知消息”。
对于每个消息,系统都会用一个MSG结构来记录,MSG具有下列原型:
typedef struct tagMSG { //msg HWND hwnd; //消息发向的(接收到消息的)窗口的句柄 UINT message; //主消息的标识值 WPARAM wParam; //附消息值,其具体含义依赖于主消息值 LPARAM lParam; //附消息值,其具体含义依赖于主消息值 DWORD time; //消息放入消息队列中时的时间 POINT pt; //消息放入消息队列中时的鼠标坐标 }MSG;
- 对于一般控件,其通知消息通常是一条WM_COMMAND消息,该消息的wParam参数的低位字中含有控件标识符,wParam参数的高位字则为通知代码,lParam参数则是指向控件的句柄。
- 而对于有些控件,其通知消息通常是一条WM_NOTIFY消息,该消息的wParam参数是发送通知消息的控件的标识符,而lParam参数则是指向一个结构指针。
四、映射控件消息
不管是什么控件消息,一般都可以用MFC ClassWizard对它们加以映射。
以一个标识符为IDC_BUTTON1的按键按钮为例:MFC ClassWizard对话框(Message Maps选项卡)→类名选定为CEx_CreateDlg(Objects IDs选定为IDC_BUTTON1)→双击Messages列表框中的(要映射的)BN_CLICKED消息→在CEx_CreateDlg类中添加该消息的映射函数OnButton1→双击消息函数OnButton1定位到CEx_CreateDlg:: OnButton1代码实现处→添加如下代码→编译并运行。
void CEx_CreateDlg::OnButton1() { // TODO: Add your control notification handler code here MessageBox(_T("你按下了\"Button1\"按钮!")); }
说明:
①不同资源对象(控件、菜单命令等)所产生的消息是不同的。例如,按键按钮IDC_BUTTON1的消息有两个:BN_CLICKED和BN_DOUBLECLICKED,分别表示当用户单击或双击该按钮时产生的消息。
②不需要对对话框中的“确定”和“取消”按钮进行消息映射,因为系统已自动设置了这两个按钮的动作,当用户单击这两个按钮时都将自动关闭对话框,且确定按钮动作还使得对话框数据有效。
五、映射控件通用消息
上述过程是映射一个控件的某一个消息,事实上也可以通过WM_COMMAND消息的映射来处理一个或多个控件的通用消息。
且以一个标识符为IDC_BUTTON1的按键按钮为例:MFC ClassWizard对话框(Message Maps选项卡)→类名选定为CEx_CreateDlg(Objects IDs选定为CEx_CreateDlg)→双击Messages列表框中的(要映射的)OnCommand消息→在CEx_CreateDlg类中添加该消息的映射函数OnCommand→双击消息函数OnCommand定位到CEx_CreateDlg:: OnCommand代码实现处→添加如下代码→编译并运行。
BOOL CEx_CreateDlg::OnCommand(WPARAM wParam, LPARAM lParam) { // TODO: Add your specialized code here and/or call the base class WORD nCode = HIWORD(wParam); //控件的通知消息 WORD nID = LOWORD(wParam); //控件的ID号 if ((nID == 201) && (nCode == BN_CLICKED)) MessageBox(_T("你按下了\"你好\"按钮!")); if ((nID == IDC_BUTTON1) && (nCode == BN_CLICKED)) MessageBox(_T("这是在OnCommand处理的结果!")); return CDialog::OnCommand(wParam, lParam); }
说明:
①OnCommand函数是一个用来处理WM_COMMAND消息的虚函数,故而这里添加的OnCommand函数事实上是一个在类中实际调用的函数,可称为“实例函数”。这样的映射操作,可以称为“对虚函数OnCommand的重载”。
②在上述编写的代码中,Button1按钮的BN_CLICKED消息用不同的方式处理了两次,即同时存在两种函数OnButton1和OnCommand,故若单击Button1按钮,系统首先执行OnCommand函数,然后执行OnButton1代码。之所以还能执行OnButton1函数代码,是因为OnCommand函数的最后一句代码“return CDialog::OnCommand(wParam, lParam);”,它将控件的消息交由对话框其他函数处理。
③由于用Create创建的控件无法用MFC ClassWizard直接映射其消息,因此上述方法弥补了ClassWizard的不足,使用时要特别注意。
六、使用控件
- 使用控件之前需获得该控件的类对象指针或映射一个对象,然后通过该指针或对象来引用其成员函数进行操作。
下表是MFC封装的常用控件类:表3.1
获取一个控件的类对象指针:通过CWnd类的成员函数GetDlgItem来实现
//GetDlgItem函数声明 CWnd* GetDlgItem(int nID) const; void GetDlgItem(int nID, HWND *phWnd) const;
说明:
①nID:指定控件或子窗口的ID值
②第一个版本:通过函数来返回CWnd类指针
③第二个版本:通过函数形参phWnd来返回其句柄指针
- 注:CWnd类(第三课有提及)是通用的窗口基类,故想要调用实际的控件类及其基类成员,要对返回值进行类型转换,如CButton *pBtn = (CButton*)GetDlgItem(IDC_BUTTON1);
七、控件对象
- 控件变量:与控件相关联的成员变量(在Member Variables选项卡添加)
- 控件变量分为两种类型:用于操作的控件对象、用于存取的数据变量
- 注:它们都是与控件或子窗口进行绑定,但MFC只允许每种类型仅绑定一次。
为了理解上述两种类型,下面看示例:基于对话框的应用程序Ex_Member→添加ID为IDC_BUTTON1的按钮控件和ID为IDC_EDIT1编辑框控件→MFC ClassWizard对话框(Member Variable选项卡)→类名选定为CEx_MemberDlg(Control IDs选定为IDC_BUTTON1)→单击Add Variable按钮(弹出Add Member Variable对话框)→成员变量名为m_btnWnd(类别项为Control)→MFC ClassWizard对话框(Member Maps选项卡)→为CexMemberDlg添加IDC_BUTTON1的BN_CLICKED消息映射函数OnButton1→添加如下代码。
void CEx_MemberDlg::OnButton1() { // TODO: Add your control notification handler code here CString strEdit; //定义一个字符串 CEdit *pEdit = (CEdit*)GetDlgItem(IDC_EDIT1); //获取编辑框中的内容 pEdit->GetWindowText(strEdit); strEdit.TrimLeft(); strEdit.TrimRight(); if (strEdit.IsEmpty()) m_btnWnd.SetWindowText(_T("Button1")); //将m_btnWnd的内容设定为按钮控件的标题 else m_btnWnd.SetWindowText(strEdit); }
说明:
①成员变量m_btnWnd与控件IDC_BUTTON1相关联
②控件按钮IDC_BUTTON1的编程操作可用与之绑定的对象m_btnWnd来操作
八、DDX和DDV技术
- 概述:对于控件的数据变量,MFC提供了DDX和DDV技术。
- DDX:将数据成员变量同对话类模板内的控件相联接(方便数据在控件间传输)
- DDV:用于数据的校验
当某控件与一个数据变量相绑定后,就可以用CWnd::UpdateData函数实现控件数据的输入和读取。
UpdateData函数:只有一个参数,它为TRUE或FALSE。当在程序中调用UpdateData(TRUE)或不带参数的UpdateData()时,数据从控件向相绑定的成员变量复制;反之,数据则由控件绑定的成员变量向控件传输。
说明:数据变量的类型由被绑定的控件类型而定。
使用示例(建立在项目Ex_Member的基础上):MFC ClassWizard对话框(Member Variable选项卡)→类名选定为CEx_MemberDlg(Control IDs选定为IDC_EDIT1)→单击Add Variable按钮(弹出Add Member Variable对话框)→成员变量名为m_strEdit(类别项为Value)→出现Maximum Characters编辑框(这就是控件变量的DDV设置)→ClassView选项卡(展开CEx_MemberDlg类结点)→双击OnButton1成员函数结点(定位到CEx_MemberDlg::OnButton1函数实现代码处)→改写如下代码。
void CEx_MemberDlg::OnButton1() { // TODO: Add your control notification handler code here UpdateData(); //没有参数,表示使用的是默认参数值TRUE m_strEdit.TrimLeft(); m_strEdit.TrimRight(); if (m_strEdit.IsEmpty()) m_btnWnd.SetWindowText(_T("Button1")); else m_btnWnd.SetWindowText(m_strEdit); }