MFC傻瓜式教程

本教程重操作,轻理论,为操作减负。需了解详细原理的朋友可以自行看各种书籍。
本文最早发布于我的博客https://hubojing.github.io/2017/04/13/MFC%E5%82%BB%E7%93%9C%E5%BC%8F%E6%95%99%E7%A8%8B/

MFC:Microsoft Foundation Class ,微软基础类库。

文章目录

对话框

对话框的创建和显示

  1. 新建MFC AppWizard(exe)工程,单文档类型。工程名:Mybole。编译运行。
    新建

  2. 点击帮助-关于Mybole。这是MFC自动创建的。
    关于

  3. 创建自己的对话框。点击Insert-Resource。选择Dialog,点击New。VC++自动将其标识设置为IDD_DIALOG1,并自动添加到ResourceView-Dialog项中。Dialog项下还有一个对话框资源标识:IDD_ABOUTBOX,即上一步中的“关于”对话框。
    Insert Resource对话框

新建的对话框资源

  1. 选中对话框本身,右键点击属性。将Caption设置为“测试”。
  2. 选择View-ClassWizard,点击create a new class,OK。出现下图,并输入下图选项。
    New Class
  3. 在随后出现的MFC ClassWizard对话框上点击OK。
    列表
    注意:看看左侧类列表中是否添加好了CTestDlg,否则会影响后续操作。

接下来,我们希望在程序中显示这个对话窗口。

  1. 点击右侧菜单Menu,选中IDR_MAINFRAME。点击帮助旁边的虚线框。
    Menu

  2. 对虚线框右键属性,修改为下图。
    属性

  3. 关闭属性。点击View-ClassWizard(中文是建立类向导),选择CMyboleView,用COMMAND命令消息响应函数。如图。
    COMMAND

模态对话框的创建

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

在FileView中选择MyboleView.cpp,编写程序。
  记得在开头添加头文件 #include “testdlg.h” (头文件大小写问题,linux区分,windows不区分)
编程
  显示模态对话框的具体实现代码:

void CMyboleView::OnDialog() 
{
	// TODO: Add your command handler code here
	CTestDlg dlg;
	dlg.DoModal();
}

编译运行,点击对话框。会发现若不确认该窗口,将无法点击其他窗口。
模态对话框1

模态对话框2

非模态对话框的创建

将上面的模态对话框代码注释掉。

改为:

void CMyboleView::OnDialog() 
{
	// TODO: Add your command handler code here
	//CTestDlg dlg;
	//dlg.DoModal();

	CTestDlg *pDlg = new CTestDlg;
	pDlg->Create(IDD_DIALOG1,this);
	pDlg->ShowWindow(SW_SHOW);
}

注意:需要把之前运行的对话框关掉才能编译成功。

然而,当它生命周期结束时,所保存的内存地址就丢失了,那么程序中也就无法再引用到它所指向的那块内存。于是,我们这样解决该问题。
MFC ClassWizard

注意:Message里双击添加函数或者点击add Class…

void CTestDlg::PostNcDestroy()
{
// TODO: Add your specialized code here and/or call the base class
delete this;
CDialog::PostNcDestroy();
}

区别:点击确定,对话框都会消失。但是,模态对话框窗口对象被销毁了。对非模态对话框来说,只是隐藏起来了,并未被销毁。
因此,若要销毁对话框,若有一个ID为IDOK的按钮,就必须重写基类的OnOK这个虚函数,并在重写的函数中调用DestroyWindow函数,完成销毁。并不要再调用基类的OnOK函数。
同样地,若有一个ID为IDCANCEL的按钮,也必须重写基类的OnCancel虚函数,并在重写的函数中调用DestroyWindow函数,完成销毁。并不要再调用基类的OnCancel函数。

动态创建按钮

注释掉非模态对话框代码,还原模态对话框代码。

点击ResourceView-IDD_DIALOG1,打开资源,用鼠标拖出控件面板上的Button按钮控件,对按钮右键,选择属性,设置如下。
按钮

接下来,我们实现当单击Add按钮时,在对话框中动态创建一个按钮这一功能。

  1. 为CTestDlg类添加一个私有的CButton成员变量。
      点击ClassView标签页右键,如图点击。
    ClassView

填入信息。
添加成员变量

  1. 添加Add按钮单击消息的响应函数。
      按钮点右键,选ClassWizard(建立类向导),如图。
    建立类向导

单击Edit Code,即可定位到该函数定义处。
  添加一下代码:

void CTestDlg::OnBtnAdd() 
{
	// TODO: Add your control notification handler code here
	m_btn.Create("New",BS_DEFPUSHBUTTON | WS_VISIBLE | WS_CHILD,
				 CRect(0,0,100,100),this,123);
}

为避免多次点击Add出现非法操作,我们需要进行如下步骤。

  1. 为CTestDlg类增加一个私有的BOOL类型成员变量。
    变量类型:BOOL
    变量名称:m_bIsCreated
    Access: private

  2. 在TestDlg.cpp中找到构造函数,将m_bIsCreated初始为FALSE。如图所示。
    这里写图片描述

或者改为如下亦可。
Static BOOL bIsCreated = FALSE;

  1. 回到Add,双击它,进入代码部分,改之。
void CTestDlg::OnBtnAdd() 
{
	// TODO: Add your control notification handler code here
	if(m_bIsCreated==FALSE)
	{
	m_btn.Create("New",BS_DEFPUSHBUTTON | WS_VISIBLE | WS_CHILD,
				CRect(0,0,100,100),this,123);
	m_bIsCreated = TRUE;
	}
	else
	{
		m_btn.DestroyWindow();
		m_bIsCreated = FALSE;
	}

}

或者以下亦能实现。

void CTestDlg::OnBtnAdd() 
{
	// TODO: Add your control notification handler code here
	if(!m_btn.m_hWnd)
	{
	m_btn.Create("New",BS_DEFPUSHBUTTON | WS_VISIBLE | WS_CHILD,
				CRect(0,0,100,100),this,123);
	}
	else
	{
		m_btn.DestroyWindow();
	}

}

效果:
  这里写图片描述

点击Add出现New窗口,再点击就销毁。

控件的访问

控件的调整

用Layout-Align,Layout-Make Same Size,Layout-Space Evenly里的选项进行调整。
这里写图片描述

静态文本控件

查看三个静态文本框,它们ID相同。我们可以更改第一个静态文本框ID为IDC_NUMBER1,再打开ClassWizard,可以在ObjectIDs看到新ID。
这里写图片描述
  对BN_CLICKED进行Add Function,并Edit Code:

此时运行程序点击第一个静态文本框并没有反应。这是因为:静态文本控件在默认状态下是不发送通告消息的

为了该控件能向父窗口发送鼠标事件,我们对该文本框右键-属性,切换到styles选项卡,勾上Notify。
这里写图片描述

现在可以显示了:
  点击就改变。
这里写图片描述

总结:为了使一个静态文本控件能够响应鼠标单击消息,那么需要进行两个特殊的步骤:第一步,改变它的ID;第二步,在它的属性对话框中选中Notify选项。

编辑框控件

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

第一种方式

void CTestDlg::OnBtnAdd() 
{
	int num1, num2, num3; 
	char ch1[10], ch2[10], ch3[10]; 
	GetDlgItem(IDC_EDIT1)->GetWindowText(ch1,10); 
	GetDlgItem(IDC_EDIT2)->GetWindowText(ch2,10);

	num1 = atoi(ch1); 
	num2 = atoi(ch2); 
	num3 = num1 + num2; 

	itoa(num3,ch3,10);
	GetDlgItem(IDC_EDIT3)->SetWindowText(ch3);
}

C语言转换函数:atoi 将一个由数字组成的字符串转换为相应的数值
itoa 数值转换为文本
itoa函数的第三个参数表示转换的进制,数字10表示十进制。

效果:
这里写图片描述

第二种方式

代码如下:

void CTestDlg::OnBtnAdd() 
{
	int num1, num2, num3; 
	char ch1[10], ch2[10], ch3[10]; 
	//GetDlgItem(IDC_EDIT1)->GetWindowText(ch1,10); 
	//GetDlgItem(IDC_EDIT2)->GetWindowText(ch2,10);
	
	GetDlgItemText(IDC_EDIT1,ch1,10);
	GetDlgItemText(IDC_EDIT2,ch2,10);

	num1 = atoi(ch1); 
	num2 = atoi(ch2); 
	num3 = num1 + num2; 

	itoa(num3,ch3,10);
	//GetDlgItem(IDC_EDIT3)->SetWindowText(ch3);
	SetDlgItemText(IDC_EDIT3,ch3);
}

GetDlgItemText 将返回对话框中指定ID的控件上的文本,相当于将上面的GetDlgItem和GetWindowText这两个函数功能组合起来了。
与之对应的是SetDlgItemText,用来设置对话框中指定ID的控件上的文本。

第三种方式

void CTestDlg::OnBtnAdd() 
{
	int num1, num2, num3; 
	//char ch1[10], ch2[10], ch3[10]; 
	//GetDlgItem(IDC_EDIT1)->GetWindowText(ch1,10); 
	//GetDlgItem(IDC_EDIT2)->GetWindowText(ch2,10);
	
	//GetDlgItemText(IDC_EDIT1,ch1,10);
	//GetDlgItemText(IDC_EDIT2,ch2,10);

	num1 = GetDlgItemInt(IDC_EDIT1);
	num2 = GetDlgItemInt(IDC_EDIT2);

	//num1 = atoi(ch1); 
	//num2 = atoi(ch2); 
	num3 = num1 + num2; 

	//itoa(num3,ch3,10);
	//GetDlgItem(IDC_EDIT3)->SetWindowText(ch3);
	//SetDlgItemText(IDC_EDIT3,ch3);
	SetDlgItemInt(IDC_EDIT3,num3);
}

第四种方式

将这三个编辑框分别与对话框类的三个成员变量相关联,然后通过这些成员变量来检索和设置编辑框的文本,这是最简单的访问控件的方式。
打开ClassWizard对话框,切换到Member Variables选项卡,如图。
这里写图片描述
  首先为IDC_EDIT1编辑框添加一个关联的成员变量,方法是在Control IDs列表中选中IDC_EDIT1,再单击Add Variable按钮,如图。
这里写图片描述
这里写图片描述

同样地,为IDC_EDIT2和IDC_EDIT3分别添加好成员变量。
  接着修改代码:

void CTestDlg::OnBtnAdd() 
{
	UpdateData();
	m_num3 = m_num1 + m_num2;
	UpdateData(FALSE);
}

对编辑框控件中输入的数值设定一个范围:
  打开ClassWizard-Member Variable,选中IDC_EDIT1,下方输入0和100。同样为IDC_EDIT2也设置好。
这里写图片描述

第五种方式

将编辑框控件再与一个变量相关联,代表控件本身。为IDC_EDIT1增加一个控件类型的变量:m_edit1,类别为Control。同样地,也为IDC_EDIT2和IDC_EDIT3添加。
这里写图片描述

修改代码:

void CTestDlg::OnBtnAdd() 
{
	int num1, num2, num3; 
	char ch1[10], ch2[10], ch3[10]; 
	
	m_edit1.GetWindowText(ch1,10);
	m_edit2.GetWindowText(ch2,10);

	//num1 = GetDlgItemInt(IDC_EDIT1);
	//num2 = GetDlgItemInt(IDC_EDIT2);

	num1 = atoi(ch1); 
	num2 = atoi(ch2); 
	num3 = num1 + num2; 

	itoa(num3,ch3,10);
	m_edit3.SetWindowText(ch3);
}

第六种方式

修改代码:

void CTestDlg::OnBtnAdd() 
{
	int num1, num2, num3; 
	char ch1[10], ch2[10], ch3[10]; 

	::SendMessage(GetDlgItem(IDC_EDIT1)->m_hWnd, WM_GETTEXT, 10, (LPARAM)ch1);
	::SendMessage(m_edit2.m_hWnd, WM_GETTEXT, 10, (LPARAM)ch2);

	num1 = atoi(ch1); 
	num2 = atoi(ch2); 
	num3 = num1 + num2; 

	itoa(num3,ch3,10);
	m_edit3.SendMessage(WM_SETTEXT, 0, (LPARAM)ch3);
}

第七种方式

修改代码:

void CTestDlg::OnBtnAdd() 
{
	int num1, num2, num3; 
	char ch1[10], ch2[10], ch3[10]; 
	
	SendDlgItemMessage(IDC_EDIT1, WM_GETTEXT, 10, (LPARAM)ch1);
	SendDlgItemMessage(IDC_EDIT2, WM_GETTEXT, 10, (LPARAM)ch2);
	
num1 = atoi(ch1); 
	num2 = atoi(ch2); 
	num3 = num1 + num2; 

	itoa(num3,ch3,10);
	SendDlgItemMessage(IDC_EDIT3, WM_SETTEXT, 0, (LPARAM)ch3);
}

获得编辑框复选的内容:
  在上述代码最后添加:
SendDlgItemMessage(IDC_EDIT3, EM_SETSEL, 0, -1); //0,-1表示全选若1,3表示选中1-3位复选
m_edit3.SetFocus();

效果:
这里写图片描述

总结

1 GetDlgItem()->Get(Set)WindowTest()
2 GetDlgItemText()/SetDlgItemText()
3 GetDlgItemInt()/SetDlgItemInt()
4 将控件和整型变量相关联
5 将控件和控件变量相关联
6 SendMessage()
7 SendDlgItemMessage()
  最常用是1、4、5。在利用MFC编程时,6、7用得少。

对话框伸缩功能的实现

对话框上再添加一个按钮,Caption设置为“收缩<<”点击ClassWizard,添加一个命令相应函数(BN_CLICKED)。具体实现代码为:

void CTestDlg::OnButton1() 
{
	CString str; 
	if(GetDlgItemText(IDC_BUTTON1,str), str == "收缩<<")
	{
		SetDlgItemText(IDC_BUTTON1, "拓展>>");
	}
	else
	{
		SetDlgItemText(IDC_BUTTON1, "收缩<<");
	}
}

拖动一个图像控件来划分对话框中要动态切除的部分。
这里写图片描述

修改该控件ID为IDC_SEPATATOR,styles选项卡勾上Sunken选项。
  修改代码:

void CTestDlg::OnButton1() 
{
	CString str; 
	if(GetDlgItemText(IDC_BUTTON1,str), str == "收缩<<")
	{
		SetDlgItemText(IDC_BUTTON1, "拓展>>");
	}
	else
	{
		SetDlgItemText(IDC_BUTTON1, "收缩<<");
	}
	static CRect rectLarge;
	static CRect rectSmall;

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

	if(rectLarge.IsRectNull())
	{
		CRect rectSeparator;
		GetWindowRect(&rectLarge);
		GetDlgItem(IDC_SEPARATOR)->GetWindowRect(&rectSeparator);

		rectSmall.left=rectLarge.left;
		rectSmall.top=rectLarge.top;
		rectSmall.right=rectLarge.right;
		rectSmall.bottom=rectSeparator.bottom;
	}
	if(str == "收缩<<")
	{
		SetWindowPos(NULL, 0, 0, rectSmall.Width(), rectSmall.Height(), SWP_NOMOVE | SWP_NOZORDER);
	}
	else
	{
		SetWindowPos(NULL, 0, 0, rectLarge.Width(), rectLarge.Height(), SWP_NOMOVE | SWP_NOZORDER);
	}
}

效果:
这里写图片描述

点击“收缩<<”:
这里写图片描述

若希望隐藏分隔条,则设置属性去掉“Visible”前的勾。

输入焦点的传递

为了屏蔽掉默认的回车键关闭对话框这一功能,应该在对话框子类(此处是CTestDlg类)中重写OK按钮的消息响应函数。
  首先点击OK按钮,添加鼠标单击消息响应函数。注释掉原有函数。

法一

在ClassView选项卡的CTestDlg类添加WM_INITDIALOG消息的响应函数。对类右键,选择Add Windows Message Handler,在弹出的框左侧选择WM_INITDIALOG,直接单击Add and Edit,跳转。
  修改代码为:

void CTestDlg::OnOK() 
{
	// TODO: Add extra validation here
	
	//CDialog::OnOK();
}


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 CTestDlg::OnInitDialog() 
{
	CDialog::OnInitDialog();
	prevProc=(WNDPROC)SetWindowLong(GetDlgItem(IDC_EDIT1)->m_hWnd,
		GWL_WNDPROC, (LONG)NewEditProc);
	return TRUE;
}

查看第一个编辑框的属性,打开styles选项卡,勾上MultiLine(多行)。即可实现焦点的传递。

法二

只需要改变一行代码:

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);
		}
	}

法三

编辑框属性有一个WS_TABSTOP,如果勾选了,则在对话框中按下Tab键后,输入焦点可以转移到此控件上。

修改一行代码:

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

三种方法的缺点:只修改了第一个编辑框的窗口过程,因此从第二到第三个编辑框的焦点转移无法实现,除非继续修改第二个编辑窗口。

再介绍一种方法解决这个问题。

法四

在MFC中,默认情况下,当在对话框窗口中按下回车键时,会调用对话框的默认按钮的响应函数,我们可以在此默认按钮的响应函数中把焦点依次向下传递。

首先取消第一个编辑框的MultiLine。
  接着修改OnOK函数为:

void CTestDlg::OnOK() 
{
	// TODO: Add extra validation here
	//GetDlgItem(IDC_EDIT1)->GetNextWindow()->SetFocus();
	//GetFocus()->GetNextWindow()->SetFocus();
	//GetFocus()->GetWindow(GW_HWNDNEXT)->SetFocus();
	GetNextDlgTabItem(GetFocus())->SetFocus();
	//CDialog::OnOK();
}```

  注释掉的部分是各种失败的尝试,各有各的bug。现在程序是正常的。

	**注意:然而该屏蔽回车键的方法并非是常规做法,应该在PreTranslateMessage中进行拦截。(return TRUE即拦截)**
  具体做法:
  现在Testdlg.h中添加:
```C++
class CTestDlg : public CDialog
{

protected:
	virtual BOOL PreTranslateMessage(MSG* pMsg);

public:
	virtual void OnOK();
……

接着:

CTestDlg::PreTranslateMessage(MSG* pMsg)
{
	//屏蔽ESC关闭窗体
	if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_ESCAPE)
	{
		return TRUE;
	}
	//屏蔽回车关闭窗体,但会导致回车在窗体上失效.
	/*
	if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_RETURN && pMsg->wParam)
	{
		return TRUE;
	}
	*/
	else 
	{
		return CDialog::PreTranslateMessage(pMsg);
	}
}

void CTestDlg::OnOK() 
{
	// TODO: Add extra validation here
	
	//CDialog::OnOK();
}

点击Layout-Tab order,这些序号就是各控件的Tab顺序。顺序可改变,依次点击希望的顺序控件即可。

调用顺序:当用户按下回车键时,Windows将查看对话框中是否存在指定的默认按钮,如果有,就调用该默认按钮单击消息的响应函数。如果没有,就会调用虚拟的OnOK函数,即使对话框没有包含默认的OK按钮(这个默认OK按钮的ID是IDOK)。

文件和注册表操作

C语言对文件操作的支持

新建单文档类型的MFC应用程序,工程名为File,并为主菜单添加一个子菜单,名称为“文件操作”,然后为其添加两个菜单项,并分别为它们添加相应的命令响应函数(通过COMMAND),让CFileView类接收这些菜单项的命令响应。
这里写图片描述
这里写图片描述

文件的打开和写入

代码:

void CFileView::OnFileWrite() 
{
	FILE *pFile = fopen("1.txt","w");
	fwrite("http://www.sunxin.org", 1, strlen("http://www.sunxin.org"), pFile); 
}

编译后可看到文件夹中生成了1.txt,打开有一行网址。

文件的关闭

增加一行代码:

void CFileView::OnFileWrite() 
{
	FILE *pFile = fopen("1.txt","w");
	fwrite("http://www.sunxin.org", 1, strlen("http://www.sunxin.org"), pFile); 
	fclose(pFile);
}

文件指针定位

代码:

void CFileView::OnFileWrite() 
{
	FILE *pFile = fopen("1.txt","w");
	fwrite("http://www.sunxin.org", 1, strlen("http://www.sunxin.org"), pFile); 
	fwrite("欢迎访问", 1, strlen("欢迎访问"), pFile);
	fclose(pFile);
}

显示:http://www.sunxin.org欢迎访问

将文件指针移动到文件的开始位置处:
  代码:

void CFileView::OnFileWrite() 
{
	FILE *pFile = fopen("1.txt","w");
	fwrite("http://www.sunxin.org", 1, strlen("http://www.sunxin.org"), pFile); 
	fseek(pFile, 0, SEEK_SET);
	fwrite("ftp:", 1, strlen("ftp:"),pFile);
	//fwrite("欢迎访问", 1, strlen("欢迎访问"), pFile);
	fclose(pFile);
}

显示:ftp:?/www.sunxin.org

文件的读取

在OnFileRead函数中写入代码:

void CFileView::OnFileRead() 
{
	FILE *pFile = fopen("1.txt","r");
	char ch[100];
	fread(ch, 1, 100, pFile);
	fclose(pFile);
	MessageBox(ch);
	
}

编译运行:
  
这里写图片描述

原因:C语言以“\0”结束。

解决方法:
  法一:
  修改代码:

void CFileView::OnFileWrite() 
{
	FILE *pFile = fopen("1.txt","w");
	char buf[22] = "http://www.sunxin.org";
	buf[21] = '\0';
	fwrite(buf, 1, 22, pFile);
	fclose(pFile);
}

先点击写入文件,再点击读取文件,就可以看到正确的内容。
  缺点:增加了文件大小。

法二:

void CFileView::OnFileRead() 
{
	FILE *pFile = fopen("1.txt","r");
	char ch[100];
	memset(ch, 0, 100);
	fread(ch, 1, 100, pFile);
	fclose(pFile);
	MessageBox(ch);
	
}

法三:
  读取文件时,不知道文件大小时的做法。

void CFileView::OnFileRead() 
{
	FILE *pFile = fopen("1.txt","r");
	char *pBuf;
	fseek(pFile, 0, SEEK_END);
	int len=ftell(pFile);
	pBuf = new char[len+1];
	rewind(pFile);
	fread(pBuf, 1, len, pFile);
	pBuf[len] = 0;
	fclose(pFile);
	MessageBox(pBuf);
}

二进制文件和文本文件

代码:

void CFileView::OnFileWrite() 
{
	FILE *pFile = fopen("2.txt", "w");
	char ch[3];
	ch[0] = 'a';
	ch[1] = 10;
	ch[2] = 'b';
	fwrite(ch, 1, 3, pFile);
	fclose(pFile);
}

void CFileView::OnFileRead() 
{
	FILE *pFile = fopen("2.txt","r");
	char ch[100];
	fread(ch, 1, 3, pFile);
	ch[3] = 0;
	fclose(pFile);
	MessageBox(ch);
}

效果:
  
这里写图片描述

文本方式:10实际上是换行符的ASCII码。

以文本方式和二进制方式读取文件是有明显的区别的。

文本方式和二进制方式

二进制方式:换行是由两个字符组成的,即ASCII码10(回车符)和13(换行符)。
  写入和读取文件时要保持一致。如果采用文本方式写入,应采用文本方式读取;如果采用二进制方式写入数据,在读取时也应采用二进制方式。

面试题:给你一个整数,如:98341,将这个整数保存到文件中,要求在以记事本程序打开该文件时,显示的是:98341。
  法一:

void CFileView::OnFileWrite() 
{
	FILE *pFile = fopen("3.txt", "w");
	char ch[5];
	ch[0] = 9 + 48;
	ch[1] = 8 + 48;
	ch[2] = 3 + 48;
	ch[3] = 4 + 48;
	ch[4] = 1 + 48;

	fwrite(ch, 1, 5, pFile);
	fclose(pFile);

}

void CFileView::OnFileWrite() 
{
	FILE *pFile = fopen("3.txt", "w");
	int i = 98341;
	char ch[5];
	itoa(i, ch, 10);

	fwrite(ch, 1, 5, pFile);
	fclose(pFile);

}

面试题:给定一个字符串,其中既有数字字符,又有26个英文字母中的几个字符,让你判断一下哪些是数字字符。

对这种问题,实际上就是判断各字符的ASCII码,对于数字字符来说,它们的ASCII码大于等于48,小于等于57。

C++对文件操作的支持

void CFileView::OnFileWrite() 
{
	ofstream ofs("4.txt");
	ofs.write("http://www.sunxin.org",strlen("http://www.sunxin.org"));
	ofs.close;

}

void CFileView::OnFileRead() 
{
	ifstream ifs("4.txt");
	char ch[100];
	memset(ch, 0, 100);
	ifs.read(ch,100);
	ifs.close();
	MessageBox(ch);
}

Win32 API 对文件操作的支持

文件的创建、打开和写入

void CFileView::OnFileWrite() 
{
	//定义一个句柄变量
	HANDLE hFile;
	//创建文件
	hFile = CreateFile("5.txt", GENERIC_WRITE, 0, NULL, CREATE_NEW, 
		FILE_ATTRIBUTE_NORMAL, NULL);
	//接收实际写入的字节数
	DWORD dwWrites;
	//写入数据
	WriteFile(hFile,"http://www.sunxin.org",strlen("http://www.sunxin.org"),
		&dwWrites, NULL);
	//关闭文件句柄
	CloseHandle(hFile);
}

文件的读取

void CFileView::OnFileRead() 
{
	HANDLE hFile;
	//打开文件
	hFile = CreateFile("5.txt", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	//接收实际收到的数据
	char ch[100];
	//接收实际读取到的字节数
	DWORD dwReads;
	//读取数据
	ReadFile(hFile, ch, 100, &dwReads, NULL);
	//设置字符串结束字符
	ch[dwReads] = 0;
	//关闭打开的文件对象的句柄
	CloseHandle(hFile);
	//显示读取到的数据
	MessageBox(ch);
}

菜单

菜单命令响应函数

新建一个单文档的MFC AppWizard(exe)工程,工程名为Menu。Build运行。

这里写图片描述
  左上角点击按钮,可以让属性框始终显示,不会因为点击对话框以外的地方就消失。
  去掉Pop-up弹出前的勾,将ID改为ID_TEST。给Test添加响应函数在CMainFrame中,在函数中加入 MessageBox(“MainFrame Clicked”);
  效果:
这里写图片描述

菜单命令的路由

程序类对菜单命令的响应顺序

响应Test
  菜单项命令的顺序依次是:视类、文档类、框架类,最后才是应用程序类。

Windows消息的分类

凡是从CWnd派生的类,它们既可以接收标准消息,也可以接收命令消息和通告消息。而对于那些从CCmdTarget派生的类,则只能接收命令消息和通告消息,不能接收标准消息。
本例中的文档类(CMenuDoc)和应用程序类(CWinApp),因为它们都派生于CCmdTarget类,所以它们可以接收菜单命令消息。但它们不是从CWnd类派生的,所以不能接收标准消息。

菜单命令的路由

菜单命令消息路由的具体过程:当点击某个菜单项时,最先接收到这个菜单命令消息的是框架类。框架类将把接收到的这个消息交给它的子窗口,即视类,由视类首先进行处理。视类首先根据命令消息映射机制查找自身是否对此消息进行了响应,如果响应了,就调用相应响应函数对这个消息进行处理,消息路由过程结束;如果视类没有对此命令消息做出响应,就交由文档类,文档类同样查找自身是否对这个菜单命令进行了响应,如果响应了,就由文档类的命令消息响应函数进行处理,路由过程结束。如果文档类也未做出响应,就把这个命令消息交还给视类,后者又把该消息交还给框架类。框架类查看自己是否对这个命令消息进行了响应,如果它也没有做出响应,就把这个菜单命令消息交给应用程序类,由后者来进行处理。

基本菜单操作

标记菜单

运行刚才创建的Menu程序,点击查看,前面都有一个对号,这种类型就是标记菜单。
在CMainFrame类的OnCreate的return语句之前添加这句代码 GetMenu()->GetSubMenu(0)->CheckMenuItem(0, MF_BYPOSITION | MF_CHECKED); 或者GetMenu()->GetSubMenu(0)->CheckMenuItem(ID_FILE_NEW, MF_BYCOMMAND | MF_CHECKED);
  Build并运行,可发现新建左边已添加一个复选标记。

默认菜单项

在刚才的代码下,添加 GetMenu()->GetSubMenu(0)->SetDefaultItem(1, TRUE); 或者GetMenu()->GetSubMenu(0)->SetDefaultItem(ID_FILE_OPEN, FALSE); 编译运行,会发现“打开”变成了粗体。

注意:“打印”的索引是5,不是4。计算菜单项索引时,一定要把分割栏菜单项计算在内。并且,一个子菜单只能有一个默认菜单项。

图形标记菜单

Insert-Resource-Bitmap,创建一个位图资源。如图。
这里写图片描述
  为CMainFrame类添加一个CBitmap类型的成员变量:m_bitmap。

接着添加代码:
CString str;
str.Format(“x=%d”,y=%d", GetSystemMetrics(SM_CXMENUCHECK),GetSystemMetrics(SM_CYMENUCHECK));
MessageBox(str);
m_bitmap.LoadBitmap(IDB_BITMAP1);
GetMenu()->GetSubMenu(0)->SetMenuItemBitmaps(0, MF_BYPOSITION, &m_bitmap, &m_bitmap);

禁用菜单项

通常把MF_GRAYED和MF_DISABLED这两个标志放在一起使用。不过这么做并不是必需的。
  删除之前的代码,写入 GetMenu()->GetSubMenu(0)->EnableMenuItem(1, MF_BYPOSITION | MF_DISABLED | MF_GRAYED);
  打开“文件”子菜单,发现“打开”菜单栏变灰,点击不起作用。

移除和装载菜单

再添加一行代码: SetMenu(NULL); 此时菜单栏被移除了。
  再添加几行代码:
CMenu menu;
menu.LoadMenu(IDR_MAINFRAME);
SetMenu(&menu);
menu.Detach();
  此时菜单栏又装载了。

	CMenu menu;
	menu.CreateMenu();
	GetMenu()->AppendMenu(MF_POPUP, (UINT)menu.m_hMenu, "Test1");
	menu.AppendMenu(MF_STRING, 111, "Hello");
	menu.AppendMenu(MF_STRING, 112, "Bye");
	menu.AppendMenu(MF_STRING, 113, "Mybole");
	
	
	menu.Detach();

	CMenu menu1;
	menu1.CreateMenu();
	GetMenu()->InsertMenu(2, MF_POPUP | MF_BYPOSITION, (UINT)menu1. m_hMenu,"Test");

	
	menu1.Detach();

	GetMenu()->GetSubMenu(2)->AppendMenu(MF_STRING, 118, "Welcome");
	GetMenu()->GetSubMenu(0)->AppendMenu(MF_STRING, 114, "Welcome");
	GetMenu()->GetSubMenu(0)->InsertMenu(ID_FILE_OPEN, MF_BYCOMMAND | MF_STRING, 115, "VC编程");

MFC菜单命令更新机制

这里写图片描述

MFC命令更新机制:当要显示菜单时,操作系统发出WM_INITMENUPOPOP消息,然后由程序窗口的基类如CFrameWnd接管,它会创建一个CCmdUI对象,并与程序的第一个菜单项相关联,调用该对象的一个成员函数DoUpdate()。这个函数发出CN_UPDATE_COMMAND_UI消息,这条消息带有一个指向CCmdUI对象的指针。这时,系统会判断是否存在一个ON_UPDATE_COMMAND_UI宏去捕获这个菜单项消息。如果找到这样一个宏,就调用相应的消息响应函数进行处理,在这个函数中,可以利用传递过来的CCmdUI对象去调用相应的函数,使该菜单项可以使用,或禁用该菜单项。当更新完第一个菜单项后,同一个CCmdUI对象就设置为与第二个菜单项相关联,依此顺序进行,直到完成所有菜单项的处理。

添加代码:

void CMainFrame::OnUpdateEditCut(CCmdUI* pCmdUI) 
{
	// TODO: Add your command update UI handler code here
	pCmdUI->Enable();
}

编辑-剪切 可用了。
  如果要把工具栏上的一个工具按钮与菜单栏中的某个菜单项相关联,只要将它们的ID设置为同一个标识就可以了。

如果希望禁用文件-新建,为ID_FILE_NEW添加UPDATE_COMMAND_UI消息响应函数。
  代码如下:


void CMainFrame::OnUpdateFileNew(CCmdUI* pCmdUI) 
{
	// TODO: Add your command update UI handler code here
	pCmdUI->Enable(FALSE);
}

或者

void CMainFrame::OnUpdateFileNew(CCmdUI* pCmdUI) 
{
	if (2 == pCmdUI->m_nIndex)
	pCmdUI->Enable();
}

快捷菜单

1. 新增一个新的菜单资源。点开,顶级菜单设置任意的文本,如abc。添加两个菜单项:
  显示 IDM_SHOW
  退出 IDM_EXIT
  2. 给CMenuView类添加WM_RBUTTONDOWN消息响应函数。

void CMenu2View::OnRButtonDown(UINT nFlags, CPoint point) 
{
	CMenu menu;
	menu.LoadMenu(IDR_MENU1);
	CMenu* pPopup = menu.GetSubMenu(0);
	ClientToScreen(&point);
	pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this);

	CView::OnRButtonDown(nFlags, point);
}

效果:
  

3.对“显示”右键ClassWizard,可以取消创建新类的询问。分别为CMainFrame类和CMenuView类添加一个响应。
  代码:

void CMenu2View::OnShow() 
{
	MessageBox("View show");	
}
void CMainFrame::OnShow() 
{
	MessageBox("Main show");
}

结果是显示“View show”。说明只有视类才能对快捷菜单项命令做出响应。若想让CMainView类对此快捷菜单项进行响应的话,修改代码:

void CMenu2View::OnRButtonDown(UINT nFlags, CPoint point) 
{
	CMenu menu;
	menu.LoadMenu(IDR_MENU1);
	CMenu* pPopup = menu.GetSubMenu(0);
	ClientToScreen(&point);
	//pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this);
	pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, GetParent());

	CView::OnRButtonDown(nFlags, point);
}

同时删去视类的显示。

动态菜单操作

添加菜单项目

在CMainFrame类的OnCreate函数中添加代码:

	CMenu menu;
	menu.CreateMenu();
	GetMenu()->AppendMenu(MF_POPUP, (UINT)menu.m_hMenu, "Test");
	menu.Detach();

插入菜单项目

	CMenu menu;
	menu.CreateMenu();
	/*GetMenu()->AppendMenu(MF_POPUP, (UINT)menu.m_hMenu, "Test");
	menu.Detach();*/
	GetMenu()->InsertMenu(2, MF_POPUP | MF_BYPOSITION, (UINT)menu. m_hMenu,"Test");
	menu.Detach();

如果要在新插入的子菜单中添加菜单项的话,同样可以使用AppendMenu函数来实现。

CMenu menu;
	menu.CreateMenu();
	/*GetMenu()->AppendMenu(MF_POPUP, (UINT)menu.m_hMenu, "Test");
	menu.Detach();*/
	GetMenu()->InsertMenu(2, MF_POPUP | MF_BYPOSITION, (UINT)menu. m_hMenu,"Test");

	menu.AppendMenu(MF_STRING, 111, "Hello");
	menu.AppendMenu(MF_STRING, 112, "Bye");
	menu.AppendMenu(MF_STRING, 113, "Mybole");
	menu.Detach();

111、112、113是随便赋予的ID号。
  
这里写图片描述

若要在“文件”子菜单下添加一个菜单项Welcome,再添加一行代码: GetMenu()->GetSubMenu(0)->AppendMenu(MF_STRING, 114, “Welcome”);
  若要在“文件”中的“新建”和“打开”插入一个菜单项VC编程,再添加一行代码:
GetMenu()->GetSubMenu(0)->InsertMenu(ID_FILE_OPEN, MF_BYCOMMAND | MF_STRING, 115, “VC编程”);

删除菜单

删除“编辑”:在CMainFrame类的OnCreate函数最后(return之前)添加:
  GetMenu()->DeleteMenu(1, MF_BYPOSITION);
  删除“文件”下的“打开”:
  GetMenu()->GetSubMenu(0)->DeleteMenu(2, MF_BYPOSITION);

动态添加的菜单项的命令响应

Resource.h中添加新ID

#define IDM_HELLO	111
将menu.AppendMenu(MF_STRING, 111, “Hello”); 改为 menu.AppendMenu(MF_STRING, IDM_HELLO, “Hello”);

  三部曲:
  1.	点开MainFrm.h,增加为
```C++
//{{AFX_MSG(CMainFrame)
	afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
	afx_msg void OnShow();
	//}}AFX_MSG
	afx_msg void OnHello();
	DECLARE_MESSAGE_MAP()

2. 点开MainFrm.cpp,增加为


BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
	//{{AFX_MSG_MAP(CMainFrame)
	ON_WM_CREATE()
	ON_COMMAND(IDM_SHOW, OnShow)
	ON_COMMAND(IDM_HELLO, OnHello)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

3. CMainFrame类中添加

void CMainFrame::OnHello()
{
	MessageBox("Hello");
}

电话本示例程序

删除之前写入CMainFrame类的OnCreate函数,留下原始函数。

动态添加子菜单的实现

利用ClassWizard添加WM_CHAR消息。在Menu2View.h中添加:

private:
	int m_nIndex;
	CMenu m_menu;

在Menu2View.cpp里,添加:

CMenu2View::CMenu2View()
{
	// TODO: add construction code here
	m_nIndex = -1;
}

显示输入的字符

添加菜单项及其命令响应函数

在资源编辑器中打开程序的菜单,在“帮助”后添加一个新菜单abc,添加4个菜单项。名称为1,ID为IDM_PHONE1,以此类推。用ClassWizard为CMenu2View类分别加上这四个菜单项的命令响应函数。
  修改CMenu2View类的头文件,如下:

protected:
	//{{AFX_MSG(CMenu2View)
	afx_msg void OnRButtonDown(UINT nFlags, CPoint point);
	afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);
	afx_msg void OnPhone1();
	afx_msg void OnPhone2();
	afx_msg void OnPhone3();
	afx_msg void OnPhone4();
	//}}AFX_MSG
	DECLARE_MESSAGE_MAP()

CMenu2View.cpp中,

	//{{AFX_MSG_MAP(CMenu2View)
	ON_WM_CHAR()
	//}}AFX_MSG_MAP
	ON_COMMAND(IDM_PHONE1, OnPhone1)
	ON_COMMAND(IDM_PHONE2, OnPhone2)
	ON_COMMAND(IDM_PHONE3, OnPhone3)
	ON_COMMAND(IDM_PHONE4, OnPhone4)
	// Standard printing commands
	ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
	ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
	ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
END_MESSAGE_MAP()
void CMenu2View::OnPhone1() 
{
	CClientDC dc(this);
	dc.TextOut(0, 0, m_strArray.GetAt(0));
	
}

void CMenu2View::OnPhone2() 
{
	CClientDC dc(this);
	dc.TextOut(0, 0, m_strArray.GetAt(1));
	
}

void CMenu2View::OnPhone3() 
{
	CClientDC dc(this);
	dc.TextOut(0, 0, m_strArray.GetAt(2));
	
}

void CMenu2View::OnPhone4() 
{
	CClientDC dc(this);
	dc.TextOut(0, 0, m_strArray.GetAt(3));
	
}

框架类窗口截获菜单命令消息

右键单击CMainFrame,选择Add Virtual Functions-OnCommand,单击Add Handler,再点击Edit Existing。
这里写图片描述
  代码:

BOOL CMainFrame::OnCommand(WPARAM wParam, LPARAM lParam) 
{
	int MenuCmdID = LOWORD(wParam);
	CMenu2View *pView = (CMenu2View *)GetActiveView();
	if (MenuCmdID >= IDM_PHONE1 && MenuCmdID < IDM_PHONE1 + pView->m_strArray.GetSize())
	{
	//MessageBox("Test");
	CClientDC dc(pView);
	dc.TextOut(0, 0, pView->m_strArray.GetAt(MenuCmdID - IDM_PHONE1));
	return TRUE;
	}
	return CFrameWnd::OnCommand(wParam, lParam);
}

将MainFrm.cpp里添加#include “Menu2View.h” 。
  将Menu2View.cpp中的#include “Menu2Doc.h”剪切到Menu2View.h文件的前部(#endif // _MSC_VER > 1000下面)。

最终代码:


void CMenu2View::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	CClientDC dc(this);
	if (0x0d == nChar)
	{
		if (0 == ++m_nIndex)
		{
			m_menu.CreatePopupMenu();
			GetParent()->GetMenu()->AppendMenu(MF_POPUP, (UINT)m_menu.m_hMenu, "PhoneBook");
			GetParent()->DrawMenuBar();
		}
		m_menu.AppendMenu(MF_STRING, IDM_PHONE1 + m_nIndex, m_strLine.Left(m_strLine.Find(' ')));
		m_strArray.Add(m_strLine);
		m_strLine.Empty();
		Invalidate();
	}
	else
	{
		m_strLine += nChar;
		dc.TextOut(0, 0, m_strLine);
	}
	CView::OnChar(nChar, nRepCnt, nFlags);
}

效果:
这里写图片描述

简单绘图

MFC消息映射机制

与消息有关的三处信息:1.头文件XXXX.h中 2.源文件XXXX.cpp中 3.源文件XXXX.cpp的响应函数中

绘制线条

对CDrawView右键点击Add Member Variable,变量名称:m_ptOrigin,类型:CPoint,访问权限设置:Private。
  代码:

void CDrawView::OnLButtonDown(UINT nFlags, CPoint point) 
{
	m_ptOrigin = point;
	CView::OnLButtonDown(nFlags, point);
}
void CDrawView::OnLButtonUp(UINT nFlags, CPoint point) 
{
	//首先获得窗口的设备描述表
	HDC hdc;
	hdc = ::GetDC(m_hWnd);
	//移动到线条的起点
	MoveToEx(hdc, m_ptOrigin.x, m_ptOrigin.y, NULL);
	//画线
	LineTo(hdc, point.x, point.y);
	//释放设备描述表
	::ReleaseDC(m_hWnd, hdc);

	CView::OnLButtonUp(nFlags, point);
}

这里写图片描述

利用MFC的CDC类实现画线功能

void CDrawView::OnLButtonUp(UINT nFlags, CPoint point) 
{
	/*//首先获得窗口的设备描述表
	HDC hdc;
	hdc = ::GetDC(m_hWnd);
	//移动到线条的起点
	MoveToEx(hdc, m_ptOrigin.x, m_ptOrigin.y, NULL);
	//画线
	LineTo(hdc, point.x, point.y);
	//释放设备描述表
	::ReleaseDC(m_hWnd, hdc);*/

	CDC* pDC = GetDC();
	pDC->MoveTo(m_ptOrigin);
	pDC->LineTo(point);
	ReleaseDC(pDC);

	CView::OnLButtonUp(nFlags, point);
}

利用MFC的CWindowDC类实现画线功能

void CDrawView::OnLButtonUp(UINT nFlags, CPoint point) 
{
	CWindowDC dc(GetParent());
	dc.MoveTo(m_ptOrigin);
	dc.LineTo(point);

	CView::OnLButtonUp(nFlags, point);
}

这里写图片描述

在桌面窗口中画线

void CDrawView::OnLButtonUp(UINT nFlags, CPoint point) 
{
	CWindowDC dc(GetDesktopWindow());
	dc.MoveTo(m_ptOrigin);
	dc.LineTo(point);

	CView::OnLButtonUp(nFlags, point);
}

这里写图片描述

注意:在桌面上画图需要权限(一般写代码时需要避免软件以外的操作)。

绘制彩色线条

在程序中,当构造一个GDI对象后,该对象并不会立即生效,必须选入设备描述表,它才会在以后的绘制操作中生效。
一般情况下,在完成绘图操作之后,都要利用SelectObject函数把之前的GDI对象选入设备描述表,以便使其恢复到先前的状态。

void CDrawView::OnLButtonUp(UINT nFlags, CPoint point) 
{
	CPen pen(PS_SOLID, 1, RGB(255, 0, 0));
	CClientDC dc(this);
	CPen* pOldPen = dc.SelectObject(&pen);
	dc.MoveTo(m_ptOrigin);
	dc.LineTo(point);
	dc.SelectObject(pOldPen);

	CView::OnLButtonUp(nFlags, point);
}

运行的效果是红色线条。

改为 CPen pen(PS_DASH, 1, RGB(255, 0, 0)); 是虚线。(其中第二个参数需小于等于10)
CPen pen(PS_DOT, 1, RGB(255, 0, 0)); 是点线。

使用画刷绘图

简单画刷

void CDrawView::OnLButtonUp(UINT nFlags, CPoint point) 
{
	//创建一个红色画刷
	CBrush brush(RGB(255, 0, 0));
	//创建并获得设备描述表
	CClientDC dc(this);
	//利用红色画刷填充鼠标拖拽过程中形成的矩形区域
	dc.FillRect(CRect(m_ptOrigin, point),&brush);

	CView::OnLButtonUp(nFlags, point);
}```
 ![这里写图片描述](https://img-blog.csdn.net/20170420230440190?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHVib2ppbmc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

### 位图画刷
  Insert-Resource-Bitmap-New,在这里发挥灵魂画手的天赋吧!
  代码:
```C++
void CDrawView::OnLButtonUp(UINT nFlags, CPoint point) 
{
	//创建位图对象
	CBitmap bitmap;
	//加载位图资源
	bitmap.LoadBitmap(IDB_BITMAP1);
	//创建位图画刷
	CBrush brush(&bitmap);
	//创建并获得设备描述表
	CClientDC dc(this);
	//利用位图画刷填充鼠标拖拽过程中形成的矩形区域
	dc.FillRect(CRect(m_ptOrigin, point),&brush);

	CView::OnLButtonUp(nFlags, point);
}

这里写图片描述
  我画的是不是很滑稽(手动滑稽)

透明画刷

先进行一种尝试:

void CDrawView::OnLButtonUp(UINT nFlags, CPoint point) 
{
	//创建并获得设备描述表
	CClientDC dc(this);
	//绘制一个矩形
	dc.Rectangle(CRect(m_ptOrigin,point));

	CView::OnLButtonUp(nFlags, point);
}

这里写图片描述
  如果希望矩形内部是透明的,能够看到被遮挡的图形,就要创建一个透明画刷。

void CDrawView::OnLButtonUp(UINT nFlags, CPoint point) 
{
	//创建并获得设备描述表
	CClientDC dc(this);
	//创建一个空画刷
	CBrush *pBrush = CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));
	//将空画刷选入设备描述表
	CBrush *pOldBrush = dc.SelectObject(pBrush);
	//绘制一个矩形
	dc.Rectangle(CRect(m_ptOrigin, point));
	//恢复先前的画刷
	dc.SelectObject(pOldBrush);

	CView::OnLButtonUp(nFlags, point);
}

这里写图片描述

绘制连续线条

首先为视类增加鼠标移动消息(WM_MOUSEMOVE)的响应函数(默认OnMouseMove),并为视类添加一个BOOL型的私有成员变量m_bDraw。
在视类头文件定义:

 Private:
 BOOL m_bDraw;
 ```
在视类的构造函数中:	
```C++
m_bDraw = FALSE;

在OnLButtonDown中:

m_bDraw = TRUE;

在OnLButtonUp中:

m_bDraw = FALSE;
void CDrawView::OnMouseMove(UINT nFlags, CPoint point) 
{
	CClientDC dc(this);
	if(m_bDraw == TRUE)
	{
		dc.MoveTo(m_ptOrigin);
		dc.LineTo(point);
		//修改线段的起点
		m_ptOrigin = point;
	}

	CView::OnMouseMove(nFlags, point);
}

这里写图片描述

给线条换色:

void CDrawView::OnMouseMove(UINT nFlags, CPoint point) 
{
	CClientDC dc(this);
		//创建一个红色的、宽度为1的实线画笔
		CPen pen(PS_SOLID, 1, RGB(255, 0, 0));
		//把创建的画笔选入设备描述表
		CPen *pOldPen = dc.SelectObject(&pen);
		if (m_bDraw == TRUE)
		{
		dc.MoveTo(m_ptOrigin);
		dc.LineTo(point);
		//修改线段的起点
		m_ptOrigin = point;
	}
	//恢复设备描述表
		dc.SelectObject(pOldPen);

	CView::OnMouseMove(nFlags, point);
}

绘制扇形效果的线条

去掉上述代码中的 m_ptOrigin = point;

效果:
  这里写图片描述

绘制一个带边线的扇形:
  为CDrawView类增加一个CPoint类型的私有成员变量m_ptOld,用来保存鼠标上一个移动点。

在OnLButton中:

m_ptOld = point;

在OnMouseMove中:

void CDrawView::OnMouseMove(UINT nFlags, CPoint point) 
{
	CClientDC dc(this);
		//创建一个红色的、宽度为1的实线画笔
		CPen pen(PS_SOLID, 1, RGB(255, 0, 0));
		//把创建的画笔选入设备描述表
		CPen *pOldPen = dc.SelectObject(&pen);
		if (m_bDraw == TRUE)
		{
		dc.MoveTo(m_ptOrigin);
		dc.LineTo(point);
		dc.LineTo(m_ptOld);
		//修改线段的起点
		//m_ptOrigin = point;
		m_ptOld = point;
	}
	//恢复设备描述表
		dc.SelectObject(pOldPen);

	CView::OnMouseMove(nFlags, point);
}

最好将OnLButtonUp里原来写的代码删除或注释之。
  效果:
这里写图片描述

MFC提供一个设置绘图模式的函数SetROP2,带有一个参数R2_BLACK、R2_WHITE、R2_MERGENOTPEN等。
  例如,在CClientDC dc(this); 下方添加代码: dc.SetROP2(R2_MERGENOTPEN); 编译运行后看不到绘制的线条,这就是设置了R2_MERGENOTPEN这种绘图模式。
使用R2_BLACK,将会发现绘制的线条颜色始终都是黑色的。

文本编程

插入符

创建文本插入符

创建一个单文档类型的MFC AppWizard(exe)工程,取名为Text。
为CTextView类添加WM_CREATE消息的响应函数OnCreate,在此函数中创建一个宽度为20、高度为100的插入符。代码如下。

int CTextView::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
	if (CView::OnCreate(lpCreateStruct) == -1)
		return -1;
	
	CreateSolidCaret(20,100);
	ShowCaret();
	return 0;
}

这里写图片描述

让插入符适应于当前字体的大小:

int CTextView::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
	if (CView::OnCreate(lpCreateStruct) == -1)
		return -1;
	
	//创建设备描述表
	CClientDC dc(this);
	//定义文本信息结构体变量
	TEXTMETRIC tm;
	//获得设备描述表中的文本信息
	dc.GetTextMetrics(&tm);
	//根据字体大小,创建何时的插入符(除以8是经验值)
	CreateSolidCaret(tm.tmAveCharWidth/8, tm.tmHeight);
	//显示插入符
	ShowCaret();

	return 0;
}

运行结果就比较符合常规了。

创建图形插入符

新建一个位图资源,画一个图形。
在TextView.h中添加

private:
		CBitmap bitmap;

代码:

int CTextView::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
	if (CView::OnCreate(lpCreateStruct) == -1)
		return -1;
	

	bitmap.LoadBitmap(IDB_BITMAP1);
	CreateCaret(&bitmap);
	ShowCaret();

	return 0;
}

这里写图片描述

窗口重绘

OnDraw函数

实现在程序窗口中输出一串文字的功能。

void CTextView::OnDraw(CDC* pDC)
{
	CTextDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);

	CString str("VC++ 深入编程");
	pDC->TextOut(50, 50, str);
}

这里写图片描述

添加字符串资源

点击Resource View-String Table选项,在此字符串表最底部的空行上双击,即可弹出添加新字符串资源的对话框。ID:IDS_STRINGVC,Caption:“VC++编程 文本编程”。代码如下。

void CTextView::OnDraw(CDC* pDC)
{
	CTextDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);

	//CString str("VC++ 深入编程");
	CString str;
	str = "VC++ 深入编程";
	pDC->TextOut(50, 50, str);

	str.LoadString(IDS_STRINGVC);
	pDC->TextOut(0, 200, str);
}

这里写图片描述

路径


void CTextView::OnDraw(CDC* pDC)
{
	CTextDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);

	//CString str("VC++ 深入编程");
	CString str;
	str = "VC++ 深入编程";
	pDC->TextOut(50, 50, str);

	CSize sz = pDC->GetTextExtent(str);

	str.LoadString(IDS_STRINGVC);
	pDC->TextOut(0, 200, str);

	pDC->BeginPath();
	pDC->Rectangle(50, 50, 50+sz.cx, 50+sz.cy);
	pDC->EndPath();

	for(int i=0; i<300; i+=10)
	{
		pDC->MoveTo(0, i);
		pDC->LineTo(300, i);
		pDC->MoveTo(i,0);
		pDC->LineTo(i,300);
	}


 ![这里写图片描述](https://img-blog.csdn.net/20170420230830380?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHVib2ppbmc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

```C++
void CTextView::OnDraw(CDC* pDC)
{
	CTextDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);

	//CString str("VC++ 深入编程");
	CString str;
	str = "VC++ 深入编程";
	pDC->TextOut(50, 50, str);

	CSize sz = pDC->GetTextExtent(str);

	str.LoadString(IDS_STRINGVC);
	pDC->TextOut(0, 200, str);

	pDC->BeginPath();
	pDC->Rectangle(50, 50, 50+sz.cx, 50+sz.cy);
	pDC->EndPath();
	pDC->SelectClipPath(RGN_DIFF);

	for(int i=0; i<300; i+=10)
	{
		pDC->MoveTo(0, i);
		pDC->LineTo(300, i);
		pDC->MoveTo(i,0);
		pDC->LineTo(i,300);
	}

}

这里写图片描述
  这正是RGN_DIFF模式的效果。
  如果是RGN_AND,效果是新的裁剪区域是当前裁剪区域和当前路径层的交集。

路径层的作用:实现特殊效果。如,希望整幅图形中某一部分与其他部分有所区别,就可以把这部分的图形设置到一个路径层中,然后利用SelectClipPath函数设置一种模式,让路径层和裁剪区域进行互操作以达到一种特殊效果。

字符输入

当用户在键盘上按下某个字符按键后,要把该字符输出到程序窗口上。
首先让CTextView捕获WM_CHAR消息,接着为该类定义一个CString类型的成员变量:m_strLine,并在CTextView类的构造函数中将这个变量初始化:m_strLine = “”;

void CTextView::OnLButtonDown(UINT nFlags, CPoint point) 
{
	SetCaretPos(point);

	CView::OnLButtonDown(nFlags, point);
}

这里写图片描述
  为CTextView类再增加一个CPoint类型的成员变量,取名m_ptOrigin,权限为私有。在CTextView类的构造函数中设置其初值为0。

void CTextView::OnLButtonDown(UINT nFlags, CPoint point) 
{
	SetCaretPos(point);
	m_strLine.Empty();
	m_ptOrigin = point;

	CView::OnLButtonDown(nFlags, point);
}

注意:回车字符的ASCII码十六进制是0x0d,退格键的ASCII码十六进制值是0x08。

最终代码:

void CTextView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	CClientDC dc(this);
	TEXTMETRIC tm;
	dc.GetTextMetrics(&tm);
	if (0x0d == nChar)
	{
	m_strLine.Empty();
	m_ptOrigin.y += tm.tmHeight;
	}
	else if(0x08 == nChar)
	{
		COLORREF clr = dc.SetTextColor(dc.GetBkColor());
		dc.TextOut(m_ptOrigin.x, m_ptOrigin.y, m_strLine);
		m_strLine = m_strLine.Left(m_strLine.GetLength() - 1);
		dc.SetTextColor(clr);
	}
	else
	{
		m_strLine += nChar; 
	}
	CSize sz = dc.GetTextExtent(m_strLine);
	CPoint pt;
	pt.x = m_ptOrigin.x + sz.cx;
	pt.y = m_ptOrigin.y;
	SetCaretPos(pt);

	dc.TextOut(m_ptOrigin.x, m_ptOrigin.y, m_strLine);

	
	CView::OnChar(nChar, nRepCnt, nFlags);
}

这里写图片描述

设置字体

void CTextView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	CClientDC dc(this);
	CFont font;
	font.CreatePointFont(300, "华文行楷", NULL);
	CFont *pOldFont = dc.SelectObject(&font);
……
	dc.SelectObject(pOldFont);
	
	CView::OnChar(nChar, nRepCnt, nFlags);
}

这里写图片描述

字幕变色功能的实现

在这个Text例子中,我们在视类的OnCreate 函数中设置定时器,设置一个时间间隔为100ms,标识为1的定时器。

int CTextView::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
	……

	SetTimer(1, 100, NULL);

	return 0;
}

给CTextView类添加WM_TIMER消息的响应函数。

void CTextView::OnTimer(UINT nIDEvent) 
{
	m_nWidth += 5;

	CClientDC dc(this);
	TEXTMETRIC tm;
	dc.GetTextMetrics(&tm);
	CRect rect;
	rect.left =0;
	rect.top = 200;
	rect.right = m_nWidth;
	rect.bottom = rect.top + tm.tmHeight;

	dc.SetTextColor(RGB(255, 0, 0));
	CString str;
	str.LoadString(IDS_STRINGVC);
	dc.DrawText(str, rect, DT_LEFT);
	
	rect.top = 150;
	rect.bottom = rect.top + tm.tmHeight;
	dc.DrawText(str, rect, DT_RIGHT);
	
	CSize sz = dc.GetTextExtent(str);
	if (m_nWidth > sz.cx)
	{
		m_nWidth = 0;
		dc.SetTextColor(RGB(0, 255, 0));
		dc.TextOut(0, 200, str);
	}
	
	CView::OnTimer(nIDEvent);
}

红色渐变效果可看到。
这里写图片描述

绘图控制

简单绘图

新建一个单文档类型的MFC AppWizard(exe)工程,取名:Graphic。
  添加的菜单项:
这里写图片描述
  给CGraphicView类中添加一个私有变量:

UINT m_nDrawType;

在视类构造函数中将此变量初始化为0。

void CGraphicView::OnDot() 
{
	m_nDrawType = 1;
	
}

void CGraphicView::OnLine() 
{
	m_nDrawType = 2;	
}

void CGraphicView::OnRectangle() 
{
	m_nDrawType = 3;	
}

void CGraphicView::OnEllipse() 
{
	m_nDrawType = 4;	
}

CGraphicView类再增加一个CPoint类型的私有成员变量:m_ptOrigin。在CGraphicView类构造函数中,将该变量的值设置为0。

void CGraphicView::OnLButtonDown(UINT nFlags, CPoint point) 
{
	m_ptOrigin = point;
	
	CView::OnLButtonDown(nFlags, point);
}

void CGraphicView::OnLButtonUp(UINT nFlags, CPoint point) 
{
	CClientDC dc(this);
	//为边框设定颜色
	CPen pen(PS_SOLID, 1, RGB(255, 0, 0));
	dc.SelectObject(&pen);
	//能看到图形内部内容(透明)
	CBrush *pBrush = CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));
	dc.SelectObject(pBrush);

	switch(m_nDrawType)
	{
	case 1:
		dc.SetPixel(point,RGB(255, 0, 0));
		break;
	case 2:
		dc.MoveTo(m_ptOrigin);
		dc.LineTo(point);
		break;
	case 3:
		dc.Rectangle(CRect(m_ptOrigin,point));
		break;
	case 4:
		dc.Ellipse(CRect(m_ptOrigin, point));
		break;
	}
	CView::OnLButtonUp(nFlags, point);
}

这里写图片描述

设置对话框

再增加一个对话框资源,ID为IDD_DLG_SETTING,Caption为Setting,Font为宋体。

设置线宽

添加一个静态文本框,并将Caption设为“线宽”。再添加一个编辑框,ID:IDC_LINE_WIDTH。为此对话框资源创建一个响应的对话框类,类名为CSettingDlg。对编辑框右键,ClassWizard,为它添加一个成员变量:m_nLineWidth,类型为UINT。为绘图菜单下再增加一个菜单项为“设置”,ID为IDM_SETTING。为此菜单项添加一个命令响应,选择视类做出响应。为CGraphicView类添加一个私有成员变量:m_nLineWidth,类型:UINT,并在CGraphicView类的构造函数初始化为0。

void CGraphicView::OnSetting() 
{
	CSettingDlg dlg;
	dlg.m_nLineWidth = m_nLineWidth; //将保存的用户先前设置的线宽再传回给该设置对话框
	if(IDOK == dlg.DoModal())//点击OK才保持线宽值
	{
		m_nLineWidth = dlg.m_nLineWidth;
	}
}

在源文件前部添加:

Include “SettingDlg.h”

修改OnLButtonUp函数:

void CGraphicView::OnLButtonUp(UINT nFlags, CPoint point) 
{
	CClientDC dc(this);
	//为边框设定颜色(m_nLineWidth定义线宽)
	CPen pen(PS_SOLID, m_nLineWidth, RGB(255, 0, 0));
……
}

设置线型

为对话框资源添加一个组框,Caption设为线型。ID为IDC_LINE_STYLE。在组框内放三个单选按钮,ID不变,名称分别为:实线、虚线、点线(不要改变顺序)。在第一个单选按钮上右键,属性勾上Group,使三个按钮成为一组。再为CGraphicView类添加一个Int类型的私有成员变量m_nLineStyle,在构造函数中初始化为0。
  由于WINGDI.h定义了一些符号常量,(可以在PS_SOLID右键,Go To Definition Of PS_SOLID),刚好PS_SOLID(实线)值本身就是0;PS_DASH(虚线)是1;PS_DOT(点线)是2。所以此处的排列是故意为之。
这里写图片描述
这里写图片描述

注意:若要画出虚线和点线,线宽只能为0或1。

颜色对话框

在绘图下增加一个子菜单,ID为IDM_COLOR,Caption为颜色。为其在视类增加一个命令响应,代码:

void CGraphicView::OnColor() 
{
	CColorDialog dlg;
	dlg.m_cc.Flags |= CC_RGBINIT;
	dlg.m_cc.rgbResult = m_clr;
	if (IDOK == dlg.DoModal())
	{
		m_clr = dlg.m_cc.rgbResult;
		//dlg.m_cc.Flags |= CC_RGBINIT | CC_FULLOPEN;//让颜色对话框完全展开
	}
}

为CGraphicView类再增加一个COLORREF类型的私有成员变量:m_clr,并在构造函数中初始化为红色:

m_clr = RGB(255, 0, 0);

  修改该函数两处位置:
```C++
void CGraphicView::OnLButtonUp(UINT nFlags, CPoint point) 
{
	CClientDC dc(this);
	//为边框设定颜色(m_nLineStyle定义线型,m_nLineWidth定义线宽,m_clr定义颜色)
	CPen pen(m_nLineStyle, m_nLineWidth, m_clr);
	dc.SelectObject(&pen);
	//能看到图形内部内容(透明)
	CBrush *pBrush = CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));
	dc.SelectObject(pBrush);

	switch(m_nDrawType)
	{
	case 1:
		dc.SetPixel(point,m_clr);
		break;
	case 2:
		dc.MoveTo(m_ptOrigin);
		dc.LineTo(point);
		break;
	case 3:
		dc.Rectangle(CRect(m_ptOrigin,point));
		break;
	case 4:
		dc.Ellipse(CRect(m_ptOrigin, point));
		break;
	}
	CView::OnLButtonUp(nFlags, point);
}

这里写图片描述

注意://dlg.m_cc.Flags |= CC_RGBINIT | CC_FULLOPEN;//让颜色对话框完全展开
这句我没能实现展开效果。

字体对话框

增加一个菜单,ID为IDM_FONT,Caption为字体。在视类增加命令响应,代码:

void CGraphicView::OnFont() 
{
	CFontDialog dlg;
	if (IDOK == dlg.DoModal())
	{
		if (m_font.m_hObject) //m_font对象是否已经与某字体资源相关联
			m_font.DeleteObject();
		m_font.CreateFontIndirect(dlg.m_cf.lpLogFont);
		m_strFontName = dlg.m_cf.lpLogFont->lfFaceName;
	}
	
}

void CGraphicView::OnDraw(CDC* pDC)
{
	CGraphicDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	// TODO: add draw code for native data here
	CFont *pOldFont = pDC->SelectObject(&m_font);
	pDC->TextOut(0, 0, m_strFontName);
	pDC->SelectObject(pOldFont);
}

示例对话框

在对话框中增加一个组框,Caption:示例,ID:IDC_SAMPLE。为CSettingDlg类添加编辑框控件的EN_CHANCE响应函数,对三个单选按钮都选择BN_CLICKED消息。

void CSettingDlg::OnRadio1() 
{
	// TODO: Add your control notification handler code here
	Invalidate();	
}

void CSettingDlg::OnRadio2() 
{
	// TODO: Add your control notification handler code here
	Invalidate();	
}

void CSettingDlg::OnRadio3() 
{
	// TODO: Add your control notification handler code here
	Invalidate();	
}
void CSettingDlg::OnPaint() 
{
	CPaintDC dc(this); // device context for painting
	
	// TODO: Add your message handler code here
	UpdateData();
	CPen pen(m_nLineStyle, m_nLineWidth, m_clr);
	dc.SelectObject(&pen);

	CRect rect;
	GetDlgItem(IDC_SAMPLE)->GetWindowRect(&rect);
	ScreenToClient(&rect);

	dc.MoveTo(rect.left+20, rect.top+rect.Height()/2);
	dc.LineTo(rect.right-20, rect.top+rect.Height()/2);
	// Do not call CDialog::OnPaint() for painting messages
}

这里写图片描述

现在可以实时修改了。

10.6 改变对话框和控件的背景及文本颜色

改变整个对话框及其子控件的背景色

为CSettingDlg类添加WM_CTLCOLOR消息,并定义一个CBrush类型的私有成员变量:m_brush,并在构造函数中初始化一个蓝色画刷:

m_brush.CreateSolidBrush (RGB(0, 0, 255));
HBRUSH CSettingDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) 
{
	HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
	
	// TODO: Change any attributes of the DC here
	
	// TODO: Return a different brush if the default is not desired
	//return hbr;
	return m_brush;
}

这里写图片描述

仅改变某个子控件的背景及文本颜色

图形的保存和重绘

坐标空间和转换

坐标空间

Win32应用程序编程接口(API)使用四种坐标空间:世界坐标系空间、页面空间、设备空间和物理设备空间。Win32 API把世界坐标系空间和页面空间称为逻辑空间。

转换

转换是把对象从一个坐标空间复制到另一个坐标空间时改变(或转变)这一对象的大小、方位和形态。

图形的保存和重绘

HBRUSH CSettingDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) 
{
	HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
	
	// TODO: Change any attributes of the DC here
	
	// TODO: Return a different brush if the default is not desired
	//return hbr;
	if (pWnd -> GetDlgCtrlID() == IDC_LINE_STYLE)
	{
		pDC->SetTextColor(RGB(255, 0, 0));
		return m_brush;
	}
	return hbr;
}

这里写图片描述

上述程序再加一行:

pDC->SetBkMode(TRANSPARENT);

这里写图片描述

HBRUSH CSettingDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) 
{
	HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
	
	// TODO: Change any attributes of the DC here
	
	// TODO: Return a different brush if the default is not desired
	//return hbr;
	if (pWnd -> GetDlgCtrlID() == IDC_LINE_STYLE)
	{
		pDC->SetTextColor(RGB(255, 0, 0));
		pDC->SetBkMode(TRANSPARENT);
		return m_brush;
	}
	if (pWnd->GetDlgCtrlID() == IDC_LINE_WIDTH)
	{
		pDC->SetTextColor(RGB(255, 0, 0));
		//pDC->SetBkMode(TRANSPARENT);
		pDC->SetBkColor(RGB(0, 0, 255));
		return m_brush;
	}
	return hbr;
}

这里写图片描述

改变控件上的文本字体

为对话框增加一个静态文本控件,ID:IDC_TEXT,Caption:程序员,为CSettingDlg类增加一个CFont类型的私有成员变量:m_font,在构造函数中添加

	m_font.CreatePointFont(200, "华文行楷");

在OnCtlColor函数中添加:

	if (pWnd->GetDlgCtrlID() == IDC_TEXT)
	{
		pDC->SelectObject(&m_font);
	}

这里写图片描述

改变按钮控件的背景色及文本颜色

在CSettingDlg类OnCtlColor函数中添加:

	if (pWnd->GetDlgCtrlID() == IDOK)
	{
		pDC->SetTextColor(RGB(255, 0, 0));
		return m_brush;
	}
	return hbr;
}

点Insert-New Class,选择MFC Class,新增类名:CTestBtn,基类CButton。
  为此类添加DrawItem虚函数重写。

void CTestBtn::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) 
{
	// TODO: Add your code to draw the specified item
	UINT uStyle = DFCS_BUTTONPUSH;

	ASSERT(lpDrawItemStruct->CtlType == ODT_BUTTON);

	if (lpDrawItemStruct->itemState & ODS_SELECTED)
		uStyle |= DFCS_PUSHED;

	::DrawFrameControl(lpDrawItemStruct->hDC, &lpDrawItemStruct->rcItem, DFC_BUTTON, uStyle);

	CString strText;
	GetWindowText(strText);

	COLORREF crOldColor = ::SetTextColor(lpDrawItemStruct->hDC, RGB(255, 0, 0));
	::DrawText(lpDrawItemStruct->hDC, strText, strText.GetLength(),
		&lpDrawItemStruct->rcItem, DT_SINGLELINE | DT_VCENTER | DT_CENTER);
	::SetTextColor(lpDrawItemStruct->hDC, crOldColor);
	
}

然而,此时我返回双击OK键显示“Cannot add new member”……

按理,接下来应该是:
  利用ClassWizard打开Add Member Variable对话框,为OK按钮关联一个成员变量,名称为m_btnTest,类型CTestBtn。在SettingDlg.h文件前部添加#include “TestBtn.h”。对OK右键属性,打开Styles,选中Owner draw选项。此时OK文字变红色。

位图的显示

定制应用程序外观

修改应用程序窗口的外观

在窗口创建之前修改

创建前,打开CMainFrame类的PreCreateWindow成员函数,修改CREATETRUCT结构体中的cx和cy成员。

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
	if( !CFrameWnd::PreCreateWindow(cs) )
		return FALSE;
	// TODO: Modify the Window class or styles here by modifying
	//  the CREATESTRUCT cs
	cs.cx = 300;
	cs.cy = 200;
	return TRUE;
}

创建运行,可看到初始大小为300x200的应用程序窗口。

修改窗口标题:在上述 return TRUE; 前添加:

cs.style &= ~FWS_ADDTOTITLE;
cs.lpszName = "http://www.sunxin.org";

在窗口创建之后修改

注释掉之前添加的代码。在OnCreate函数中添加:

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	…

	// TODO: Delete these three lines if you don't want the toolbar to
	//  be dockable
	m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
	EnableDocking(CBRS_ALIGN_ANY);
	DockControlBar(&m_wndToolBar);

	SetWindowLong(m_hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);


	return 0;
}

创建运行后可看到文档标题去掉了。

去掉窗口最大化框类型:
  将上述SetWindowLong函数替换为

	SetWindowLong(m_hWnd, GWL_STYLE, GetWindowLong(m_hWnd, GWL_STYLE) & ~WS_MAXIMIZEBOX);

创建运行发现最大化框变灰,不能放大窗口了。

修改窗口的光标、图标和背景

在窗口创建之前修改

网络编程

计算机网络基本知识

ISO/OSI七层参考模型
应用层——处理网络应用
Telnet、FTP、HTTP、DNS、SMTP、POP3

表示层——数据表示
TCP、UDP

会话层——主机间通信
传输层——端到端的连接
网络层——寻址和最短路径
IP、ICMP、IGMP

数据链路层——介质访问(接入)
物理层——二进制传输

基于TCP的网络应用程序的编写

服务器端程序

关闭先前的工作区,新建一个工程,选择Win32 Console Application类型,名为TCPSrv。选择An empty project选项,创建一个空工程。再新建一个C++源文件:TcpSrv.cpp。

#include <Winsock2.h>
#include <stdio.h>

void main()
{
	//加载套接字库
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;

	wVersionRequested = MAKEWORD(1, 1);

	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0){
		return;
	}

	if (LOBYTE(wsaData.wVersion) != 1 ||
		HIBYTE(wsaData.wVersion) != 1){
		WSACleanup();
		return;
	}
	//创建用于监听的套接字
	SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);

	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(7000);

	//绑定套接字
	bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
	//将套接字设为监听模式,准备接收客户请求
	listen(sockSrv, 5);

	SOCKADDR_IN addrClient;
	int len = sizeof(SOCKADDR);

	while (1)
	{
		//等待客户请求到来
		SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrClient, &len);
		char sendBuf[100];
		sprintf(sendBuf, "Welcome %s to http://www.sunxin.org", inet_ntoa(addrClient.sin_addr));
		//发送数据
		send(sockConn, sendBuf, strlen(sendBuf)+1, 0);
		char recvBuf[100];
		//接收数据
		recv(sockConn, recvBuf, 100, 0);
		//打印接收的数据
		printf("%s\n", recvBuf);
		//关闭套接字
		closesocket(sockConn);
	}
}

Project-Setting-Link,在Object/library modules编辑框中添加ws2_32.lib文件,注意输入的库文件与前面的库文件之间一定 要有一个空格。
这里写图片描述

客户端程序

在工作区名称上单击鼠标右键,选择Add New Project to Workspace,再创建一个Win32 Console Application类型的应用程序,创建一个空工程。为此增加一个C++源文件:TcpClient.cpp。

#include <Winsock2.h>
#include <stdio.h>

void main()
{
	//加载套接字库
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;

	wVersionRequested = MAKEWORD(1, 1);

	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0){
		return;
	}

	if (LOBYTE(wsaData.wVersion) != 1 || 
		HIBYTE(wsaData.wVersion) != 1){
		WSACleanup();
		return;
	}
	//创建套接字
	SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);

	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(7000);

	//向服务器发出连接请求
	connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));

	//接收数据
	char recvBuf[100];
	recv(sockClient, recvBuf, 100, 0);
	printf("%s\n",recvBuf);
	//发送数据
	send(sockClient, "This is lisi", strlen("This is lisi")+1, 0);
	//关闭套接字
	closesocket(sockClient);
	WSACleanup();
}

链接库文件:ws2_32.lib。
  创建运行,首先运行服务器程序,然后再运行客户端程序。

这里写图片描述

注意:当没有报错,服务器端运行结果为“烫烫……烫”(N个烫)时,尝试换一个端口号,有可能你设置的端口号被其它的应用程序占用了。

基于UDP的网络应用程序的编写

服务器端程序

关闭先前的工作区,新建一个工程,选择Win32 Console Application类型,名为UdpSrv。选择An empty project选项,创建一个空工程。再新建一个C++源文件:UdpSrv.cpp。

#include <Winsock2.h>
#include <stdio.h>

void main()
{
	//加载套接字库
	WORD wVersionRequired;
	WSADATA wsaData;
	int err;

	wVersionRequired = MAKEWORD(1, 1);

	err = WSAStartup(wVersionRequired, &wsaData);
	if (err != 0)
	{
		return;
	}

	if (LOBYTE(wsaData.wVersion) != 1 ||
		HIBYTE(wsaData.wVersion) !=1)
	{
		WSACleanup();
		return;
	}
	//创建套接字
	SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0);
	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(7000);
	//绑定套接字
	bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));

	//等待并接收数据
	SOCKADDR_IN addrClient;
	int len = sizeof(SOCKADDR);
	char recvBuf[100];
	recvfrom(sockSrv, recvBuf, 100, 0, (SOCKADDR*)&addrClient, &len);
	printf("%s\n",recvBuf);
	//关闭套接字
	closesocket(sockSrv);
	WSACleanup();

}

在工程设置对话框的链接选项卡下添加库文件:Ws2_32.lib的链接。

客户端程序

在同一个UdpSrv工作区中创建客户端应用程序。创建一个空的Win32 Console Application类型的工程,名为:UdpClient。为该工程添加一个C++源文件:UdpClient.cpp。

#include <Winsock2.h>
#include <stdio.h>

void main()
{
	//加载套接字库
	WORD wVersionRequired;
	WSADATA wsaData;
	int err;

	wVersionRequired = MAKEWORD(1, 1);

	err = WSAStartup(wVersionRequired, &wsaData);
	if (err != 0)
	{
		return;
	}

	if (LOBYTE(wsaData.wVersion) != 1 ||
		HIBYTE(wsaData.wVersion) !=1)
	{
		WSACleanup();
		return;
	}

	//创建套接字
	SOCKET sockClient = socket(AF_INET, SOCK_DGRAM, 0);
	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(7000);
	//发送数据
	sendto(sockClient, "Hello", strlen("Hello")+1, 0, 
		(SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
	//关闭套接字
	closesocket(sockClient);
	WSACleanup();
}

链接库文件:ws2_32.lib。

创建运行。服务器端程序应先启动,然后启动客户端程序。

这里写图片描述

基于TCP和基于UDP的网络应用程序在发送和接收数据时使用的函数是不一样的:前者使用send和recv,后者使用sendto和recvfrom。

基于UDP的简单聊天程序

在新工作区新建一个空的Win32 Console Application类型的应用程序,名为NetSrv。为该工程添加一个C++源文件:NetSrv.cpp。接着为该工程添加对WinSock库的链接,即在工程设置对话框的Link选项卡上添加ws2_32.lib文件的链接。

#include <Winsock2.h>
#include <stdio.h>
 void main()
 {
	 //加载套接字库
	 WORD wVersionRequested;
	 WSADATA wsaData;
	 int err;

	 wVersionRequested = MAKEWORD(1, 1);

	 err = WSAStartup(wVersionRequested, &wsaData);
	 if (err != 0)
	 {
		 return;
	 }

		if (LOBYTE(wsaData.wVersion) != 1 ||
		HIBYTE(wsaData.wVersion) !=1)
	{
		WSACleanup();
		return;
	}

	 //创建套接字
	 SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0);

	 SOCKADDR_IN addrSrv;
	 addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	 addrSrv.sin_family = AF_INET;
	 addrSrv.sin_port = htons(7000);

	 //绑定套接字
	 bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));

	 char recvBuf[100];
	 char sendBuf[100];
	 char tempBuf[200];

	 SOCKADDR_IN addrClient;
	 int len = sizeof(SOCKADDR);
	 while(1)
	 {
		 //等待并接收数据
		 recvfrom(sockSrv, recvBuf, 100, 0, (SOCKADDR*)&addrClient, &len);
		 if ('q' == recvBuf[0])
		 {
			 sendto(sockSrv, "q", strlen("q")+1, 0, (SOCKADDR*)&addrClient, len);
			 printf("Chat end!\n");
			 break;
		 }
		 sprintf(tempBuf, "%s say : %s", inet_ntoa(addrClient.sin_addr), recvBuf);
		 printf("%s\n", tempBuf);
		 //发送数据
		 printf("Please input data:\n");
		 gets(sendBuf);
		 sendto(sockSrv, sendBuf, strlen(sendBuf)+1, 0, (SOCKADDR*)&addrClient, len);
	}
	 //关闭套接字
	 closesocket(sockSrv);
	 WSACleanup();
 }

客户端程序

向已有工作区增加一个空的Win32 Console Application类型的工程:NetClient。为此添加一个C++源文件:NetClient.cpp。为该工程添加ws2_32.lib文件的链接。

#include <Winsock2.h>
#include <stdio.h>
 void main()
 {
	 //加载套接字库
	 WORD wVersionRequested;
	 WSADATA wsaData;
	 int err;

	 wVersionRequested = MAKEWORD(1, 1);

	 err = WSAStartup(wVersionRequested, &wsaData);
	 if (err != 0)
	 {
		 return;
	 }

		if (LOBYTE(wsaData.wVersion) != 1 ||
		HIBYTE(wsaData.wVersion) !=1)
	{
		WSACleanup();
		return;
	}

	 //创建套接字
	 SOCKET sockClient = socket(AF_INET, SOCK_DGRAM, 0);

	 SOCKADDR_IN addrSrv;
	 addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	 addrSrv.sin_family = AF_INET;
	 addrSrv.sin_port = htons(7000);

	 char recvBuf[100];
	 char sendBuf[100];
	 char tempBuf[200];

	 int len = sizeof(SOCKADDR);

	 while(1)
	 {
		 //发送数据
		 printf("Please input data:\n");
		 gets(sendBuf);
		 sendto(sockClient, sendBuf, strlen(sendBuf)+1, 0, (SOCKADDR*)&addrSrv, len);
		 //等待并接收数据

		 recvfrom(sockClient, recvBuf, 100, 0, (SOCKADDR*)&addrSrv, &len);
		 if('q' == recvBuf[0])
		 {
			 sendto(sockClient, "q", strlen("q")+1, 0, (SOCKADDR*)&addrSrv, len);
			 printf("Chat end!\n");
			 break;
		 }
		 sprintf(tempBuf, "%s say : %s", inet_ntoa(addrSrv.sin_addr), recvBuf);
		 printf("%s\n", tempBuf);
	 }
	 //关闭套接字
	 closesocket(sockClient);
	 WSACleanup();
 }

这里写图片描述

多线程

进程

程序和进程

简单多线程示例

#include <windows.h>
#include <iostream.h>

    DWORD WINAPI Fun1Proc(
	LPVOID lpParameter  //thread data					 
	);
	void main()
	{
		HANDLE hThread1;
		hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
		CloseHandle(hThread1);
		cout<<"main thread is running"<<endl;
		Sleep(10);//让主线程暂停运行,进入分线程
	}

	//线程1的入口函数
	DWORD WINAPI Fun1Proc(
		LPVOID lpParameter  //thread data
		)
	{
		cout<<"thread1 is running"<<endl;
		return 0;
	}

这里写图片描述

交替运行:

#include <windows.h>
#include <iostream.h>

    DWORD WINAPI Fun1Proc(
	LPVOID lpParameter  //thread data					 
	);
	
	int index = 0;

	void main()
	{
		HANDLE hThread1;
		hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
		CloseHandle(hThread1);
		
		while (index ++< 100)
		{
			cout<<"main thread is running"<<endl;
		}
			//Sleep(10);//让主线程暂停运行,进入分线程
	}

	//线程1的入口函数

	DWORD WINAPI Fun1Proc(
			
		LPVOID lpParameter  //thread data
		)

	{
		while (index++< 100)
		cout<<"thread1 is running"<<endl;
		return 0;
	}

这里写图片描述

线程同步

火车站售票系统模拟程序

由主线程创建的两个线程(1和2)负责销售火车票。

#include <windows.h>
#include <iostream.h>

    DWORD WINAPI Fun1Proc(
	LPVOID lpParameter  //thread data					 
	);
	
	 DWORD WINAPI Fun2Proc(
	LPVOID lpParameter  //thread data					 
	);

	int index = 0;
	int tickets = 100;
	HANDLE hMutex;

	void main()
	{
		HANDLE hThread1;
		HANDLE hThread2;

		//创建互斥对象
		hMutex = CreateMutex(NULL, FALSE, NULL);

		//创建线程
		hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
		hThread2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);
		CloseHandle(hThread1);
		CloseHandle(hThread2);
		Sleep(4000);
	}
	
	

	

	//线程1的入口函数
	DWORD WINAPI Fun1Proc(
			
		LPVOID lpParameter  //thread data
		)

	{
	while (TRUE)
		{
			WaitForSingleObject(hMutex, INFINITE);//实现线程同步
			if (tickets > 0)
			{
				Sleep(1);
				cout<<"thread1 sell ticket:"<<tickets--<<endl;
			}
			else 
				break;
			ReleaseMutex(hMutex);//释放当前线程对互斥对象的所有权
		}
		return 0;

	}
	

		//线程2的入口函数
	DWORD WINAPI Fun2Proc(
			
		LPVOID lpParameter  //thread data
		)


	{
		while (TRUE)
		{
			WaitForSingleObject(hMutex,INFINITE);
			if (tickets > 0)
			{
				Sleep(1);
				cout<<"thread2 sell ticket:"<<tickets--<<endl;
			}
			else 
				break;
			ReleaseMutex(hMutex);
		}
		return 0;
}

这里写图片描述
  这时所销售的票号正常,没有看到销售了号码为0的票。

对互斥对象来说,谁拥有谁释放。

保证应用程序只有一个实例运行

#include <windows.h>
#include <iostream.h>

    DWORD WINAPI Fun1Proc(
	LPVOID lpParameter  //thread data					 
	);
	
	 DWORD WINAPI Fun2Proc(
	LPVOID lpParameter  //thread data					 
	);

	int index = 0;
	int tickets = 100;
	HANDLE hMutex;

	void main()
	{
		HANDLE hThread1;
		HANDLE hThread2;

		//创建互斥对象(注意命名)
		hMutex = CreateMutex(NULL, FALSE, "1");
		if (hMutex)
		{
			if (ERROR_ALREADY_EXISTS == GetLastError())
			{
				cout<<"only one instance can run!"<<endl;
				return;
			}
		}

		//创建线程
		hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
		hThread2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);
		CloseHandle(hThread1);
		CloseHandle(hThread2);
		WaitForSingleObject(hMutex, INFINITE);
		ReleaseMutex(hMutex);
		ReleaseMutex(hMutex);
		Sleep(4000);
	}
	
	//线程1的入口函数
	DWORD WINAPI Fun1Proc(
			
		LPVOID lpParameter  //thread data
		)

	{
	while (TRUE)
		{
			WaitForSingleObject(hMutex, INFINITE);//实现线程同步
			if (tickets > 0)
			{
				Sleep(1);
				cout<<"thread1 sell ticket:"<<tickets--<<endl;
			}
			else 
				break;
			ReleaseMutex(hMutex);//释放当前线程对互斥对象的所有权
		}
		return 0;

	}
	

		//线程2的入口函数
	DWORD WINAPI Fun2Proc(
			
		LPVOID lpParameter  //thread data
		)


	{
		while (TRUE)
		{
			WaitForSingleObject(hMutex,INFINITE);
			if (tickets > 0)
			{
				Sleep(1);
				cout<<"thread2 sell ticket:"<<tickets--<<endl;
			}
			else 
				break;
			ReleaseMutex(hMutex);
		}
		return 0;
}


网络聊天室程序的实现

新建一个基于对话框的工程,名为:Chat。
这里写图片描述
这里写图片描述

加载套接字库

在CChatApp类的InitInstance函数开始位置

BOOL CChatApp::InitInstance()
{
	if (!AfxSocketInit())
	{
		AfxMessageBox("加载套接字库失败!");
		return FALSE;
	}
……
}
  在stdafx.h中,添加头文件`#include <Afxsock.h>`。

### 创建并初始化套接字
```C++
BOOL CChatDlg::InitSocket()
{
	//创建套接字
	m_socket = socket(AF_INET, SOCK_DGRAM, 0);
	if (INVALID_SOCKET == m_socket)
	{
		MessageBox("套接字创建失败!");
		return FALSE;
	}
	SOCKADDR_IN addrSock;
	addrSock.sin_family = AF_INET;
	addrSock.sin_port = htons(7000);
	addrSock.sin_addr.S_un.S_addr = htonl(INADDR_ANY);

	int retval;
	//绑定套接字
	retval = bind(m_socket, (SOCKADDR*)&addrSock, sizeof(SOCKADDR));
	if (SOCKET_ERROR == retval)
	{
		closesocket(m_socket);
		MessageBox("绑定失败!");
		return TRUE;
	}
	return TRUE;

}
BOOL CChatDlg::OnInitDialog()
{
……
	
	// TODO: Add extra initialization here
	InitSocket();

	return TRUE;  // return TRUE  unless you set the focus to a control
}

实现接收端功能

在CChatDlg类中定义:

/
// CChatDlg dialog

struct RECVPARAM
{
	SOCKET sock; //已创建的套接字
	HWND hwnd; //对话框句柄
};

在Chatdlg.h中添加:static DWORD WINAPI RecvProc(LPVOID lpParameter);
在OnInitDialog()中添加:

BOOL CChatDlg::OnInitDialog()
{

……

// TODO: Add extra initialization here
	InitSocket();
	RECVPARAM *pRecvParam = new RECVPARAM;
	pRecvParam->sock = m_socket;
	pRecvParam->hwnd = m_hWnd;
	//创建接收线程
	HANDLE hThread = CreateThread(NULL, 0, RecvProc, (LPVOID)pRecvParam, 0, NULL);
	//关闭该接收程句柄,释放其引用计数
	CloseHandle(hThread);


	return TRUE;  // return TRUE  unless you set the focus to a control
}

  在CChatDlg类中添加:
```C++
DWORD WINAPI CChatDlg::RecvProc(LPVOID lpParameter)
{
	return 0;
}

若要求采用完全面向对象的思想来编程,不能使用全局函数和全局变量了,可以采用静态成员函数和静态成员变量的方法来解决。

DWORD WINAPI CChatDlg::RecvProc(LPVOID lpParameter)
{
	//获取主线程传递的套接字和窗口句柄
	SOCKET sock = ((RECVPARAM*)lpParameter)->sock;
	HWND hwnd = ((RECVPARAM*)lpParameter)->hwnd;
	delete lpParameter;

	SOCKADDR_IN addrFrom;
	int len = sizeof(SOCKADDR);

	char recvBuf[200];
	char tempBuf[300];
	int retval;
	while(TRUE)
	{
		//接收数据
		retval = recvfrom(sock, recvBuf, 200, 0, (SOCKADDR*)&addrFrom, &len);
		if (SOCKET_ERROR == retval)
			break;
		sprintf(tempBuf, "%s 说: %s", inet_ntoa(addrFrom.sin_addr), recvBuf);
		::PostMessage(hwnd, WM_RECVDATA, 0, (LPARAM)tempBuf);
	}
	return 0;
}

在该类添加头文件 #define WM_RECVDATA WM_USER+1

在CChatDlg类头文件中编写该消息响应函数原型的声明:

	// Generated message map functions
	//{{AFX_MSG(CChatDlg)
	virtual BOOL OnInitDialog();
	afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
	afx_msg void OnPaint();
	afx_msg HCURSOR OnQueryDragIcon();
	//}}AFX_MSG
	afx_msg void OnRecvData(WPARAM wParam, LPARAM lParam);
	DECLARE_MESSAGE_MAP()

在CChatDlg类的源文件中添加WM_RECVDATA消息映射。

BEGIN_MESSAGE_MAP(CChatDlg, CDialog)
	//{{AFX_MSG_MAP(CChatDlg)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	//}}AFX_MSG_MAP
	ON_MESSAGE(WM_RECVDATA, OnRecvData)
END_MESSAGE_MAP()

在构造函数中

void CChatDlg::OnRecvData(WPARAM wParam, LPARAM lParam)
{
	//取出接收到的数据
	CString str = (char*)lParam;
	CString strTemp;
	//获得已有数据
	GetDlgItemText(IDC_EDIT_RECV, strTemp);
	str += "\r\n";
	str += strTemp;
	//显示所有接收到的数据
	SetDlgItemText(IDC_EDIT_RECV, str);
}

实现发送端功能

双击发送,添加响应函数。

void CChatDlg::OnBtnSend() 
{
	//获取对方IP
// TODO: Add your control notification handler code here
	DWORD dwIP;
	((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->GetAddress(dwIP);

	SOCKADDR_IN addrTo;
	addrTo.sin_family = AF_INET;
	addrTo.sin_port = htons(7000);
	addrTo.sin_addr.S_un.S_addr = htonl(dwIP);

	CString strSend;
	//获得待发送数据
	GetDlgItemText(IDC_EDIT_SEND, strSend);
	//发送数据
	sendto(m_socket, strSend, strSend.GetLength()+1, 0,
		(SOCKADDR*)&addrTo, sizeof(SOCKADDR));
	//清空发送编辑框中的内容
	SetDlgItemText(IDC_EDIT_SEND, "");
}

为了让编辑框控件接受换行符,必须设置该控件支持多行数据这一属性。
这里写图片描述

将“发送”设置为Default button,还可以选择取消Visible选项。
本例在一个程序中同时实现了接收端和发送端的功能,所以只需在聊天双方各自的机器上安装本程序,在聊天时,通过输入对方主机的IP地址,就可以与对方进行通信了。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 像素格子 设计师:CSDN官方博客 返回首页