VC++入门实验及部分知识点讲解

我是在大二上学期开始接触到VC的开发的。当时,首先接触的是使用MFC来开发应用程序,但当时的知识有限,对MFC框架了解甚少。为了弥补这些知识上的缺陷,首先从Win32的应用程序学起的,中间参考着Windows API 做过些很小型的开发,随后使用了一本《学通VC++的24堂课》做了一些书本上给出的小示例的叠加。但是,代码基本上都没有保留下来。(在一次的硬盘损坏中丢失了)

现在大学里正式开这门课程,我决定将老师在课堂上给出的VC的小示例积累下来,也方便以后的学习。

1.设计界面

2.关键代码实现

m_ListId:整型变量,用于记录哪一条记录被选中。

为控件关联相应的变量;

m_Name,m_Number,m_Score     (CString类型)

m_Average  (double 类型)

m_ListCon (Control 类型)

为ListBox添加事件监听函数(响应SELCHANGE消息)

void CDiStudentDlg::OnSelchangeList1() 
{
	// TODO: Add your control notification handler code here
	m_ListId = m_ListCon.GetCurSel();
	
}

添加控件按钮相应函数:

void CDiStudentDlg::OnAdd() 
{
	// TODO: Add your control notification handler code here
	UpdateData(TRUE);
	CString str ;
	str = "姓名:"+this->m_Name+",学号:"+this->m_Number+",成绩:"+this->m_Score;

	this->m_ListCon.AddString(str);
	
}

删除控件的响应函数:
void CDiStudentDlg::OnDel() 
{
	// TODO: Add your control notification handler code here
	this ->m_ListCon.DeleteString(m_ListId);	
}

查找成绩函数模块:(因为在显示成绩和计算平均成绩中,均有用到查找成绩函数,因此写成模块提高代码的复用率)
CString CDiStudentDlg::FindGrade(int m_ListId)
{
//	m_ListId = this ->m_ListId;
	CString se_str;
	this ->m_ListCon.GetText(m_ListId,se_str);
	CString str = "成绩:" ;
	CString result ;
	result = se_str.Right(se_str.GetLength()-se_str.Find(str,0)-str.GetLength());

	return result ;

}

在VC中的MFC,添加类的成员函数时,可以采用手工的方式,在头文件中添加函数的声明,在源文件中,添加函数的具体实现代码。也可以采用开发工具提供的机制,在ClassView中,选中一个类,然后右键单击选择添加成员函数,成员变量的添加方式也可以采用这种方式。
显示成绩模块:
void CDiStudentDlg::OnShow() 
{
	// TODO: Add your control notification handler code here

	AfxMessageBox(FindGrade(m_ListId));
	
}

计算平均成绩模块:
void CDiStudentDlg::OnAve() 
{
	// TODO: Add your control notification handler code here
	int count = 0;
	double score = 0.0 ;
	count = this->m_ListCon.GetCount();
	for(int i = 0 ; i < count ; i ++)
	{
		score += atof(FindGrade(i));
	}
	m_Average = score/count ;
	UpdateData(false);
	
}


3.总结:关于UpdateData()函数的使用:

当使用了ClassWizard建立了控件和变量之间的联系后:当修改了变量的值,而希望对话框控件更新显示,就应该在修改变量后调用UpdateData(FALSE);如果你希望知道用户在对话框中到底输入了什么,就应该在访问变量前调用UpdateData(TRUE)。

  UpdateData,顾名思义,是用来刷新数据的。
  UpdateData(TRUE) -- 刷新控件的值到对应的变量
  UpdateData(FALSE) -- 拷贝变量值到控件显示

关于CString类的常用的函数的说明:

http://blog.csdn.net/yfwei/article/details/1771658

可以参考这篇文章的说明。

4.VC的一些基础知识

inline内联函数是以空间换时间

ADO(ActiveX Data Object ActiveX数据对象),代表了提供数据访问的新途径,他通过把数据绑定在ActiveX控件与ADODC结合起来使用。

Delete Function  VC都从头文件中删除子函数声明,并在源代码中注释掉函数体。若要手工删除,必须删除多个文件。

选择一个对话框类,Goto Dialog Editor 选项。不一定要在视图中去做。

F9设置或取消断点。F5调试方式执行程序。

MSDEV。在运行中输入MSDEV可以打开VC6.0的编译器。

InstallShield用于制作安装程序。

在VC中如果WorkSpace 和Output消失的话,可以点击标题栏上的相应的图标,也可以在View菜单中选择WorkSpace和Output

关键字都是以两个下划线开始的,但这些保留字在普通程序设计中并不常用。

__asm   C++语言源文件中插入汇编语言(一种语言中可以嵌入另一种编程语言)

__cdecl  使用C命名和调用约定(在函数调用中,参数被从右向左压入堆栈)

预编译头的概念

把工程的一部分代码,预先编译好,放在一个文件中,通常是已.pch为扩展名,这个文件就是预编译头文件。避免了每次都要重新处理这些头文件,提高了效率。

预编译头文件默认是StdAfx.h 对应的源文件是StdAfx.cpp (在这个文件里只有如下一句代码  #include "Stdafx.h")

如果没有包含指定产生pch文件的.h文件(默认是stdafx.h),就会返回Unexpected file end

VC中的宽字符型w_char

cerr不使用缓冲,以便调试输出的信息能立即显示出来。


MFC中类的定义在头文件中,类的构造函数,析构函数,其他成员函数的定义在源文件中,所以包含头文件是在类的头文件中包含,定义类的新的成员变量也在头文件中操作。全局变量在主类中的源文件中定义的。


(实验二)


此次实验中,要使用到的对话框的控件包括:树形控件,Group Box ,List Box  和List Control 等控件的使用。

界面设计如下图所示:

其中,使用到的控件为Tree Control  , Group Box  (Static, Edit, Combo Box), Group Box(List Control ) , List Box 。

为控件关联相应的控制变量或值变量

控制变量:

Tree Control  ---》m_TreeCon

 List Box  --》m_Log

List Control --》m_Inquriy

Combo Box --》m_Grade

值变量:

m_Name    m_ID   m_Sub1   m_Sub2   m_Sub3

Step 1:

在OnInitDialog()函数中,给树形结构和List Control 进行初始化。 代码示例如下:

	// TODO: Add extra initialization here

	this ->m_TreeCon.ModifyStyle(0,TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT);

	root = this ->m_TreeCon.InsertItem("年级");

	root1 = this ->m_TreeCon.InsertItem("一年级",root) ;
	root2 = this ->m_TreeCon.InsertItem ("二年级",root);
	root3 = this ->m_TreeCon.InsertItem("三年级",root) ;
	root4 = this->m_TreeCon.InsertItem("四年级",root) ;

	this ->m_Grade.SetCurSel(0) ;

	this->m_Inquiry.SetExtendedStyle(LVS_EX_GRIDLINES|LVS_EX_FULLROWSELECT|LVS_EX_ONECLICKACTIVATE) ;

	m_Inquiry.InsertColumn(0,"姓名",LVCFMT_LEFT,40);
	m_Inquiry.InsertColumn(1,"学号",LVCFMT_LEFT,80);
	m_Inquiry.InsertColumn(2,"年级",LVCFMT_LEFT,80);
	m_Inquiry.InsertColumn(3,"Sub1",LVCFMT_LEFT,80);
	m_Inquiry.InsertColumn(4,"Sub2",LVCFMT_LEFT,80);
	m_Inquiry.InsertColumn(5,"Sub3",LVCFMT_LEFT,80);
	

Step 2: Add按钮的响应函数:

将数据加入到树形控件中的相应的位置。

将数据保存到Student类中

显示日志内容

	UpdateData (true);

	CString grade ;

	switch(this->m_Grade.GetCurSel())
	{
	case 0:
		this->m_TreeCon.InsertItem(this->m_Name,root1);
		this->m_TreeCon.Expand(root,TVE_EXPAND);
		this->m_TreeCon.Expand(root1,TVE_EXPAND);
		grade = "一年级" ;
		break ;
	case 1:
		
		this->m_TreeCon.InsertItem(this->m_Name,root2);
		this->m_TreeCon.Expand(root,TVE_EXPAND);
		this->m_TreeCon.Expand(root2,TVE_EXPAND);
		grade = "二年级" ;
		break ;
	case 2:
		this->m_TreeCon.InsertItem(this->m_Name,root3);
		this->m_TreeCon.Expand(root,TVE_EXPAND);
		this->m_TreeCon.Expand(root3,TVE_EXPAND);
		grade = "三年级" ;
		break ;
	case 3:
		this->m_TreeCon.InsertItem(this->m_Name,root4);
		this->m_TreeCon.Expand(root,TVE_EXPAND);
		this->m_TreeCon.Expand(root4,TVE_EXPAND);
		grade = "四年级" ;
		break ;

	}

	//将数据保留到对象数组中
	count ++ ;
	
	stu[count-1].name = this ->m_Name ;
	stu[count-1].id = this ->m_ID ;
	stu[count-1].grade = grade ;
	stu[count-1].sub1= this->m_Sub1 ;
	stu[count-1].sub2= this->m_Sub2 ;
	stu[count-1].sub3= this ->m_Sub3 ;

	//更新日志
	log = "" ;
	log = "DATA ADD ";

	CTime CurrentTime = CTime::GetCurrentTime();

	CString strTime ;
	strTime.Format("%d:%d:%d",CurrentTime.GetHour(),CurrentTime.GetMinute(),CurrentTime.GetSecond());

	log += strTime ;

	m_Log.AddString(log ) ;
	UpdateData(false) ;

Step 3 :Reset按钮的相应函数

清空Edit中的内容

增加日志信息

UpdateData(true);
	this ->m_Grade.SetCurSel(0);
	this ->m_ID = "" ;
	this ->m_Name="" ;
	this ->m_Sub1=0.0;
	this ->m_Sub2=0.0 ;
	this ->m_Sub3=0.0 ;

	//增加日志信息
	log = "" ;
	log = "DATA RESET" ;

	CTime CurrentTime = CTime::GetCurrentTime() ;

	CString strTime ;
	strTime.Format("%d:%d:%d",CurrentTime.GetHour(),CurrentTime.GetMinute(),CurrentTime.GetSecond()) ;

	log +=strTime ;

	m_Log.AddString(log );
	UpdateData(false);

Step 4 : Clear按钮的响应函数
	log = "" ;
	m_Log.ResetContent();

Step 5:为树形控件增加双击和选择更改监听函数。

双击监听函数:

捕获双击节点

在Student类对象中查找匹配的内容

将内容添加到List Control 中。

增加日志信息

	HTREEITEM temp = this ->m_TreeCon.GetSelectedItem();
	CString text = this ->m_TreeCon.GetItemText(temp);

	if (text =="一年级"||text == "二年级" ||text == "三年级"||text =="四年级")
	{
		AfxMessageBox("Please select the left node");
		return ;
	}
	for (int i = 0 ; i < count ; i ++)
	{
		if (text == stu[i].name)
		{
			this->m_Inquiry.InsertItem(0,stu[i].name) ;
			this->m_Inquiry.SetItemText(0,1,stu[i].id);
			this->m_Inquiry.SetItemText(0,2,stu[i].grade);
			CString str ;
			str.Format("%lf",stu[i].sub1);
			this->m_Inquiry.SetItemText(0,3,str);
			str.Format("%lf",stu[i].sub2);
			this->m_Inquiry.SetItemText(0,4,str);
			str.Format("%lf",stu[i].sub3);
			this->m_Inquiry.SetItemText(0,5,str);

			break ;
		}

	}
	//增加日志信息

	log = "" ;
	log = "TREE DOUBLE CLICKED " ;

	CTime CurrentTime = CTime ::GetCurrentTime() ;
	CString strTime ;
	strTime.Format("%d:%d:%d",CurrentTime.GetHour(),CurrentTime.GetMinute(),CurrentTime.GetSecond()) ;
	log += strTime ;
	this ->m_Log.AddString(log );

增加一个选择改变事件监听函数
void CDialogEx4Dlg::OnSelchangingTree1(NMHDR* pNMHDR, LRESULT* pResult) 
{
	NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
	// TODO: Add your control notification handler code here
	
	*pResult = 0;
}

这样,本次实验的基本功能就基本上完成了。


(实验三)约瑟夫环实验的探讨


本次实验是面向结构的程序设计和面向对象的程序设计的对比实验, 是以经典的算法问题——约瑟夫环为背景来实现的。


首先,采用的是面向结构的程序设计的思路设计解体思路。

采用的算法是——模拟算法  模拟了约瑟夫环的产生过程,具体代码如下:

#include <iostream>
using namespace std;

int main()
{
	int m,n;
	cin>>m>>n;
	int current = 0;
	int a[100000]={0};
	while(1)
	{
		int sum = 0;
		for (int i=1;i<=m;i++)
		{
			sum+=a[i];
		}
		if(sum == m-1)
			break ;//以上这几行代码的目的是为了检验是否完成了约瑟夫环的数据的产生,当和达到m-1时,模拟生成完成。
		int k = n;
		while(k!=0)
		{
			current = (current+1)%m;
			if (current==0)
			{
				current = m;
			}
			if(a[current]==0)
			{
				k--;
			}
		
			
		
		}//这里的代码的含义是进行往后计数n个数据。
		a[current]=1;
	}
	for(int j=1;j<=m;j++)
	{
		if(a[j] == 0)
		{
			cout<<j<<endl;
			break ;
		}
	
	}

	return 0 ;
}

以上的代码,还存在着一些问题,并且没有显示数据的筛选顺序。下面改进该算法的实现。
#include <iostream>
using namespace std;

int main()
{
	int m,n;
	cin>>m>>n;
	int current = 0;
	cin>>current ;
	current-=1 ;
	int a[100000]={0};
	int b[100000]={0};
	int count=1;
	while(1)
	{

	/*	int sum = 0;
		for (int i=1;i<=m;i++)
		{
			sum+=a[i];
		}
		if(sum == m-1)
			break ;
			*/
		if(count ==m)
			break ;
		int k = n;
		while(k!=0)
		{
			current = (current+1)%m;
			if (current==0)
			{
				current = m;
			}
			if(a[current]==0)
			{
				k--;
			}
		
			
		
		}
		a[current]=1;
		b[count++]=current ;
	}
	for(int j=1;j<=m;j++)
	{
		if(a[j] == 0)
		{
			cout<<j<<endl;
			break ;
		}
	
	}
	cout<<"The sequence of the quitting : \n" ;
	for(int l=1;l<=m-1;l++)
	{
		cout<<b[l]<<" ";
	}
	cout<<endl;
	return 0 ;
}

这样代码可以接收从哪一个位置开始进行计数,同时记录了依次退出系统的顺序,改进了推出系统的条件。


下面采用面向对象的思路来构造本题的解法,并最后用MFC的对话框实现这种思想。

#include <iostream>
using namespace std ;

class Children
{
public :
	int No ;//Number of the children
	int quit_No ; //Number of the quitting order 
	Children *left ;
	Children *right ;
public :
	Children();
};
Children::Children()
{

}

class Circle
{

public :
	int scale ; //the size of the circle
	int num ; // the counter
	int cur_pos ; // the beginning position

	Children *chi ; 
public :
	Circle(int s, int n , int c) ;
	~Circle();
	void Build();
	void Run();
	void Re_output();
	void Se_output() ;
} ;

Circle::Circle(int s, int n, int c)
{
	scale = s ;
	num = n ;
	cur_pos = c ;

}
Circle ::~Circle()
{
	delete []chi ;
}
void Circle::Build()
{
	chi = new Children [scale];

	for(int i=0;i<scale ; i++)
	{
		chi[i].No=i;
		chi[i].quit_No=-999;
	}
	if(scale ==1)
	{
		chi[0].left=&chi[0];
		chi[0].right=&chi[0];
	}
	else
	{
		for(int j=0;j<scale ; j++)
		{
			if(j==0)
			{
				chi[0].right=&chi[1];
				chi[0].left=&chi[scale-1];
			}else if(j==scale-1)
			{
				chi[j].left=&chi[j-1];
				chi[j].right=&chi[0];
			}else
			{
				chi[j].left=&chi[j-1] ;
				chi[j].right =&chi[j+1];

			}
		}
	}
}

void Circle::Run()
{
	Children * cur = &chi[cur_pos-1] ;
	int temp_num =0 ;
	int left = scale ;
	while(left >1)
	{
		temp_num++;
		if(temp_num ==num)
		{
			cur->left->right=cur->right;
			cur->right->left=cur->left ;

			cur->quit_No=scale-left;
			temp_num =0;
			left --;
		}

		cur = cur->right ;

	}
}
void Circle::Re_output()
{
	for(int i=0; i<scale ; i++)
	{
		if(chi[i].quit_No==-999)
		{
			cout<<chi[i].No<<endl;
			break ;
		}
	}
}
void Circle::Se_output()
{
	int  *order  = new int [scale];
	for(int i=0;i<scale;i++)
	{
		if(chi[i].quit_No==-999)
		{
			order[scale-1]=chi[i].No;
			break ;
		}
	}
	for(int j=0;j<scale;j++)
	{
		order[chi[j].quit_No]=chi[j].No;
	}
	for(int k=0; k<scale;k++)
		cout<<order[k]<<" ";
	cout<<endl;
        delete []order ;
 }
int main() 
{
	Circle c (10,3,1);
	c.Build() ;
	c.Run() ;
	c.Re_output() ;
	c.Se_output() ;
	return 0;
}

在MFC中实现上述的思想,注意MFC中可以将Circle类融入到对话框类中,而不要在新建一个Circle类,(如果新建了Circle类,不要在析构函数中释放内存空间)


界面设计如上图:

下面简要介绍一下过程:

step 1 : 创建两个类(Circle类和Children类)

代码和上面的类似

不一样的代码:

int CCircle::Re_output()
{
for(int i=0; i<scale ; i++)
	{
		if(chi[i].quit_No==-999)
		{
			return chi[i].No;
		}
	}

}
CString CCircle::Se_output()
{
	CString result =" " ;
	int  *order  = new int [scale];
	for(int i=0;i<scale;i++)
	{
		if(chi[i].quit_No==-999)
		{
			order[scale-1]=chi[i].No;
			break ;
		}
	}
	for(int j=0;j<scale;j++)
	{
		order[chi[j].quit_No]=chi[j].No;
	}
for(int k=0;k<scale;k++)
{
	CString str =" "; 
	str.Format("%d ",order[k]);

	result +=str ;

}

        delete []order ;
		return result ;

}

这两个函数的返回值要在界面中使用,因此更改了其返回值的形式。

step 2: 添加好控件,并关联相应的变量。


step 3: 为计算按钮添加相应函数

在其中,实例化Circle对象。从界面接收参数(先UpdateData(true)  最后在UpdateData(false))

并使用此对象的函数实现功能。

	UpdateData(true);

	CCircle c (this->m_total,this->m_begin ,this->m_count) ;
	c.Build();
	c.Run();
	this->m_result =c.Re_output();
	this->m_sequrence = c.Se_output();

	UpdateData(false);

本次实验差不多完成了。




实验四 基于单文档的应用程序的设计——画笔和画刷的使用




前面的三个实验都是基于对话框的程序的设计,练习了一些在对话框中经常使用的到的常用的控件的使用方法。下面的应用程序会转向面向文档的应用程序的设计。基于对话框的应用程序的设计,在新建的工程中,VC的MFC的框架提供了3个基本的类,分别是一个帮助对话框类,一个对话框主窗口类,一个应用程序类。

使用VC的MFC创建基于文档的应用程序时,会提供5个最基本的类。一个帮助对话框类,一个应用程序类,一个文档类,一个视图类,一个框架类(基于多文档的应用程序还有子框架类)。经常的开发模式是,在相应的类中,做相应的一些处理的响应的方式。

下面,主要是在视图类中练习一些基本的方法和对程序菜单的响应函数的添加方法。


Step 1 :新建一个基于单文档的应用程序,然后在View类中添加CPoint类型的对象。

CPoint对象是封装的一个点,用它来记录鼠标在屏幕下点下的位置。

Step 2:为应用程序的View类增加两个消息响应函数,通过在View类上右击---》Add Windows Message Handler ---》选择鼠标左键按下和鼠标左键谈起两个消息响应函数。

代码如下:

void CDrawTestView::OnLButtonDown(UINT nFlags, CPoint point) //当左键按下时,记录当时的按下的点的坐标
{
	// TODO: Add your message handler code here and/or call default
	m_point = point ;
	CView::OnLButtonDown(nFlags, point);
}

void CDrawTestView::OnLButtonUp(UINT nFlags, CPoint point) //当鼠标左键弹起时,使用API方式进行画线。要进行申请设备,准备划线,释放设备的阶段。
{
	// TODO: Add your message handler code here and/or call default
	//API method 
	HDC hdc = ::GetDC(m_hWnd);
	::MoveToEx(hdc,m_point.x ,m_point.y ,NULL);
	::LineTo(hdc,point.x ,point.y );
	::ReleaseDC(m_hWnd,hdc);
	CView::OnLButtonUp(nFlags, point);
}

方式二  :使用CDC类进行绘画。
	CDC *pdc =GetDC();
	pdc->MoveTo(m_point);
	pdc->LineTo(point);
	ReleaseDC(pdc);
同样要进行设备的申请和释放

方式三 :使用CClientDC类进行绘画。

 
        CClientDC dc(GetParent()) ;    //注意这里的参数的选择,如果选择是NULL。则整个电脑的屏幕就会被获得了。
	dc.MoveTo(m_point.x ,m_point.y );
	dc.LineTo(point.x ,point.y );

因为CClientDC类在其构造函数中进行了设备的申请,在其析构函数中进行了设备的释放,所以不需要在显示的进行设备的申请工作了。还有,这种方式的画笔可以在客户区上进行绘画(在工具栏上是可以画出东西的。)

方式四:使用CWindowDC类进行绘画。

	CWindowDC dc(GetParent());
	dc.MoveTo(m_point.x ,m_point.y );
	dc.LineTo(point.x , point.y );

这样画笔可以在包含菜单栏的地方以可以进行绘画了。




画笔的选择和设置。

可以通过下列的代码实现设置需要的画笔。

	CPen pen(PS_SOLID,10,RGB(255,0,0));
	CClientDC dc(this) ;
	CPen *OldPen ;
	OldPen = dc.SelectObject(&pen);
	dc.MoveTo(m_point);
	dc.LineTo(point);
	dc.SelectObject(OldPen);

也可以通过设置菜单来进行选择画笔的颜色。通过对话框接收画笔的粗细程度。

设计思路。

先设置相应的菜单项,在菜单项中添加好相应的菜单按钮,打开Class Wizard窗口,然后根据菜单项的ID号,添加在上诉4个基本类中的响应函数。在颜色设置菜单中,通过CColorDialog对话框进行选择颜色,使用GetColor可以返回一个COLORREF类型的颜色值,需要在类中添加相应类型的color变量一值,用于记录选择的值。

在画笔大小设置菜单中,需要先设置一个对话框类,用来接收输入的画笔的粗细。然后在菜单的响应函数中,实例化这个对话框,然后接收输入的参数。代码如下:

	CPen pen(PS_SOLID,m_scale,color) ;
	CClientDC dc(this) ;
	CPen *OldPen ;
	OldPen = dc.SelectObject(&pen);
	dc.MoveTo(m_point);
	dc.LineTo(point);
	dc.SelectObject(OldPen);	

void CDrawTestView::OnPencolor() 
{
	// TODO: Add your command handler code here
	CColorDialog m_ColorDialog ;
	m_ColorDialog.DoModal();
	color = m_ColorDialog.GetColor();


}

void CDrawTestView::OnPenscale() 
{
	// TODO: Add your command handler code here
	CPenScale m_PenScale ;
	m_PenScale.DoModal();
	m_scale = atoi(m_PenScale.m_scale) ;


}

画出国际象棋的棋盘:

先来看看画出矩形的方式。

	CBrush brush(color);
	
	CClientDC dc(this);
	dc.FillRect(CRect(m_point,point),&brush);
画出国籍象棋的代码如下所示:
void CDrawTestView::OnChessdraw() 
{
	// TODO: Add your command handler code here

	CClientDC dc (this) ;

	for(int i=0;i<13;i++)
	{
		for(int j=0;j<13;j++)
		{
			point1.x =i*hight;
			point1.y =j*width;
			point2.x =(i+1)*hight;
			point2.y=(j+1)*width ;
			if((i+j)%2 ==0)
			{
				CBrush brush(RGB(0,0,0));
				dc.FillRect(CRect(point1,point2),&brush);
			}
			else
			{
				 CBrush brush(RGB(255,255,255));
				 dc.FillRect(CRect(point1,point2),&brush);
			}		

		}
	}
}

注意到,当i+j为偶数时,象棋位的颜色是白色,而当其为奇数时,象棋位大的颜色是黑色,所以采用了上面的方式进行设计。

问题,当窗口的大小发生变化时,原本的图形会都消失,应该的设计方式,是绘图函数写成单独的函数,函数的参数可以在其响应函数中进行调用,而函数体在OnPaint()函数中进行调用。OnPaint()函数是捕获VM_PAINT消息的函数,这样就不会发生图形消失的问题了。

(上述方法并没有完全解决所有的问题)



实验五

双缓冲技术在绘图程序中的应用


由于实验四并没有完全的解决绘图中的问题,因此引出本次的实验的探究。

这次实验的主要探究问题是双缓冲技术来实现绘图中的图形重绘工作,同时跟踪调试程序,观察MFC中的框架的架构,理解某些类中的函数的执行过程。(通过设置断点来进行单步调试的方式)。

下面先进行第一阶段的实验。

为了更深刻的了解这种方法,所以首先进行了搜索,下面是搜索到的一些有用的资料。

下面的是关于具体的在OnDraw()函数中添加代码使用双缓冲机制的方式。

http://blog.csdn.net/xgx198831/article/details/8268731

下面一篇文章是关于双缓冲机制使用时的应注意的事项,并给出了CMemDC的开源源代码,比较深入。

http://blog.csdn.net/r3000/article/details/5454262

下面一篇文章介绍了双缓冲机制的发展历程,同时对实现的方式讲解的十分的详细。(好)

http://blog.csdn.net/xsc2001/article/details/5378601

下面一篇文章介绍的比较多,也和上面找到的一些资料是重复的。先记录下来吧。(内容比较多)

http://blog.csdn.net/scollins/article/details/5507791


下面进行代码的测试。

要实现的功能是:首先,运行程序时,默认会进行自动绘图的工作,但这时候不应该进行绘图,应该在调用过OnMenuChess()函数之后进行的。所以,添加了一个控制变量count。初始化为-1,当每调用一次OnMenuChess()函数之前,将其值置为-1,然后在调用完函数之后,将值+1。

在OnDraw()函数中,进行判断,如果count>-1,说明已经调用过OnMenuChess()函数了。这时候可以使用双缓冲机制来应对重画的要求了。


还有一个要注意的地方是,在重画函数中,一定要使用刚开始申请的DC进行窗口的重画工作。不能使用其他的DC设备,这样重画的内容无法显示了。

添加的成员变量为:

public:
	int width ;
	int height ;
	CPoint point1;
	CPoint point2 ;
	CRect rect ;
	int count ;
菜单按钮的响应函数为:
void CTestFileDrawView::paint()
{
	CClientDC dc(this);

	width=rect.Width()/13;
	height=rect.Height()/13;
	for(int i=0;i<13;i++)
	{
		for(int j=0;j<13;j++)
		{
			point1.x=i*width;
			point1.y=j*height;
			point2.x=point1.x+width;
			point2.y=point1.y+height;
			
			if((i+j)%2==0)
			{
				CBrush brush(RGB(0,0,0));
				dc.FillRect(CRect(point1,point2),&brush);
			}
			else
			{
				CBrush brush (RGB(255,255,255));
				dc.FillRect(CRect(point1,point2),&brush);
			}

		}
	}
	
	count +=1;
}

void CTestFileDrawView::OnMenuchess() 
{
	// TODO: Add your command handler code here
	count = -1 ;
	paint();
	
}

重画窗口的代码如下:
void CTestFileDrawView::OnDraw(CDC* pDC)
{
	CTestFileDrawDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	// TODO: add draw code for native data here
	GetWindowRect(&rect);
	ScreenToClient(&rect);
	if(count >-1)
	{
		CDC MemDC ; //定义一个显示设备对象
		CBitmap MemBitmap; //定义一个位图
		//定义一个与屏幕显示兼容的内存显示设备
		MemDC.CreateCompatibleDC(NULL);
		//现在还不能绘图
		//定义一个屏幕显示兼容的位图
		
		
		//上三行代码实现获得屏幕的大小的宽高
		MemBitmap.CreateCompatibleBitmap (pDC,rect.Width(),rect.Height());
		//将位图选入到设备中,类似于画笔的选择
		CBitmap *pOldBit = MemDC.SelectObject(&MemBitmap);
		//清除原来的背景颜色,这里选用了白色
		MemDC.FillSolidRect (0,0,rect.Width(),rect.Height(),RGB(255,255,255));
		//绘图代码
		//这里要注意的地方,绘图时,一定要使用MemDC设备进行绘图才能显示,不然只使用函数调用的方式,不能正确的显示图像。='
	//	paint();
		width=rect.Width()/13;
		height=rect.Height()/13;
		for(int i=0;i<13;i++)
		{
			for(int j=0;j<13;j++)
			{
				point1.x=i*width;
				point1.y=j*height;
				point2.x=point1.x+width;
				point2.y=point1.y+height;
				
				if((i+j)%2==0)
				{
					CBrush brush(RGB(0,0,0));
					MemDC.FillRect(CRect(point1,point2),&brush);
				}
				else
				{
					CBrush brush (RGB(255,255,255));
					MemDC.FillRect(CRect(point1,point2),&brush);
				}

			}
		}
		//内存中的图拷入到屏幕中显示
		pDC->BitBlt(0,0,rect.Width(),rect.Height(),&MemDC,0,0,SRCCOPY);

		//清理工作
		MemBitmap.DeleteObject();
		MemDC.DeleteDC();
		

		count ++ ;

	}

}

这样,基本实现了我想要实现的功能,也解决了上次实验留下的问题。本次实验结束。






下面过程,我通过设置了一些断点来了解程序的执行过程。下面的是断点调试现象得到的一些推理。

首先,程序在创建生成时,由无到有的过程,首先会调用OnDraw()函数进行程序的绘图区域的初始化。然后等待着响应其他的事件。


当程序进行最大化时,会再一次调用OnDraw()函数,进行重新绘图,用来匹配当前屏幕上适合显示的图形。

当从最大化的状态变化到正常显示的状态时,再一次调用OnDraw()函数。

当变成最小化状态时,首先不会发生什么函数调用,只有从最小化重新恢复时,再一次调用OnDraw ()函数,进行恢复屏幕时的绘画。


  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值