基础语法篇_6——对话框的创建与显示、动态创建按钮、控件的访问【控件调整|静态文本控件|编辑框控件】、对话框伸缩功能、输入焦点的传递、默认按钮的说明

35 篇文章 42 订阅

🔳🔳 绘制线条 、画刷绘图、绘制连续线条、绘制扇形效果的线条


🔳🔳 插入符【文本插入符|图形插入符】、窗口重绘、路径、字符输入【设置字体|字幕变色】


🔳🔳 菜单命令响应函数、菜单命令的路由、基本菜单操作、动态菜单操作、电话本实例


🔳🔳 对话框的创建与显示、动态创建按钮、控件的访问【控件调整|静态文本控件|编辑框控件】、对话框伸缩功能、输入焦点的传递、默认按钮的说明


🔳🔳 MFC对话框:逃跑按钮、属性表单、向导创建


🔳🔳 在对话框程序中让对话框捕获WM_KEYDOWN消息


🔳🔳 修改应用程序窗口的外观【窗口光标|图标|背景】、模拟动画图标、工具栏编程、状态栏编程、进度栏编程、在状态栏上显示鼠标当前位置、启动画面


🔳🔳 设置对话框、颜色对话框、字体对话框、示例对话框、改变对话框和控件的背景及文本颜色、位图显示

一、基本知识

对话框就是一个窗口,它不仅可以接收消息,而且可以被移动和关闭,甚至可以在客户区进行绘图。对话框相当于一个容器,在它上面能够放置各种各样的标准控件和扩展控件,使程序支持用户输入的手段更加丰富。

1.1 常用控件介绍

在MFC中,所以的控件类都由CWnd类派生的。因此控件也是窗口。通常控件作为对话框的子窗口而创建的。控件也可以出现在视类窗口、工具栏和状态条中。

MFC的控件类封装类控件的功能。

控件功能对应控件类
静态文本框(Static Text)显示文本,一般不接受输入信息CStatic
图像控件(Picture)显示位图、图标、方框和图元文件,一般不接受输入信息CStatic
编辑框(Edit Box)输入并编辑正文,支持单行和多行编辑CEdit
按钮(Button)响应用户的输入,触发相应的事件CButton
复选框(Check Box)用于选择标记,可以有选中、未选中和不确定三种状态CButton
单选按钮(Radio Button)用于从两个或多个选项中选中一项CButton
组框(Group Box)显示正文和方框,主要用于将相关的一些控件(用于共同的目的)组织在一起CButton
列表框(List Box)显示一个列表,用户可以从该列表中选中一项或者多项CListBox
组合框(Combo Box)是一个编辑框和一个列表框的组合,分成简易式、下拉式和下拉列表式CComboBox
滚动条(Scroll Bar)主要用于从一个预定义范围值中迅速而有效地选取一个整数值CScrollBar
1.2 对话框的种类

对话框分为两种:
🔲◼◾ 模态对话框
模态对话框是指当其显示时,程序会暂停执行,直到关闭这个模态对话框后,才能继续执行程序中其他任务。

模态对话框垄断了用户的输入,当一个模态对话框打开时,用户只能与该对话框进行交互。而其他用户界面对象接收不到输入信息。我们平时所遇到的大部分对话框都是模态对话框。

        例如:在Word中利用文件打开菜单命令显示一个 “打开” 对话框后,再用鼠标去选择其他菜单或者进行该对话框以外的任何操作时,只会听到嘟嘟声。这是因为 “打开” 对话框是一个模态对话框。

🔲◼◾ 非模态对话框
当非模态对话框显示时,允许转去执行程序中其他任务,而不用关闭这个对话框。

二、对话框的创建与显示

先创建一个virtual studio类型的单文档应用工程,程序名为Mybole。

点击帮助菜单的子菜单关于Mybole,会出现一个对话框。这是MFC为我们创建的一个“关于”对话框,主要用于显示程序的版本号和版权信息。

自己创建对话框,需要通过插入一个对话框资源。具体步骤:资源视图—>右键Dialog—>添加资源—>新建

这个新建的IDD_DIALOG1对话框中有两个按钮: OKCancel,并通过它们的属性对话框可以发现它们的ID分别为IDOKIDCANCEL。VC++已经为这两个按钮提供了默认的消息响应函数OnOKOnCancel它们实现的主要功能都是一样的,就是关闭对话框。因此当程序运行时,单击这两个按钮中的任何一个都可以关闭对话框。但是单击这两个按钮关闭对话框后,返回的结果值是不一样的。在程序中通常根据该返回值来判断用户单击的是哪个按钮,从而确定用户的行为:是确定还是取消当前操作。

我们选中IDD_DIALOG1这个对话框资源本身,打开其属性对话框,将其Caption属性设置为:“测试”,以下统称这个对话框为测试对话框。

在MFC中,对资源的操作通常都是通过一个与资源相关的类来完成的。对话框资源也有一个相应的基类: CDialog。CDialog类派生于CWnd类,所以它是一个与窗口相关的类,主要用来在屏幕上显示一个对话框。对话框本身也是一个窗口界面。

在MFC中对资源的操作是通过一个类来完成的,那么就需要创建一个类与这个新建的对话框资源相关联。
在新建的对话框上双击鼠标左键,可弹出添加MFC类的界面。
② 对话框 ID项的内容已经被自动填充,就是刚才新建的那个对话框资源标识:IDD_DIALOG1,并且在基类项中也指定了这个新类的基类: CDialogEx。可以更换成CDialog基类。
③ 设置新类名。在输入类名的同时, 头文件和源文件会自动填充。

生成的CTestDialog类的构造函数如下。此类与IDD_DIALOG1相关联。

CTestDialog::CTestDialog(CWnd* pParent /*=nullptr*/)
	: CDialog(IDD_DIALOG1, pParent)
{

}

在程序中显示此对话框。
1)先在帮助菜单后添加一个菜单项,打开其属性对话框,将其Caption属性设置为**“对话框”,PopUp设置为False**,并将其ID设置为IDM_DIALOG
在这里插入图片描述
2)为此子菜单添加COMMAND命令消息响应函数。视类CMyboleView捕获对话框菜单项命令响应。

2.1 模态对话框的创建

创建模态对话框需要调用CDialog类的成员函数:DoModal,该函数功能就是创建并显示一个模态对话框。其返回值将作为CDialog类的另一个成员函数:EndDialog的参数,功能是关闭模态对话框。

✨ 1)视类引入CTestDialog的头文件。
🔴1.1)视类源文件文件引入#include "CTestDialog.h
🟡 1.2)后续根据提示进行添加。

✨✨ 2)定义对话框对象,并利用此对象调用DoModal函数以产生一个模态对话框。

void CMyboleView::OnDialog()
{
	// TODO: 在此添加命令处理程序代码
	CTestDialog  dlg;
	dlg.DoModal();
}

运行程序,点击对话框菜单,即可弹出自己创建的测试对话框。在此点击菜单中的任意菜单项都不会反应。

📋📋 原因:因为当Mybole程序执行到CMyboleView类OnDialog函数中的DoModal这行代码时,它就会停在这行代码处,不再向下执行。只有这个模态对话框被关闭了之后,这行代码才会返回,程序才能继续执行。

2.2 非模态对话框的创建

创建非模态的对话框,需要利用CDialog类的Create成员函数。该函数具有以下两种形式的声明:

BOOL create(LPCTSTR lpszTemplateName,CWnd* pParentWnd = NULL);
BOOL create(UINT nIDTemplate, CWnd* pParentWnd = NULL);
  • 第一个参数:
         🔳 是对话框资源的ID(nIDTemplate参数)
         🔳 或者是对话框模板的名称(IpszTemplateName参数)。
  • 第二个参数指定了对话框的父窗口。
         如果其值是NULL,对话框的父窗口就是主应用程序窗口。

在CMyboleView类OnDialog函数中实现创建非模态对话框的功能。

void CMyboleView::OnDialog()
{
	// TODO: 在此添加命令处理程序代码
	//CTestDialog  dlg;
	//dlg.DoModal();

	CTestDialog dlg;
	dlg.Create(IDD_DIALOG1, this);
}

🔳◼◾ 问题一:运行Mybole程序,单击程序菜单栏上的对话框菜单,发现并未出现测试对话框窗口。
        📋📋 原因:当利用Create函数创建非模态对话框时,还需要调用ShowWindow函数将这个对话框显示出来。
        🟢🟢 解决:在Create函数调用后再调用显示函数ShowWindow函数

void CMyboleView::OnDialog()
{
	// TODO: 在此添加命令处理程序代码
	//CTestDialog  dlg;
	//dlg.DoModal();

	CTestDialog dlg;
	dlg.Create(IDD_DIALOG1, this);
	dlg.ShowWindow(SW_SHOW);
}

📢📢📢 DoModal函数创建对话框时不需要ShowWindow函输。这是因为DoModal函数本身就有显示模态对话框的作用。所以模态对话框不需要再调用ShowWindow函数来显示对话框了。


🔳◼◾ 问题二:运行程序,单击程序菜单栏上的对话框菜单,发现任然出现问题。

        📋📋 原因:创建的非模态对话框对象(dlg)是一个局部对象,当程序执行时会依次执行各条代码。当OnDialog函数的右大括号执行结束时,dlg这个对象的生命周期也就结束了,它就会销毁与之相关联的对话框资源。

📢📢📢 为什么上面创建模态对话框时就可以使用局部对象呢?
                上面已经讲过,在创建模态对话框时,当执行到调用DoModal函数以显示这个对话框时,程序就会暂停执行。直到模态对话框关闭之后,程序才继续向下执行。也就是说,当模态对话框显示时,程序中创建的dg这个对象的生命周期并未结束。

        🟢🟢 解决:在创建非模态对话框时,不能把对话框对象定义为局部对象。对于这个问题,有两种解决办法:
① 把这个对话框对象定义为视类的成员变量

  • 先定义dlg为一个视类成员变量:
  • 然后修改命令响应函数代码:
    void CMyboleView::OnDialog()
    {
    	// TODO: 在此添加命令处理程序代码
    	//CTestDialog  dlg;
    	//dlg.DoModal();
    
    	//CTestDialog dlg;
    	dlg.Create(IDD_DIALOG1, this);
    	dlg.ShowWindow(SW_SHOW);
    }
    
    ⭕ 问题2.1:运行程序,发现编译出错:

    🟢🟢 解决办法:在视类的头文件中引入CTestDialog的头文件。

② 将对话框对象定义为指针,在堆上分配内存。

📢📢📢 在堆上配的内存,与程序的整个生命周期是一致的(程序中不主动销毁)

void CMyboleView::OnDialog()
{
	// TODO: 在此添加命令处理程序代码
	//CTestDialog  dlg;
	//dlg.DoModal();

	CTestDialog*  dlg=new CTestDialog;
	dlg->Create(IDD_DIALOG1, this);
	dlg->ShowWindow(SW_SHOW);
}

⭕问题2.2:定义的dlg这个指针变量是一个局部对象,当它的生命周期结束时,它所保存的内存地址就丢失。那么在程序中也就无法再引用到它所指向的那块内存。
🟢🟢 解决:解决办法有两种:
① 将这个指针变量定义为视类(CMyboleView)的成员变量,然后在CMyboleView类的析构函数中调用delete函数来释放这个指针变量所指向的那块内存;

在构造函数中初始化,在析构函数中释放。

CMyboleView::CMyboleView() noexcept
{
	// TODO: 在此处添加构造代码
	pDlg = new CTestDialog;

}

CMyboleView::~CMyboleView()
{
	if (pDlg != NULL) {
		delete pDlg;
		pDlg = NULL;
	}
}

......

void CMyboleView::OnDialog()
{
	// TODO: 在此添加命令处理程序代码
	//CTestDialog  dlg;
	//dlg.DoModal();
	
	//CTestDialog* dlg = new CTestDialog;
	//dlg->Create(IDD_DIALOG1, this);
	//dlg->ShowWindow(SW_SHOW);
	
	pDlg->Create(IDD_DIALOG1, this);
	pDlg->ShowWindow(SW_SHOW);
}

② 在CTestDlg类中重载PostNcDestroy虚函数,释放this指针所指向的内存。

//CMyboleView.cpp
void CMyboleView::OnDialog()
{
	// TODO: 在此添加命令处理程序代码
	//CTestDialog  dlg;
	//dlg.DoModal();

	CTestDialog* dlg = new CTestDialog;
	dlg->Create(IDD_DIALOG1, this);
	dlg->ShowWindow(SW_SHOW);

	//pDlg->Create(IDD_DIALOG1, this);
	//pDlg->ShowWindow(SW_SHOW);
	
}

//CTestDialog.cpp
void CTestDialog::PostNcDestroy() {
	delete this;
	CDialog::PostNcDestroy();
}
//CTestDialog.h
protected:
    void PostNcDestroy();

2.3 OK按钮

        无论创建的是模态对话框,还是非模态对话框,当单击对话框上的OK按钮时,对话框都会消失。但有一点需要注意:

  • 模态对话框而言,此时对话框窗口对象被销毁了。
  • 非模态对话框来说,对话框窗口对象并未被销毁,只是隐藏起来了。

        非模态对话框中单击OK按钮后,程序所发生的事情:

1. 程序调用基类(CDialog)的OnOK函数,这是一个虚函数。
2. OnOK调用EndDialog函数,这个函数用于终止模态对话框,而对于非模态对话框,这个函数只是使对话框窗口不可见,并不销毁它。

        所以对于非模态对话框来说,如果有一个ID值为IDOK的按钮,就必须重写基类的OnOK这个虚函数,并在重写的函数中调用DestroyWindow函数,以完成销毁对话框的工作,同时注意不要再调用基类的OnOK函数。同样,如果非模态对话框中有一个ID值为IDCANCEL的按钮,也必须重写基类的OnCancel虚函数,并在重写的函数中调用DestroyWindow函数销毁对话框,同时注意不要再调用基类的OnCancel函数。

三、动态创建按钮

利用模态对话框实现一个功能:
        点击对话框中某个按钮,就在对话框中动态创建一个新按钮。

注释与非模态对话框有关的代码(一定要屏蔽掉CTestDlg类中重载的PostNcDestroy虚函数,否则程序运行会出现debug assertion failed错误)。恢复与模态框有关的代码。
✨ 1)为IDD_DIALOG1对话框添加一个按钮。

鼠标点击资源视图–>Dialog–>点击IDD_DIALOG1这个对话框资源ID,会打开对应的对话框资源。点击工具箱,会看到许多资源,将Button按钮拖动到对话框中

将添加的按钮的ID设置为IDC_BIN_ADD,Caption设置为:Add

✨✨ 2)响应Add按钮单击消息。为程序添加Add按钮点击消息的响应函数。

鼠标右键单击Add,选择类向导。因为现在是对CTestDialog对话框进行操作,所以类名选择CTestDialog

✨✨✨3)动态创建按钮。

  • 🔹方法一:通过定义一个变量判断是否已经创建了一个按钮窗口并与按钮对象相关联。
    🔵🔵🔵3.1)为CTestDialog类添加一个CButton成员变量:m_btn。创建按钮,需要调用CButton类的成员函数Create函数实现。

    void CTestDialog::OnClickedBinAdd()
    {
    	// TODO: 在此添加控件通知处理程序代码
    	//在对话框窗口客户区的(0,0)处创建了一个文本为"New"、长度和宽度均为100个逻辑单位的按钮
        //给这个新建按钮随意赋了一个ID号: 123
    	m_btn.Create(_T("New"), BS_DEFPUSHBUTTON | WS_VISIBLE | WS_CHILD, CRect(0,0, 100, 100),this,123);
    }
    

    📢📢📢 如果在创建按钮时,没有指定WS_VISIBLE风格,那么随后一定要调用这个按钮对象的ShowWindow函数,把该按钮控件显示出来。

    运行程序,点击Add按钮,会生成一个名为New的按钮:

    在点击Add,会弹出非法操作对话框:

    📋📋 原因:当单击Add按钮后,就会调用CButton的Create函数,创建一个窗口并与m_btn这个对象相关联。当再次单击Add按钮时,它又重复创建一个窗口并试图与m_btn这个对象相关联,但因为此时m_btn这个对象已经和一个窗口绑定在一起了,因此就会出现非法操作。
    🟢🟢 解决:在程序中需要进行判断,如果m_btn对象已经与一个窗口绑定在一起了,就需要先销毁这个窗口。当下次再单击Add按钮时,再创建一个窗口并与m_btn对象绑定

    🟡🟡🟡3.2)为CTestDlg类增加一个私有的BOOL类型成员变量: m_bIsCreated用来标识是否已经创建过按钮窗口了。并在CTestDlg类的构造函数将此变量初始为FALSE

    CTestDialog::CTestDialog(CWnd* pParent /*=nullptr*/)
    	: CDialog(IDD_DIALOG1, pParent)
    {
    	m_bIsCreated = FALSE;
    }
    

    然后在Add按钮单击消息的响应函数中,首先判断m_bIsCreated这个变量的值:

    • 如果其值为FALSE,说明还没有创建按钮窗口,那就调用Create函数创建,然后将m_bIsCreated变量设置为TRUE;
    • 如果其值为TRUE,销毁已创建的按钮窗口。调用CWnd类的成员函数:DestroyWindow来实现,然后将m_bIsCreated变量设置为FALSE。
    void CTestDialog::OnClickedBinAdd()
    {
    	// TODO: 在此添加控件通知处理程序代码
    	//还未动态创建过按钮
    	if (m_bIsCreated == TRUE) {
    		m_btn.DestroyWindow();
    		m_bIsCreated = FALSE;
    	}
    	else
    	{
    		//在对话框窗口客户区的(0,0)处创建了一个文本为"New"、长度和宽度均为100个逻辑单位的按钮
    			//给这个新建按钮随意赋了一个ID号: 123
    		m_btn.Create(_T("New"), BS_DEFPUSHBUTTON | WS_VISIBLE | WS_CHILD, CRect(0, 0, 100, 100), this, 123);
    		m_bIsCreated = TRUE;
    	}
    	
    }
    
  • *🔹🔹 方法二:利用mhWnd保存的窗口句柄
    CWnd对象都有一个成员变量: mhWnd,用来保存与窗口对象相关联的窗口句柄。如果窗口对象"没有与任一个窗口相关联,这个句柄就为NULL。

    因此可以判断:

    • 如果m_btn这个按钮对象的窗口句柄确实为空,就创建按钮;
    • 否则就销毁。在窗口对象销毁时,会把它的窗口句柄设置为NULL。
    void CTestDialog::OnClickedBinAdd()
    {
    	// TODO: 在此添加控件通知处理程序代码
    	//还未动态创建过按钮
    	if (m_bIsCreated == TRUE) {
    		m_btn.DestroyWindow();
    		m_bIsCreated = FALSE;
    	}
    	else
    	{
    		//在对话框窗口客户区的(0,0)处创建了一个文本为"New"、长度和宽度均为100个逻辑单位的按钮
    			//给这个新建按钮随意赋了一个ID号: 123
    		m_btn.Create(_T("New"), BS_DEFPUSHBUTTON | WS_VISIBLE | WS_CHILD, CRect(0, 0, 100, 100), this, 123);
    		m_bIsCreated = TRUE;
    	}
    	
    }
    

运行Mybole程序,单击对话框上的Add按钮,这时在该对话框的左上角就会出现一个动态创建的按钮:New。再次单击Add按钮,动态创建的New按钮销毁了。当再次单击Add按钮,又动态创建了New按钮。

四、控件的访问

在对话框上再放置几个控件:

  • 三个静态文本控件
  • 三个编辑框控件

并静态文本控件的文本Caption分别设置为: “Numer1:”、“Numer2:”、 “Numer3:”


4.1 控件调整

1)如果在对话框中添加了多个控件,可以根据需要调整对话框窗口的大小:
       在对话框资源编辑器中选中对话框窗本身,然后拖动其外围边框上出现的蓝色小方框,直到合适的大小时松开鼠标,即可完成其大小的调整。

2)为了调整对话框上多个控件的位置,或者设置它们的大小及间距,可以使用对话框编辑器菜单上的相应按钮来调整或者布局菜单下提供的各种功能。

4.2 静态文本控件

实现功能:
        当单击“Numer1:”这个静态文本时,把其文本变成:“数值1:”。

Static控件上单击鼠标右键的属性对话框,可以看到这个控件的ID是: IDC_STATIC。对话框中其他两个静态文本控件,发现它们的ID都是一样的。静态文本框主要是起标签作用的,并不是用来响应诸如鼠标单击这类消息的,所以它们的ID都是一样的,默认都是IDC_STATIC

但在有些特殊情况下,例如本例这种情况,需要处理静态文本控件上的鼠标单击消息,则可以通过修改其ID的方法,来为它添加所需的消息响应函数。

✨ 1)先将“Numer1:” 静态文本的ID改为: IDC_NUMBER1

✨✨ 2) 添加命令响应函数,选择STN_CLICKED消息。

void CTestDialog::OnClickedNumber1()
{
	// TODO: 在此添加控件通知处理程序代码
}

✨✨✨ 3)控件实际上也是窗口,因此如果想获取静态文本控件上显示的文本,就可以利用CWnd类的成员函数:GetWindowText来实现。
①首先要获得这个静态文件控件对象,然后才能调用这个对象的 GetWindowText 函数来获取该控件上显示的文本。
        获取这个静态文本框控件对象利用CWnd类的另一个成员函数:GetDlgltem,该函数的一种声明形式如下所示:

Cwnd* GetDlgItem( int nID) const;

        这个函数返回一个指向由参数nID指定的控件或子窗口对象的指针。大多数情况下,这个函数都是在对话框中使用的。

② 设置控件的文本,可以利用CWnd类中与GetWindowText函数相对应的另一个成员函数: SetWindowText来实现。


void CTestDialog::OnClickedNumber1()
{
	// TODO: 在此添加控件通知处理程序代码
	CString str;
	if (GetDlgItem(IDC_NUMBER1)->GetWindowText(str), str == "Numer1:") {
		GetDlgItem(IDC_NUMBER1)->SetWindowText(_T("数值1:"));
	}
	else
	{
		GetDlgItem(IDC_NUMBER1)->SetWindowText(_T("Numer1:"));
	}
}

运行Mybole程序,单击该对话框上的"Numer1:" 静态文本框,但是将会发现这个控件的文本并没有改变。
📋📋 原因:静态文本控件在默认状态下是不发送通告消息的。

🟢🟢 解决:IDC_NUMBER1静态文本控件的属性对话框默认情况下Notify设置为False,因此静态文本控件就不能向其父窗口发送鼠标事件,从而父窗口(本例中的对话框窗)口就接收不到在静态文本控件上鼠标单击这一消息。Notify选项设置为True,即可实现文本的改变

📢📢📢 为了使一个静态文本控件能够响应鼠标单击消息,需要进行两个特殊的步骤:

  • 第一步,改变它的ID;
  • 第二步,在它的属性对话框中选中Notify选项。
4.3 编辑框控件

利用上面的对话框实现功能:
        在前两个编辑框中分别输入一个数字,然后单击 Add按钮,对前两个编辑框中的数字求和,并将结果显示在第三个编辑框中。

三个编辑框的ID分别为:IDC_EDIT2IDC_EDIT3IDC_EDIT4。在Add按钮的点击响应函数中进行。

4.3.1 第一种方式

1)获得两个编辑框中输入的内容。利用GetWindowText 函数实现此功能。
2)计算运算结果。
2)利用SetWindowText函数将求和结果显示在第三个编辑框中。


void CTestDialog::OnClickedBinAdd()
{
	// TODO: 在此添加控件通知处理程序代码

	//m_bIsCreated成员变量实现New按钮动态创建
	/*.....*/
	
	//m_hWnd实现点击Add创建new按钮
	/*......*/

	int num1, num2, num3;
	CString str1,str2,str3;
	
	GetDlgItem(IDC_EDIT2)->GetWindowTextW(str1);
	GetDlgItem(IDC_EDIT3)->GetWindowTextW(str2);
	
	num1 = _ttoi(str1);
	num2 = _ttoi(str2);
	num3 = num1 + num2;
	str3.Format(_T("%d"), num3);
    
    GetDlgItem(IDC_EDIT4)->SetWindowTextW(str3);
}
4.3.2 第二种方式

CWnd类还提供了一个成员函数: GetDlgltemText,这个函数将返回对话框中指定ID的控件上的文本GetDlgltemText函数把GetDlgItem和GetWindowText这两个函数的功能组合起来了。同样,CWnd类还有一个与之对应的成员函数: SetDlgltemText,用来设置对话框中指定ID的控件上的文本

void CTestDialog::OnClickedBinAdd()
{
	// TODO: 在此添加控件通知处理程序代码

	//m_bIsCreated成员变量实现New按钮动态创建
	/*.....*/
	
	//m_hWnd实现点击Add创建new按钮
	/*......*/

	int num1, num2, num3;
	CString str1,str2,str3;
	
	GetDlgItemText(IDC_EDIT2, str1);
	GetDlgItemText(IDC_EDIT3, str2);
	
	num1 = _ttoi(str1);
	num2 = _ttoi(str2);
	num3 = num1 + num2;
	str3.Format(_T("%d"), num3);
	
    SetDlgItemText(IDC_EDIT4, str3);
}
4.3.3 第三种方式

利用CWnd类的另一对成员函数:GetDlgItemlntSetDlgItemlnt 实现上述功能。

💦💦1、其中GetDlgltemlnt函数返回指定控件的文本,并将其转换为一个整型数值。它的声明形式如下所示:

UINT GetDlgItemInt(int nID, BOOL* 1pTrans = NULL, BOOL bsigned = TRUE )const;

◼ nID
控件的ID。
◼ IpTrans
        指向一个布尔型变量,该变量接收转换标志。如果这个函数在调用时有错误发生。例如:遇到一个非数值型的字符,或者得到的数值超过了UINT类型所能表示的最大值,则这个函数将一个0值(即FALSE)放置到这个参数所指向的地址空间中;否则,给这个参数所指向的地址空间中设置一个非0值(即TRUE)。如果调,用这个函数时,将这个参数设置为NULL,则这个函数将不对错误提出警告
◼ bSigned
        指定被检索的值是否有符号。如果为真,说明这个数字是有符号的;否则,是无符号的数值。

💦💦 2、SetDlgltemlnt函数用指定的数值来设置特定控件的文本,声明如下:

void SetDlgItemInt(int nID, UINT nvalue, BOOL bSigned =TRUE);

◼ nlD
控件的ID
◼ nValue
指定用来产生控件上文本的整型数值。
◼ ubSigned
指定nValue参数指定的数值是否是有符号数。如果其值为TRUE,则nValue指定的数值就是一个有符号数;否则是无符号数。

void CTestDialog::OnClickedBinAdd()
{
	// TODO: 在此添加控件通知处理程序代码

	//m_bIsCreated成员变量实现New按钮动态创建
	/*.....*/
	
	//m_hWnd实现点击Add创建new按钮
	/*......*/

	int num1, num2, num3;
	CString str1,str2,str3;
	
	num1=GetDlgItemInt(IDC_EDIT2);
	num2 = GetDlgItemInt(IDC_EDIT3);

	num3 = num1 + num2;
	SetDlgItemInt(IDC_EDIT4, num3);
}
4.3.4 第四种方式

将这三个编辑框分别与对话框类的三个成员变量相关联,然后通过这些成员变量来检索和设置编辑框的文本,这是最简单的访问控件的方式。

为了将对话框控件与类成员变量相关联,需要利用类向导。首先打开类向导对话框,并单击成员变量选项卡,类名选择CTestDialog。

为编辑框添加关联的成员变量。变量的类别要先选择,在设置变量名和变量类型。具体操作如下:


上面的操作在CTestDialog中新添加的代码有:
1.头文件中增加了三个成员变量。

//CTestDialog.h
public:
	afx_msg void OnEnChangeEdit2();
	afx_msg void OnClickedNumber1();
	int m_num1;
	int m_num2;
	int m_num3;

2.CTestDialog的构造函数中,可以看到这三个成员变量进行了初始化,分别赋值为:0。

//CTestDialog.h
//构造函数
CTestDialog::CTestDialog(CWnd* pParent /*=nullptr*/)
	: CDialog(IDD_DIALOG1, pParent)
	, m_num1(0)
	, m_num2(0)
	, m_num3(0)
{
	m_bIsCreated = FALSE;
}

并运行Mybole程序,打开自定义的测试对话框,可以发现这时三个编辑框中都显示了一个数值: 0,这个初始的值就是CTestDlg构造函数为这三个成员变量所设置的,因为这三个成员变量已经与这三个控件相关联了。

3. DoDataExchange函数由程序框架调用,以完成对话框数据的交换和校验。这个函数内部调用了三个 DDX_TEXT函数,功能就是将ID指定的控件与特定的类成员变量相关联。因此就在DoDataExchange函数内部实现了对话框控件与类成员变量的关联

void CTestDialog::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	DDX_Text(pDX, IDC_EDIT2, m_num1);
	DDX_Text(pDX, IDC_EDIT3, m_num2);
	DDX_Text(pDX, IDC_EDIT4, m_num3);
}

void CTestDialog::OnClickedBinAdd()
{
	// TODO: 在此添加控件通知处理程序代码

	//m_bIsCreated成员变量实现New按钮动态创建
	/*.....*/
	
	//m_hWnd实现点击Add创建new按钮
	/*......*/
   m_num3=m_num1+m_num2;
}

运行,发现第三个编辑框并未显示出相加的结果。

📋📋 原因:在程序代码中从来不直接调用DoDataExchange函数,而是通过CWnd类的另一个成员函数: UpdateData来调用。通过调用UpdateData初始化对话框控件或从对话框获取数据。

🟢🟢 解决:为了让数据交换生效,就必须去调用DoDataExchange函数,但程序代码不是直接调用这个函数,而是需要去调用UpdateData这个函数

UpdateData函数声明:

BOOL UpdateData( BOOL bSaveAndvalidate = TRUE );

UpdateData函数有一个BOOL类型的参数。

  • 如果其值为TRUE,则说明该函数正在获取对话框的数据;
  • 如果其值为FALSE,则说明该函数正在初始化对话框的控件。

对模态对话框来说,当它创建时,框架自动以参数值FALSE调用UpdateData函数来初始化对话框控件的内容。因此,上面Mybole程序中的测试对话框中的几个编辑框控件才能收到在CTestDlg类的构造函数中设置的初始值。


        现在要想获得编辑框中的文本,那就需要以参数值TRUE调用UpdateData函数,然后再对与编辑框控件相关联的变量进行计算。在计算语句之前需要加上下面这行代码:

UpdateData();

void CTestDialog::OnClickedBinAdd()
{
	// TODO: 在此添加控件通知处理程序代码

	//m_bIsCreated成员变量实现New按钮动态创建
	/*.....*/
	
	//m_hWnd实现点击Add创建new按钮
	/*......*/
	UpdateData();
    m_num3=m_num1+m_num2;
}

        在调用了先前的计算语句之后, m_num3变量就保存了计算结果,但要想把这个结果显示在第三个编辑框中,还需要进行一次数据交换,但这次是要以参数值FALSE来调用UpdateData函数,即用变量的值来初始化对话框控件。在计算语句之后再加上下面这条语句:

UpdateData (FALSE);
void CTestDialog::OnClickedBinAdd()
{
	// TODO: 在此添加控件通知处理程序代码

	//m_bIsCreated成员变量实现New按钮动态创建
	/*.....*/
	
	//m_hWnd实现点击Add创建new按钮
	/*......*/
	UpdateData();
    m_num3=m_num1+m_num2;
    UpdateData (FALSE);
}

运行Mybole程序,测试一下对编辑框中输入数据的计算结果,将会发现这时程序正确地实现了所需功能。这时,如果在前两个编辑框的任一个中输入一个非数值型的字符,然后单击Add,弹出一个对话框,提示: “请键入一个整数”:

并没有编写过这个提示功能,这里为什么会出现这样一个提示对话框?


        这是因为我们把编辑框控件与一个int型的变量相关联了。这样,当输入的内容不是数值时,程序就会弹出这样的提示信息框。

4.3.5 第五种方式

添加与编辑框控件相关联的控件变量——代表控件本身。

  • IDC_EDIT2编辑框控件增加一个控件类型的变量:m_edit1。
  • IDC_EDIT3编辑框控件增加一个控件类型的变量:m_edit2。
  • IDC_EDIT4编辑框控件增加一个控件类型的变量:m_edit3。

利用类向导打开增加成员变量对话框。
变量的类别选择控件,此时变量类型会自动变为CEdit。

上面的操作在CTestDialog中新添加的代码有:
1.头文件中增加了三个成员变量。

//CTestDialog.h
private:
	CEdit m_edit1;
	CEdit m_edit2;
	CEdit m_edit3;

2.CTestDialog的DoDataExchange函数中增加了三个DDX_Control函数、分别将一个对话框控件与一个控件变量相关联。

void CTestDialog::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	DDX_Text(pDX, IDC_EDIT2, m_num1);
	DDX_Text(pDX, IDC_EDIT3, m_num2);
	DDX_Text(pDX, IDC_EDIT4, m_num3);
	DDX_Control(pDX, IDC_EDIT2, m_edit1);
	DDX_Control(pDX, IDC_EDIT3, m_edit2);
	DDX_Control(pDX, IDC_EDIT4, m_edit3);
}

因为这些控件变量代表的就是控件本身,并且CEdit类派生于CWnd类。因此可以利用这些控件变量调用GetWindowText和SetWindowText这两个函数来获取和设置编辑框的文本。

并运行Mybole程序,打开自定义的测试对话框,可以发现这时三个编辑框中都显示了一个数值: 0,这个初始的值就是CTestDlg构造函数为这三个成员变量所设置的,因为这三个成员变量已经与这三个控件相关联了。

void CTestDialog::OnClickedBinAdd()
{
	// TODO: 在此添加控件通知处理程序代码

	//m_bIsCreated成员变量实现New按钮动态创建
	/*.....*/
	
	//m_hWnd实现点击Add创建new按钮
	/*......*/

	int num1, num2, num3;
	CString str1,str2,str3;
	
	m_edit1.GetWindowTextW(str1);
	m_edit2.GetWindowTextW(str2);

	num1 = _ttoi(str1);
	num2 = _ttoi(str2);
	num3 = num1 + num2;
	str3.Format(_T("%d"), num3);

	m_edit3.SetWindowTextW(str3);
}

以上7种方式实现的功能:

4.3.6 第六种方式

Windows程序都是基于消息的系统,因此为了获取或设置窗口的文本,只要知道获取或设置窗口文本的消息,就可以通过SendMessage来发送这条消息,从而获取或设置窗口的文本

Windows系统中,获取窗口文本的消息是 WM_GETTEXT,发送该消息后,系统将把指定窗口的文本复制到调用者提供的一个缓存中。在这个消息的两个附加参数中:

  • wParam指定将复制的字符数,
  • lParam就是调用者提供的用来保存窗口文本的缓存地址。

而设置窗口文本的消息是WM_SETTEXT

  • wParam参数没有被使用,值为0
  • IParam参数指定了用来设置窗口文本的字符串地址。

🔹1) 利用Platform SDK的SendMessage函数,通过发WM_GETTEXT消息获取前两个编辑框控件的文本。
🔹2) 接着将它们转换为相应的数值并完成加法计算,然后将结果转换为相应的字符串。
🔹3) 最后再发送WM_SETTEXT消息,用得到的结果字符串设置第三个编辑框文本的实现代码。

void CTestDialog::OnClickedBinAdd()
{
	// TODO: 在此添加控件通知处理程序代码

	//m_bIsCreated成员变量实现New按钮动态创建
	/*.....*/
	
	//m_hWnd实现点击Add创建new按钮
	/*......*/

	int num1, num2, num3;
	CString str1,str2,str3;
	
	::SendMessage(GetDlgItem(IDC_EDIT2)->m_hWnd, WM_GETTEXT, 10, (LPARAM)str1.GetBuffer(256));
	::SendMessage(GetDlgItem(IDC_EDIT3)->m_hWnd, WM_GETTEXT, 10, (LPARAM)str2.GetBuffer(256));
	

	num1 = _ttoi(str1);
	num2 = _ttoi(str2);
	num3 = num1 + num2;
	str3.Format(_T("%d"), num3);

	m_edit3.SendMessage(WM_SETTEXT,0, (LPARAM)str3.GetBuffer(0));

	str1.ReleaseBuffer();
	str2.ReleaseBuffer();
	str3.ReleaseBuffer();

}

◼◼ 因为Platform SDK和CWnd类都提供SendMessage函数,所以如果想要调用Platform SDK的函数,则前面必须加上两个冒号(😃
◼◼ 如果调用的是SDK SendMessage函数,第一个参数是窗口的句柄。每个窗口类对象都有一个保存了窗口句柄的成员mhWnd,因此对于编辑框控件来说,可以首先获取编辑框窗口对应的C++对象的指针(即上述第3行代码中调用GetDlgItem函数的作用),然后通过该指针获得窗口句柄(即CWnd类的mhWnd成员变量)。
◼◼ 前面已经把编辑框控件与一个控件对象相关联了,所以可以直接利用该对象来获取其窗口句柄。直接利用m_edit2对象获得第二个编辑框的窗口句柄:m_hwnd。
◼◼ 因为编辑框控件也是窗口,所以可以调用该对象的SendMessage函数来发送相应消息,直接调用m_edit3对象的SendMessage函数通过发送WM_SETTEXT消息来设置第三个编辑框的文本。SendMessage函数不需要窗口句柄这个参数了。
◼◼ SendMessage函数的最后一个参数要求是LPARAM类型,因此在代码中必须进行强制转换。

4.3.7 第七种方式

通过发送消息来完成对控件的访问,但这时是直接给对话框的子控件发送消息,使用的函数是 SendDIgltemMessage,函数声明:

LRESULT SendDlgItemMessage ( int nID, UINT message, WPARAM WParam = 0, LPARAM Param =o);
  • 这个函数的后三个参数与SendMessage函数的相同。
  • 第一个参数指定了将接收当前发送的这条消息的对话框控件。

实际上,SendDlgltemMessage函数的功能相当于把上面的GetDlgltem和SendMessage这两个函数组合起来了。因为SendDigltemMessage函数本身就是在一个对话框中给它的子控件发送消息时使用的,所以在调用时,不必先获得子控件对象,再发送消息,可以直接给子控件发送消息

void CTestDialog::OnClickedBinAdd()
{
	// TODO: 在此添加控件通知处理程序代码

	//m_bIsCreated成员变量实现New按钮动态创建
	/*.....*/
	
	//m_hWnd实现点击Add创建new按钮
	/*......*/

	int num1, num2, num3;
	CString str1,str2,str3;
	SendDlgItemMessage(IDC_EDIT2, WM_GETTEXT, 10, (LPARAM)str1.GetBuffer(256));
	SendDlgItemMessage(IDC_EDIT3, WM_GETTEXT, 10, (LPARAM)str2.GetBuffer(256));

	num1 = _ttoi(str1);
	num2 = _ttoi(str2);
	num3 = num1 + num2;
	str3.Format(_T("%d"), num3);

	SendDlgItemMessage(IDC_EDIT4, WM_GETTEXT, 0, (LPARAM)str3.GetBuffer(0));
	str1.ReleaseBuffer();
	str2.ReleaseBuffer();
	str3.ReleaseBuffer();
}
总结以上7种方式

上面总共介绍了七种访问对话框控件的方式:

  1. GetDlgltem()->Get(Set) WindowTextO)
  2. GetDlgltemText()/SetDlgltemText)
  3. GetDlgltemInt()/SetDlgltemlnt()
  4. 将控件和整型变量相关联
  5. 将控件和控件变量相关
  6. SendMessageo)
  7. SendDlgltemMessage)

◻◽▪ 最常用的是:第一种、第四种和第五种。
◻◽▪ 控件和变量相关联时,根据需要与特定类型的变量相关联。
◻◽▪ 在利用MFC编程时,SendMessageSendDIgltemMessage 方法用得比较少。
◻◽▪ 必须掌握GetDlgltem函数的用法,在对话框编程中经常会用到这个函数。

五、对话框伸缩功能的实现

实现功能如下:
        对话框的扩展与收缩功能。添加一个收缩<< 按钮,当用户点击此按钮,测试对话框将切除一部分,并把此按钮的文本改成:扩展>> 。点击此按钮时,程序还原原来的对话框。

1)测试对话框中在添加一个按钮,Caption设置为:收缩<<

收缩<<按钮添加鼠标单击消息BN_CLICKED响应函数。可以直接在此按钮上鼠标双击左键,即可直接跳到添加的成员函数处。

在此函数中,首先实现鼠标单击按钮后按钮文本发生变化这一功能:

void CTestDialog::OnBnClickedButton1()
{
	// TODO: 在此添加控件通知处理程序代码
	CString str;
	if (GetDlgItemText(IDC_BUTTON1, str), str == "收缩<<") {
		SetDlgItemText(IDC_BUTTON1, _T("扩展>>"));
	}
	else
	{
		SetDlgItemText(IDC_BUTTON1, _T("收缩<<"));
	}

}

实现了按钮文本的切换:

✨✨2)在测试对话框中放置一个分隔条,用来划分对话框中要动态切除的部分,分隔条可以通过VC++提供的图像控件来实现。

打开图片控件的属性对话框,可以看到它的ID也是:IDC_STATIC,首先将其ID改为IDC_SEPERATOR 。Sunken选项设置True状态,这条分隔条就会呈现出一种下陷的状态。


✨✨✨ 3)实现功能:当单击收缩<<按钮时,对话框显示新添的这条“分隔条”以上的内容;当单击扩展>>按钮时,对话框还原为原来的样子。

① 为了还原对话框,需要保存其原始位置,对话框的原始位置通过调用GetWindowRect 函数就能得到。
② 那么如何确定收缩后的对话框大小呢?
当切除掉“分隔条”以下部分后,对话框的左上角坐标,以及对话框的宽度并没有改变,发生变化的只是右下角的纵坐标。也就是说只要得到切除后的对话框右下角的纵坐标,也就得到了切除后的对话框的大小和位置
③ 要得到切除后的对话框右下角的纵坐标,就要利用所添加的图像控件窗口(作为分隔条使用)。在图像控件对象上调用GetWindowRect 函数来得到图像控件窗口的大小和位置,它的右下角纵坐标也就是收缩后的对话框的右下角纵坐标。

先介绍一些需要使用的函数:
🔲🔳🔲 1. CRect类的两个成员函数用来判断一个矩形是否为空:

  • IsRectEmpty
    检测矩形区域是否为空。如果矩形的宽度和高度为0或是一个负值,则说明此矩形为空,返回非零值;否则,返回0。
  • IsRectNull
    如果矩形的左上角和右下角的四个坐标值都是0,则此函数返回一个非零值;否则,返回0。

定义了两个矩形变量:

CRect rect1 (10,10,10,10);
CRect rect2 (0,0,0, 0);

IsRectEmpty (rect1)和IsRectEmpty (rect2)调用都将返回一个非零值。
IsRectNull( rect2 )将返回一个非零值,但IsRectNull( rect1)返回0。
🔲🔳🔲 2. 利用SetWindowPost 函数来设置对话框的收缩和扩展之后的大小。此函数的作用是设置指定窗口的位置和大小。这个函数的原型声明如下:

BOOL SetWindowPos ( const Cwnd* pWndInsertAfter, int x, int y, int cx, intсу, UINT nFlags );
  • pWndInsertAfter
    标识一个CWnd对象,该对象是在以Z次序排序的窗口中位于当前窗口前面的那个窗口对象。这个参数可以是指向某个CWnd对象的指针,也可以是指向下表中所列值的指针之一。
  • x 和 y
    窗口左上角的x和y坐标。
  • cx 和 cy
    窗口的宽度的高度。
  • nFlags
    设定窗口的尺寸和定位。参数取值是下表的各种取值的组合。

介绍Z次序:

实现代码:


void CTestDialog::OnBnClickedButton1()
{
	// TODO: 在此添加控件通知处理程序代码
	CString str;
	//获取到ID为IDC_BUTTON1的控件的文本内容,并赋值给str
	GetDlgItemText(IDC_BUTTON1, str);
	/*
	//判断文本内容是否为收缩<<
	if (GetDlgItemText(IDC_BUTTON1, str),str == "收缩<<") {
	    //设置ID为IDC_BUTTON1的控件文本为: 扩展>>
		SetDlgItemText(IDC_BUTTON1, _T("扩展>>"));
	}
	//否则文本内容不为收缩<<,即为:扩展>>
	else
	{
	     //设置ID为IDC_BUTTON1的控件文本为: 收缩<<
		SetDlgItemText(IDC_BUTTON1, _T("收缩<<"));
	}*/


	//定义了两个矩形变量: 
	//rectLarge: 保存对话框原始尺寸
	//rectSmall: 切除部分区域之后的尺寸
	static CRect rectLarge;
	static CRect rectSmall;


	//判断对话框的原始尺寸是否已经被赋值
	//因为矩形变量是静态变量, 系统将它们的坐标均初始化为0。
	//因此, 可以用上面介绍的两个函数中的任一个来判断矩形是否为空。
	//此时返回非零值,即True:
	if (rectLarge.IsRectNull()) {
		// 用于存放分隔条的尺寸
		CRect rectSeparator;
		//获取对话框的原始尺寸并保存在静态变量中
		GetWindowRect(&rectLarge);
		//获取ID为IDC_SEPERATOR的窗口,并获取此窗口的尺寸保存到rectSeparator
		GetDlgItem(IDC_SEPERATOR)->GetWindowRect(&rectSeparator);
		
		//设置收缩后的对话框尺寸,左上角坐标不变,宽度不变,底部坐标更改为分隔条的坐标
		rectSmall.left = rectLarge.left;//左上角横坐标
		rectSmall.top = rectLarge.top;  //左上角纵坐标
		rectSmall.right = rectLarge.right;//右下角横坐标,即宽度
		rectSmall.bottom = rectSeparator.bottom;//右下角纵坐标,成为分隔条的纵坐标
	}
	//判断文本内容是否为收缩<<
	if (str=="收缩<<")
	{
		//设置ID为IDC_BUTTON1的控件文本为: 扩展>>
		SetDlgItemText(IDC_BUTTON1, _T("扩展>>"));
		//重新设置对话框的尺寸
		SetWindowPos(NULL, 0, 0, rectSmall.Width(), rectSmall.Height(), SWP_NOMOVE | SWP_NOZORDER);
	}
	else
	{
		//设置ID为IDC_BUTTON1的控件文本为: 收缩<<
		SetDlgItemText(IDC_BUTTON1, _T("收缩<<"));
		SetWindowPos(NULL, 0, 0, rectLarge.Width(), rectLarge.Height(), SWP_NOMOVE | SWP_NOZORDER);
	}

}

📢📢📢 如果不想让用户看到对话框中这条添加的分隔条,可以在对话框资源编辑窗口中,利用控件属性对话框,设置这个图片控件的Visible属性为False。

六、输入焦点的传递

打开Mybole程序的测试对话框,在第一个编辑框中输入文字,然后按下回车键,这时会发现这个对话框被关闭了。

在对话框资源界面中查看该对话框上OK按钮的属性,将会发现它的ID是: IDOK。切换到行为选项卡,可以看到它的Default Button选项是:True状态。

查看其他按钮的属性,发现它们都没有选中此选项。说明这个OK按钮是测试对话框的默认按钮。实际上,当在对话框中按下回车键时,会选择对话框中默认按钮的消息响应函数来处理这一事件,而基类(CDialog)的IDOK按钮的默认响应函数(OnOK)的功能就是关闭对话框。因此,一旦在Mybole程序的测试对话框中按下回车键,这个对话框就被关闭了。

实现功能:
        按下回车键后,将输入焦点转移到第二个编辑框。

✨ 1)为了屏蔽掉到默认的回车键关闭对话框这一功能,应该在对话框子类(本例中是指CTestDlg类)中重写OK按钮的消息响应函数
        在Mybole程序中,应该为测试对话框中的IDOK按钮添加鼠标单击消息响应函数。一种添加方法是直接在IDOK按钮上双击鼠标左键

void CTestDialog::OnBnClickedOk()
{
	// TODO: 在此添加控件通知处理程序代码
	CDialog::OnOK();
}

默认情况下,对话框子类重写的IDOK按钮的响应函数仍调用了基类的OnOK函数。为了实现按下回车键时不关闭对话框这一功能,应该在此重写函数中将调用基类的OnOK函数这条语句注释起来。

运行Mybole程序,打开测试对话框,把输入焦点移到第一个编辑框中,接着按下回车键,发现此时对话框并没有被关闭了。默认按钮关闭对话框这一问题解决了。

✨✨ 2)实现当在测试对话框中的第一个编辑框中按下回车键后,输入焦点被转移到第二个编辑框这一功能。

这可以通过捕获键盘按键消息,然后在此消息响应函数中把输入焦点移动到下一编辑框控件来实现。这有两种实现方式:
🔳🔳🔳 一种是为编辑框控件生成一个相关联的类,然后利用这个类来捕获键盘按键消息。
🔳🔳🔳 另一种方式是修改编辑框控件的窗口过程函数。即自己编写一个编辑框控件的窗口过程,然后替换MFC提供的默认的编辑框控件窗口过程函数。窗口的所有消息都要到该窗口的窗口过程函数中来报道。因此在这个新过程函数中可以进行一个判断,如果当前到来的是一个字符消息,并且该字符是一个回车符,那么就将输入焦点移动到一下编辑框控件。窗口过程是在定义窗口类时设置的

         当一个窗口已经创建之后,如何去修改该窗口已指定的过程函数呢?
        这可以通过调用SetWindowLong函数来实现该函数的作用是改变指定窗口的属性,该函数的原型声明:

	LONG SetWindowLong (HWND hwnd, int nIndex, LONG dwNewLong );
  • hWnd
    指定想要改变其属性的窗口句柄。
  • nIndex
    指定要设置的属性值的偏移地址。其取值可以取下表中所列各值。

    当hWnd参数指定的是一个对话框,那么nlndex参数也可以取下表中所列各值。
  • dwNewLong
    指定设置的新值。

        如果调用成功,SetWindowLong函数将返回先前为窗口指定的32位整形值。即如果为指定窗口设定一个新的窗口过程,则该函数将返回先前为该窗口类指定的窗口过程的地址。因此,可以利用这个函数来修改编辑框控件的窗口过程

        实际上,在程序运行时,当对话框及其上的子控件创建完成,将要显示之前会发送的一个消息:WM_INITDIALOG。因此在此消息的响应函数中修改编辑框控件的窗口过程比较合适。

  1. 首先就需要为CTestDlg类添加WM_INITDIALOG消息的响应函数。VS2003后更正为:直接添加OnInitDialog重写函数

    解决对话框“消息”中找不到WM_INITDIALOG

    BOOL CTestDialog::OnInitDialog()
    {
    	CDialog::OnInitDialog();
    
    	// TODO:  在此添加额外的初始化
    
    	return TRUE;  // return TRUE unless you set the focus to a control
    				  // 异常: OCX 属性页应返回 FALSE
    }	
    
  2. 自定义编辑框窗口过程函数NewEditProc。首先定义一个窗口过程函数,在此函数中,截获WM_CHAR消息并作相应判断和处理。因为对于WM_CHAR消息来说,它的wParam参数保存的是字符的ASCI码。所以利用此参数判断当前字符是不是回车符。如果是回车符,把输入焦点传递到下一个编辑框控件。如果不是回车符,那么就调用先前的窗口过程来处理该消息。

            设置焦点可以利用SetFocus 函数来实现。因为NewEditProc这个窗口过程是全局函数,所以不能调用CWnd类的成员函数,只能使用相应的Platform SDK函数。SDK提供的SetFocus函数只有一个参数,即设置输入焦点的窗口句柄,其声明形式:

      HWND SetFocus( HWND hwnd);
    

    ⭕⭕ 方法一:如果希望获得对话框中某个控件的下一个控件的句柄,可以调用GetNextWindow函数,当然这里也应该调用SDK提供的这个函数。 这个函数将返回指定窗口的下一个窗口的句柄。其声明形式:

    HWND GetNextWindow( HWND hWnd, UINT wCmd);
    
    • 第一个参数指定当前窗口句柄。
    • 第二个参数是查找的方向,如果是GW_HWNDNEXT,那么该函数将查找给定窗口的下一个窗口;如果是GW_HWNDPREV,该函数返回给定窗口的上一个窗口的句柄。

    NewEditProc函数的hwnd参数保存的就是第一个编辑框控件的句柄

    当设置完输入焦点后,可以简单地让窗口过程返回数值:1,结束对回车字符的处理。然后在CTestDlg类的OnInitDialog这个消息响应函数中修改第一个编辑框的窗口过程函数。

    WNDPROC prevProc;
    LRESULT CALLBACK NewEditProc(HWND hwnd, UINT uMsg, WPARAM wparam, LPARAM lparam) {
       if (uMsg == WM_CHAR && wparam == 0x0d) {
       	::SetFocus(GetNextWindow(hwnd, GW_HWNDNEXT));
       	return 1;
       }
       else
       {
       	return prevProc(hwnd, uMsg, wparam, lparam);
       }
    }
    
    BOOL CTestDialog::OnInitDialog()
    {
       CDialog::OnInitDialog();
    
       // TODO:  在此添加额外的初始化
       prevProc = (WNDPROC)SetWindowLong(GetDlgItem(IDC_EDIT2)->m_hWnd, GWL_WNDPROC, (LONG)NewEditProc);
    
       return TRUE;  // return TRUE unless you set the focus to a control
       			  // 异常: OCX 属性页应返回 FALSE
    }
    
    

  3. 设置第一个编辑框控件的属性Multiline为True。默认这个编辑框不支持多行,因此就无法接收回车键按下这一消息。所以应该选中这个选项。

运行程序,在第一个编辑框中按下回车键,将会发现输入焦点被转移到第二个编辑框窗口上了。

但是如果希望当再次按下回车键时,输入焦点从第二个编辑框转移到第三个编辑框,目前是无法是实现的,因为现在只修改了第一个编辑框的窗口过程。 按照此方法可以再修改第二个编辑框的窗口过程函数,让输入焦点往下传递。但是这种方式实现起来很不方便。

⭕⭕ 方法二:使用GetWindow函数实现获得窗口句柄。该函数返回与指定窗口有特定关系的窗口句柄,其声明:

HWND GetWindow (HWND hwnd, UINT uCmd );
  • 第一个参数是开始查找的窗口的句柄,
  • 第二个参数指定hWnd参数指定的窗口与要获得的窗口之间的关系:
    • 如果其取值是GW_HWNDNEXT,则该函数将查找在Z次序中位于指定窗口下面的窗口;
    • 如果其取值是GW_HWNDPREV,则该函数将查找在Z次序中位于指定窗口前面的窗口。
WNDPROC prevProc;
LRESULT CALLBACK NewEditProc(HWND hwnd, UINT uMsg, WPARAM wparam, LPARAM lparam) {
	if (uMsg == WM_CHAR && wparam == 0x0d) {
		//::SetFocus(GetNextWindow(hwnd, GW_HWNDNEXT));
		SetFocus(::GetWindow(hwnd, GW_HWNDNEXT));
		return 1;
	}
	else
	{
		return prevProc(hwnd, uMsg, wparam, lparam);
	}
}

Tabstop
⭕⭕ 方法三:利用GetNextDlgTabltem函数来实现获得窗口句柄。该函数将返回指定控件前面或后面的一个具有WS_TABSTOP风格的控件。定控件前面或后面的一个具有WS-TABSTOP风格的控件。关于WS_TABSTOP风格,可以打开测试对话框中任一个编辑框控件的属性对话框,可以看到有一个Tabstop选项,如果选中该选项即设置True,就设置了编辑框具有WS_TABSTOP风格。默认情况下,所有的编辑框控件和按钮控件都选中了此选项,即设置True,而静态文本控件并没有选中此选项即设置False。如果设置了这个Tabstop属性,则在对话框中按下Tab键后,输入焦点可以转移到此控件上。

        GetNextDlgTabltem函数原型:

HWND GetNextDlgTabItem(HWND hDlg, HWND hctl, BOOL bPrevious);
  • hDlg
    指定将被搜索的对话框。
  • hCtl
    指定用来作为搜索开始点的控件。
  • bPrevious
    指定搜索的方向。如果此参数为TRUE,则该函数将寻找对话框中上一个控件;如果此参数为FALSE,则该函数将搜索对话框中下一个控件。

利用GetNextDlgTabltem函数来实现所需功能:

  1. 首先需要获得对话框的句柄,而当前只有该对话框中子控件的句柄。可以通过调用子控件的GetParent函数,得到它的父窗口,也就是对话框的句柄。
  2. 另外应该把GetNextDlgTabltem函数的第三个变量设置为FALSE,让它查找下一个控件。

这样,在Mybole程序中:

WNDPROC prevProc;
LRESULT CALLBACK NewEditProc(HWND hwnd, UINT uMsg, WPARAM wparam, LPARAM lparam) {
	if (uMsg == WM_CHAR && wparam == 0x0d) {
		//::SetFocus(GetNextWindow(hwnd, GW_HWNDNEXT));
		//SetFocus(::GetWindow(hwnd, GW_HWNDNEXT));
		SetFocus(::GetNextDlgTabItem(::GetParent(hwnd), hwnd, FALSE));
		return 1;
	}
	else
	{
		return prevProc(hwnd, uMsg, wparam, lparam);
	}
}

✨✨✨3)利用对话框的默认按钮实现让焦点在对话框中各控件上依次传递。
MFC中,默认情况下,当在对话框窗口中按下回车键时,会调用对话框的默认按钮的响应函数,我们可以在此默认按钮的响应函数中把焦点依次向下传递。

  1. 为了利用这种方法,首先将第一个编辑框的MultiLine选项取消。

  2. 为了使焦点能够依次向下传递,应该获取的是当前拥有焦点的窗口,然后利用此窗口去调用GetNextWindow函数,得到当前拥有焦点的窗口的下一个窗口,再设置后者拥有焦点。

    void CTestDialog::OnBnClickedOk()
    {
    	// TODO: 在此添加控件通知处理程序代码
    	
    	//利用此指针调用GetNextWindow函数得到它的下一个窗口,
    	//调用SetFocus函数将输入焦点设置到该窗口上
    	GetFocus()->GetNextWindow()->SetFocus();
    	//CDialog::OnOK();
    }
    

    这里使用的都是CWnd类的成员函数,其中GetNextWindow函数的原型声明如下所示:

    CWnd* GetNextWindow( UINT nFlag =GW_HWNDNEXT) const;
    

    该函数只有一个参数,用来指定返回的是下一个窗口的句柄(GW_HWNDNEXT)还是上一个窗口的句柄(GW_HWNDPREV),默认值就是GW_HWNDNEXT。所以这里不需要给它传递参数了。

    运行Mybole程序,先把输入焦点放到第一个编辑框,然后按下回车键,将会发现焦点转移到第二个编辑框,再次按下回车键,可以看到焦点转移到了第三个编辑框。但是当继续再按三次回车键后,程序将弹出提示对话框,提示程序出现了非法访问。

    📋📋 原因:主要是因为在程序运行时,在调用GetNextWindow函数获得下一个窗口的过程中,当对对话框中最后一个控件调用此函数时,它返回的这个窗口指针是一个空(NULL)指针,此时再对该指针调用SetFocus函数,就会出现非法访问,程序就会弹出非法对话框。

    🟢🟢解决:使用CWnd类的另一个成员函数: GetNextDlgTabltem来获取对话框中的下一个控件。该函数的原型声明:

    CWnd* GetNextDlgTabItem( CWnd* pWndctl, BOOL bPrevious = FALSE) const;
    

    这个函数与上面介绍的SDK同名函数的作用相同,该函数将返回指定控件前面或后面的一个具有WS_TABSTOP风格的控件。

    • pWndctl
      指定用来作为搜索开始点的控件。
    • bPrevious
      指定搜索的方向。如果此参数为TRUE,则该函数将寻找对话框中上一个控件;如果此参数为FALSE,则该函数将搜索对话框中下一个控件。

最终实现代码如下:

void CTestDialog::OnBnClickedOk()
{
	// TODO: 在此添加控件通知处理程序代码
	
	//利用此指针调用GetNextWindow函数得到它的下一个窗口,
	//调用SetFocus函数将输入焦点设置到该窗口上
	//GetFocus()->GetNextWindow()->SetFocus();
	
	GetNextDlgTabItem(GetFocus())->SetFocus();

	//CDialog::OnOK();
}


焦点在七个WS_TABSTOP风格的控件之间循环。

📢📢📢 3个静态文本和图片控件默认都未选中Tapstop。

Tab顺序
实际上, GetNextDlgTabltem函数和先前使用的GetNextWindow和GetWindow这两个函数搜索窗口的行为是不一样的。
        前者是查找具有Tab stop属性的控件,并按Tab顺序依次查找各控件。

        那么对话框的Tab顺序是什么样的顺序呢?
        在VC++开发环境窗口中,点击格式菜单项,选中Tab键顺序,在对话框资源编辑器中可以看到对话框各个子控件上都有一个序号,这些序号就是各控件的Tab顺序。当用户在对话框中按下Tab键后,输入焦点将按照这个顺序依次传递。这个顺序是可以改变的。
、

七、默认按钮的说明

如果把测试对话框中的收缩按钮设置为默认按钮,即在其属性对话框中选中default button选项,那么当在测试对话框中按下回车键时,就不会再由CTestDialog类的OnOK函数来响应,而是由收缩按钮的响应函数 OnButton1来响应这一事件了。

  • 运行这时的Mybole程序,会发现当按下回车键后,对话框会发生收缩,再次按下回车键时,对话框又还原了。也就是说,这时回车键按下这一操作由收缩按钮的响应函数来响应了。
  • 现在取消收缩按钮的默认设置,并删除OK按钮,再次运行Mybole程序,打开测试对话框,按下回车键,会发现焦点仍是在各控件间依次转移。也就是说,当用户按下回车键时,Windows将查看对话框中是否存在指定的默认按钮:
    • 如果有,就调用该默认按钮单击消息的响应函数;
    • 如果没有,就会调用虚拟的OnOK函数,即使对话框没有包含默认的OK按钮。

📢📢📢 一定要注意这个默认OK按钮的ID是: IDOK

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值