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.youkuaiyun.com/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.youkuaiyun.com/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地址,就可以与对方进行通信了。

第1章 MFC概述 1 1.1 MFC是一个编程框架 1 1.1.1 封装 1 1.1.2 继承 2 1.1.3 虚拟函数和动态约束 2 1.1.4 MFC的宏观框架体系 2 1.2 MDI应用程序的构成 3 1.2.1 构成应用程序的对象 3 1.2.2 构成应用程序的对象之间的关系 5 1.2.3 构成应用程序的文件 5 第2章 MFC和Win32 9 2.1 MFC Object和Windows Object的关系 9 2.2 Windows Object 12 2.2.1 Windows的注册 12 2.2.2 MFC窗口类CWnd 15 2.2.3 在MFC下创建一个窗口对象 17 2.2.4 MFC窗口的使用 18 2.2.5 在MFC下窗口的销毁 19 2.3 设备描述表 20 2.3.1 设备描述表概述 20 2.3.2 设备描述表在MFC中的实现 22 2.3.3 MFC设备描述表类的使用 24 2.4 GDI对象 25 第3章 CObject类 28 3.1 CObject的结构 28 3.2 CObject类的特性 30 3.3 实现CObject特性的机制 32 3.3.1 DECLARE_DYNAMIC等宏的定义 32 3.3.2 CruntimeClass类的结构与功能 35 3.3.3 动态类信息、动态创建的原理 38 3.3.4 序列化的机制 39 第4章 消息映射的实现 42 4.1 Windows消息概述 42 4.1.1 消息的分类 42 4.1.2 消息结构和消息处理 42 4.2 消息映射的定义和实现 44 4.2.1 MFC处理的三类消息 44 4.2.2 MFC消息映射的实现方法 45 4.2.3 在声明与实现的内部 46 4.2.3.1 消息映射声明的解释 47 4.2.3.2 消息映射实现的解释 49 4.2.4 消息映射宏的种类 51 4.3 CcmdTarget类 54 4.4 MFC窗口过程 55 4.4.1 MFC窗口过程的指定 56 4.4.2 对Windows消息的接收和处理 58 4.4.2.1 从窗口过程到消息映射 59 4.4.2.2 Windows消息的查找和匹配 60 4.4.2.3 Windows消息处理函数的调用 62 4.4.2.4 消息映射机制完成虚拟函数功能的原理 63 4.4.3 对命令消息的接收和处理 64 4.4.3.1 MFC标准命令消息的发送 64 4.4.3.2 命令消息的派发和消息的多次处理 67 4.4.3.3 一些消息处理类的OnCmdMsg的实现 69 4.4.3.4 一些消息处理类的OnCommand的实现 71 4.4.4 对控制通知消息的接收和处理 72 4.4.4.1 WM_COMMAND控制通知消息的处理 72 4.4.4.2 WM_NOTIFY消息及其处理: 73 4.4.4.3 消息反射 74 4.4.5 对更新命令的接收和处理 77 4.4.5.1 实现方法 77 4.4.5.2 状态更新命令消息 78 4.4.5.3 类CCmdUI 79 4.4.5.4 自动更新用户接口对象状态的机制 80 4.5 消息的预处理 82 4.6 MFC消息映射的回顾 83 第5章 MFC对象的创建 85 5.1 MFC对象的关系 85 5.1.1 创建关系 85 5.1.2 交互作用关系 86 5.2 MFC提供的接口 87 5.2.1 虚拟函数接口 87 5.2.2 消息映射方法和标准命令消息 91 5.3 MFC对象的创建过程 94 5.3.1 应用程序中典型对象的结构 94 5.3.1.1 应用程序类的成员变量 95 5.3.1.2 CDocument的成员变量 97 5.3.1.3 文档模板的属性 97 5.3.2 WinMain入口函数 99 5.3.2.1 WinMain流程 99 5.3.2.2 MFC空闲处理 101 5.3.3 SDI应用程序的对象创建 102 5.3.3.1 文档模板的创建 102 5.3.3.2 文件的创建或者打开 103 5.3.3.3 SDI边框窗口的创建 112 5.3.3.4 视的创建 115 5.3.3.5 窗口初始化 116 5.3.3.6 视的初始化 120 5.3.3.7 激活边框窗口(处理WM_ACTIVE) 121 5.3.3.8 SDI流程的回顾 122 5.3.4 MDI程序的对象创建 123 5.3.4.1 有别于SDI的主窗口加载过程 124 5.3.4.2 MDI子窗口、视、文档的创建 125 5.3.4.3 MDI子窗口的初始化和窗口的激活 127 第6章 应用程序的退出 131 6.1 边框窗口对WM_CLOSE的处理 131 6.2 窗口的销毁过程 135 6.2.1 DestroyWindow 135 6.2.2 处理WM_DESTROY消息 136 6.2.3 处理WM_NCDESTROY消息 136 6.3 SDI窗口、MDI主、子窗口的关闭 137 第7章 MFC的DLL 139 7.1 DLL的背景知识 139 7.2 调用约定 141 7.2.1 MFC的DLL应用程序的类型 142 7.3 DLL的几点说明 143 7.4 输出函数的方法 145 第8章 MFC的进程和线程 148 8.1 Win32的进程和线程概念 148 8.2 Win32的进程处理简介 148 8.2.1 进程的创建 148 8.2.2 进程的终止 149 8.3 Win32的线程 150 8.3.1 线程的创建 150 8.3.2 线程的终止 150 8.3.3 线程局部存储 151 8.4 线程同步 152 8.4.1 同步对象 152 8.4.2 等待函数 153 8.5 MFC的线程处理 154 8.5.1 创建用户界面线程 155 8.5.2 创建工作者线程 155 8.5.3 AfxBeginThread 155 8.5.4 CreateThread和_AfxThreadEntry 157 8.5.5 线程的结束 160 8.5.6 实现线程的消息循环 161 第9章 MFC的状态 163 9.1 模块状态 163 9.2 模块、进程和线程状态的数据结构 164 9.2.1 层次关系 164 9.2.2 CNoTrackObject类 166 9.2.3 AFX_MODULE_STATE类 166 9.2.4 _AFX_BASE_MODULE_STATE 169 9.2.5 _AFX_THREAD_STATE 169 9.2.6 AFX_MODULE_THREAD_STATE 171 9.3 线程局部存储机制和状态的实现 172 9.3.1 CThreadSlotData和_afxThreadData 173 9.3.1.1 CThreadSlotData的定义 173 9.3.1.2 CThreadSlotData的一些数据成员 174 9.3.1.3 _afxThreadData 175 9.3.2 线程状态_afxThreadState 176 9.3.3 进程模块状态afxBaseModuleState 178 9.3.4 状态对象的创建 180 9.3.4.1 状态对象的创建过程 180 9.3.4.2 创建过程所涉及的几个重要函数的算法 183 9.4 管理状态 184 9.4.1 模块状态切换 184 9.4.2 扩展DLL的模块状态 186 9.4.2.1 _AFX_EXTENSION_MODULE 187 9.4.2.2 扩展DLL的初始化函数 188 9.4.3 核心MFC DLL 190 9.4.4 动态链接的规则DLL的模块状态的实现 190 9.5 状态信息的作用 192 9.5.1.1 模块信息的保存和管理 192 9.5.2 MFC资源、运行类信息的查找 193 9.5.3 模块信息的显示 194 9.5.4 模块-线程状态的作用 196 9.5.4.1 只能访问本线程MFC对象的原因 196 9.5.4.2 实现MFC对象和Windows对象之间的映射 196 9.5.4.3 临时对象的处理 199 9.6 状态对象的删除和销毁 199 第10章 内存分配方式和调试机制 202 10.1 M内存分配 202 10.1.1 内存分配函数 202 10.1.2 C++的new 和 delete操作符 204 10.2 调试手段 204 10.2.1 C运行库提供和支持的调试功能 204 10.2.2 MFC提供的调试手段 205 10.2.3 内存诊断 208 第11章 MFC下的文件类 210 11.1 文件操作的方法 210 11.2 MFC的文件类 210 11.2.1 CFile的结构 211 11.2.1.1 CFile定义的枚举类型 211 11.2.1.2 CFile的其他一些成员变量 212 11.2.1.3 CFile的成员函数 212 11.2.2 CFile的部分实现 213 11.2.3 CFile的派生类 215 第12章 对话框和对话框类CDialog 217 12.1 模式和无模式对话框 217 12.1.1 模式对话框 217 12.1.2 无模式对话框 218 12.2 对话框的MFC实现 219 12.2.1 CDialog的设计和实现 219 12.2.1.1 CDialog的成员变量 219 12.2.1.2 CDialog的成员函数: 219 12.2.2 MFC模式对话框的实现 220 12.2.2.1 MFC对话框过程 220 12.2.2.2 模式对话框窗口过程 221 12.2.2.3 使用原对话框窗口过程作消息的缺省处理 225 12.2.2.4 Dialog命令消息和控制通知消息的处理 226 12.2.2.5 消息预处理和Dialog消息 228 12.2.2.6 模式对话框的消息循环 229 12.2.3 对话框的数据交换 233 12.2.3.1 数据交换的方法 233 12.2.3.2 CDataExchange 234 12.2.3.3 数据交换和验证函数 236 12.2.3.4 UpdateData函数 239 12.3 无模式对话框 240 12.3.1 CScrollView 240 12.3.2 CFormView 242 12.3.2.1 CFormView的创建 243 12.3.2.2 CFormView的消息预处理 245 12.3.2.3 CFormView的输入焦点 247 第13章 MFC工具条和状态栏 248 13.1 Windows控制窗口 248 13.2 MFC的工具条和状态栏类 249 13.2.1 控制窗口的创建 251 13.2.1.1 PreCreateWindow 251 13.2.1.2 控制条的窗口创建 253 13.2.2 控制条的销毁 259 13.2.3 处理控制条的位置 259 13.2.3.1 计算控制条位置的过程和算法 259 13.2.3.2 CFrameWnd的虚拟函数RecalcLayout 260 13.2.3.3 CWnd的成员函数RepositionBars 262 13.2.4 工具条、状态栏和边框窗口的接口 265 13.2.4.1 应用程序在状态栏中显示信息 265 13.2.4.2 状态栏显示菜单项的提示信息 268 13.2.4.3 控制条的消息分发处理 270 13.2.4.4 Tooltip 275 13.2.4.5 禁止和允许 279 13.2.4.6 显示或者隐藏工具栏和状态栏 285 13.2.5 泊位和漂浮 286 第14章 SOCKET类的设计和实现 288 14.1 WinSock基本知识 288 14.1.1 WinSock API 288 14.1.2 Socket的使用 290 14.2 MFC对WinSockt API的封装 291 14.2.1 CAsyncSocket 291 14.2.2 socket对象的创建和捆绑 292 14.2.3 异步网络事件的处理 296 14.3 CSocket 297 14.4 CSocketFile 299
评论 37
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值