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 ()函数,进行恢复屏幕时的绘画。


上诉的处理窗口重画的过程中,加入了一些程序流程的控制,使得双缓冲在我们想要的时候在起作用。从而达到正常的程序使用逻辑。但这样做起来别别扭扭的,并且随着以后的绘图要求的增加,程序控制变得越来越不容易。下面结合其中的作业,重新采用双缓冲技术来解决窗口重画问题。


期中作业的要求是能够使用Windows的GDI绘制简单的图元,并保存成.bmp格式的文件。

首先,梳理一下程序的设计思路。

画图是通过菜单驱动的,也就是需要响应菜单命令,但在菜单响应函数中,并不是真正的完成绘图代码,而是设置一定的标记,用来表明绘图的种类,然后所有的绘图操作是由新设计的类来统一处理的。

首先是新设计了一个继承自CWnd的类CShape

CShape的头文件如下所示:

// Shape.h: interface for the CShape class.
//
//

#if !defined(AFX_SHAPE_H__8B622629_8518_430F_91D1_BFBD75C0D1CC__INCLUDED_)
#define AFX_SHAPE_H__8B622629_8518_430F_91D1_BFBD75C0D1CC__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#define SHAPE_LINE 1
#define SHAPE_CIRCLE 2
#define SHAPE_ELLIPSE 3
#define SHAPE_RECT 4

class CShape : public CWnd  
{
public:
	POINT BeginPoint ;
	POINT EndPoint ;
	DWORD ShapeType ;
	RECT ShapeRect ;

public:
	void StartDraw(POINT point);
	void DrawLine(CDC *pDC ,POINT point);
	void DrawRect(CDC *pDC, POINT point);
	void DrawEllipse(CDC *pDC,POINT point);
	void DrawCircle(CDC *pDC,POINT point);
	RECT GetShapeRect(POINT ptbeg, POINT ptend);
	RECT GetCircleRect (POINT ptbeg , POINT ptend) ;
	
public:
	CShape(DWORD type);
	virtual ~CShape();

};

#endif // !defined(AFX_SHAPE_H__8B622629_8518_430F_91D1_BFBD75C0D1CC__INCLUDED_)

CShape类是继承自CWnd的Generic class

初始时,定义了四个宏常量,用于表示要绘图的总类。

构造函数带有一个参数,用于创建相应类型的CShape类,这里的Type的种类,就是在CView类中,通过选择菜单后,设置的绘图种类。

CShape::CShape(DWORD type)
{
	ShapeType = type;
	BeginPoint = CPoint(0,0);
	EndPoint = CPoint(0,0);
}

数据成员包括:

绘图起始点和终止点,绘图类型和绘图区域。

成员函数:有两个函数,用于获得矩形区域。因为画圆和画椭圆都是在矩形区域里进行的。

RECT CShape::GetShapeRect(POINT ptbeg , POINT ptend)
{
	 CRect rtShape(CRect(ptbeg, ptend));
     rtShape.NormalizeRect();

     return rtShape;
}
RECT CShape::GetCircleRect(POINT ptbeg,POINT ptend)
{
	
    CRect rtShape(CRect(ptbeg, ptend));
    rtShape.NormalizeRect();

    int cx = rtShape.Width();
    int cy = rtShape.Height();
    int diameter = (cx <= cy) ? cx : cy;

    if (ptbeg.x <= ptend.x)
        rtShape.left = ptbeg.x, rtShape.right = ptbeg.x+diameter;
    else
        rtShape.left = ptbeg.x-diameter, rtShape.right = ptbeg.x;

    if (ptbeg.y <= ptend.y)
        rtShape.top = ptbeg.y, rtShape.bottom = ptbeg.y+diameter;
    else
        rtShape.top = ptbeg.y-diameter, rtShape.bottom = ptbeg.y;

    return rtShape;
}

开始绘画函数:

设置绘画起始点和终止点为从调用方传来的点的参数。

void CShape::StartDraw(POINT point)
{
	BeginPoint = point ;
	EndPoint = point ;

}


具体绘图函数:

void CShape::DrawLine(CDC *pDC,POINT point)
{
	ASSERT (this->ShapeType = SHAPE_LINE);
	EndPoint = point ;
	pDC->MoveTo(BeginPoint);
	pDC->LineTo(EndPoint);
}
void CShape::DrawRect(CDC *pDC, POINT point)
{
	ASSERT(this->ShapeType=SHAPE_RECT);
	EndPoint = point ;
	this->ShapeRect = GetShapeRect(BeginPoint,EndPoint);

	CBrush brush (RGB(0,255,0));
	CBrush *pOldBrush ;
	pOldBrush = pDC->SelectObject(&brush);
	pDC->Rectangle(&ShapeRect);
	pDC->SelectObject(pOldBrush);

}
void CShape::DrawCircle(CDC *pDC,POINT point)
{
	ASSERT(this->ShapeType=SHAPE_CIRCLE);
	EndPoint = point ;
	this->ShapeRect=GetCircleRect(BeginPoint,EndPoint);

	CBrush brush (RGB(255,0,0));
	CBrush *pOldBrush ;
	pOldBrush = pDC->SelectObject(&brush);
	pDC->Ellipse(&ShapeRect);
	pDC->SelectObject(pOldBrush);


}
void CShape::DrawEllipse(CDC *pDC,POINT point)
{
	
	ASSERT(this->ShapeType=SHAPE_ELLIPSE);
	EndPoint = point ;
	this->ShapeRect=GetShapeRect(BeginPoint,EndPoint);

	CBrush brush (RGB(0,0,255));
	CBrush *pOldBrush ;
	pOldBrush = pDC->SelectObject(&brush);
	pDC->Ellipse(&ShapeRect);
	pDC->SelectObject(pOldBrush);
	

}

将EndPoint设置成为从调用方传来的参数,然后获得矩形区域,在矩形区域里进行绘图。


这些操作实际上是将图形会在屏幕上的操作。

接下来要做的是CView类里的操作了:

添加如下成员数据:

	int drawtype ;
	CShape * pCurrentShape ;
	CDC MemDC ;
	RECT canvas ;

首先,重载CView类的OnCreate方法。(添加捕捉WM_CREATE消息的函数,并设置好MemDC和MemBitmap)

	this->drawtype=0;
	this->pCurrentShape=NULL;
	this->canvas = CRect(0,0,800,600);

	CBitmap MemBitmap;
	CDC *pDC ;
	pDC = GetDC();
	MemBitmap.CreateCompatibleBitmap (pDC,canvas.right-canvas.left,canvas.bottom-canvas.top);
	MemDC.CreateCompatibleDC(pDC);

	MemDC.SelectObject(&MemBitmap);
	MemDC.BitBlt(0,0,canvas.right,canvas.bottom,pDC,0,0,SRCCOPY);
	MemDC.PatBlt(0,0,canvas.right,canvas.bottom,PATCOPY);

	MemBitmap.DeleteObject();
	ReleaseDC(pDC);

上面的代码是初始化,为双缓冲做准备。

响应菜单的代码:

void CHomeWorkView::OnDrawline() 
{
	// TODO: Add your command handler code here
	this->drawtype = SHAPE_LINE ;
}

void CHomeWorkView::OnDrawcircle() 
{
	// TODO: Add your command handler code here
	this->drawtype = SHAPE_CIRCLE;

}


void CHomeWorkView::OnDrawrect() 
{
	// TODO: Add your command handler code here
	this->drawtype=SHAPE_RECT;
}


void CHomeWorkView::OnEllipse() 
{
	// TODO: Add your command handler code here
	this->drawtype=SHAPE_ELLIPSE;	
}

捕捉系统的WM_MOUSEDOWN ,WM_MOUSEMOVE ,WM_MOUSEUP等消息,并进行响应。

void CHomeWorkView::OnLButtonDown(UINT nFlags, CPoint point) 
{
	// TODO: Add your message handler code here and/or call default
	if (this->drawtype != 0&& PtInRect(&canvas, point)) 
    {

        if (this->pCurrentShape != NULL) delete this->pCurrentShape;
        pCurrentShape = new CShape(this->drawtype);

        pCurrentShape->StartDraw(point);
        RECT rtClient = canvas;
        ClientToScreen(&rtClient);
		
    }

	CView::OnLButtonDown(nFlags, point);
}

void CHomeWorkView::OnLButtonUp(UINT nFlags, CPoint point) 
{
	// TODO: Add your message handler code here and/or call default

	if (this->drawtype && pCurrentShape != NULL) 
    {
        CClientDC dc(this);
       MemDC.BitBlt(0, 0, canvas.right, 
            canvas.bottom, &dc, 0, 0, SRCCOPY);

        delete pCurrentShape;
        pCurrentShape = NULL;

    }

	CView::OnLButtonUp(nFlags, point);
}


void CHomeWorkView::OnMouseMove(UINT nFlags, CPoint point) 
{
	// TODO: Add your message handler code here and/or call default
	if (this->drawtype && pCurrentShape!=NULL)
    {
        CClientDC dc(this);
        switch (drawtype)
        {
        case SHAPE_LINE:
           dc.BitBlt(0, 0, canvas.right, canvas.bottom, 
              &MemDC, 0, 0, SRCCOPY);
            pCurrentShape->DrawLine(&dc, point);
            break;
        case SHAPE_RECT:
            dc.BitBlt(0, 0, canvas.right, canvas.bottom, 
                &MemDC, 0, 0, SRCCOPY);
            pCurrentShape->DrawRect(&dc, point);
            break;
        case SHAPE_CIRCLE:
           dc.BitBlt(0, 0, canvas.right, canvas.bottom, 
		        &MemDC, 0, 0, SRCCOPY);
            pCurrentShape->DrawCircle(&dc, point);
            break;
         case SHAPE_ELLIPSE:
            dc.BitBlt(0, 0, canvas.right, canvas.bottom, 
                &MemDC, 0, 0, SRCCOPY);
            pCurrentShape->DrawEllipse(&dc, point);
            break;
        default:
            break;
        }
    }
	
	CView::OnMouseMove(nFlags, point);
}

当窗口发生变化是:

	RECT rtClient;
    GetClientRect(&rtClient);
    pDC->FillSolidRect(&rtClient, RGB(128,128,128));

    pDC->BitBlt(0, 0, canvas.right, canvas.bottom,
        &MemDC, 0, 0, SRCCOPY);

这样,双缓冲机制解决重新问题就解决了。



注意:关于OnDraw()函数和OnPaint()函数的区别:

如果没有OnPaint()函数,则系统会调用OnDraw()函数来处理重画响应消息,但是如果一旦定义了OnPaint()函数,则系统会屏蔽对OnDraw()函数的调用。OnPaint()函数的实现,是在底层调用了OnDraw()函数。



实验六: 探索MFC的框架

本学期马上就要到期末了。老师给的实验可能不会有了,接下来是自己给自己的要求了。

侯俊杰先生的《深入浅出MFC》刚刚买来。看了近100页了,感觉确实MFC很神奇,在侯俊杰先生的讲解下,变得逐渐清晰了。下面的实验基本上是对MFC框架的探索了。


在新建的MFC项目中,有如此多的自动生成的代码,我们需要大致知道一些函数的作用。可以帮助我们在以后学习和开发过程中知道,自己的需求需要改动框架的什么地方。

先看一下CApp类中的函数:

class CTestDApp : public CWinApp
{
public:
	CTestDApp();  //构造函数

	public:
	virtual BOOL InitInstance();   //初始化实例函数

	afx_msg void OnAppAbout();		//About菜单响应函数
	DECLARE_MESSAGE_MAP()		//声明消息映射
};
VC中为代码自动添加的注释采用的是英文,针对注释进行了一系列探索,在网上搜到如下的资料:

http://blog.csdn.net/gtatcs/article/details/8830152

有比较详细的介绍VC或VS中开发常用的小插件的安装和使用。实验了一下,挺好用的。

http://blog.csdn.net/llhhyy1989/article/details/8013711

可以向在Java的IDE中使用注释的方式来注释代码了。选中代码之后,ctrl+/就注释起来了。

其实在VS中提供的类似的注释的快捷键是Ctrl+k之后ctrl+c是批量化注释选中的代码,ctrl+k之后ctrl+u是取消注释。

http://www.cnblogs.com/seaven/archive/2010/10/28/1863131.html

这里提供的解决方案和上面的解决方法是相似的。只是快捷键不一样。

http://it.china-b.com/cxsj/vc/20090612/102063_1.html

这里还介绍了一个像Java中一样自动生成文档的工具,也挺好用。

这里介绍了Visual AssistX的安装

http://www.haogongju.net/art/1896531

好了,回来接着探讨框架吧。

初始化实例函数的代码如下:

BOOL CTestDApp::InitInstance()
{
	AfxEnableControlContainer();
	//标准的初始化

#ifdef _AFXDLL
	Enable3dControls();			// 以共享DLL方式使用MFC时
#else
	Enable3dControlsStatic();	// 以静态DLL方式使用MFC时
#endif

	SetRegistryKey(_T("Local AppWizard-Generated Applications"));//设置注册表键值

	LoadStdProfileSettings();	//读取INI设置


	//注册应用程序的文档模版,作为文档,框架窗口与视图间的连接
	CMultiDocTemplate* pDocTemplate;
	pDocTemplate = new CMultiDocTemplate(
		IDR_TESTDTYPE,
		RUNTIME_CLASS(CTestDDoc),
		RUNTIME_CLASS(CChildFrame), //自定义MDI子框架
		RUNTIME_CLASS(CTestDView));
	AddDocTemplate(pDocTemplate);
	//创建主MDI框架窗口
	CMainFrame* pMainFrame = new CMainFrame;
	if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
		return FALSE;
	m_pMainWnd = pMainFrame;

	// 分析标准外壳命令,DDE,打开文件操作的命令行
	CCommandLineInfo cmdInfo;
	ParseCommandLine(cmdInfo);

	// 调度在命令行中指定的命令
	if (!ProcessShellCommand(cmdInfo))
		return FALSE;

	// 主窗口已经初始化,因此显示该窗口并对其更新。
	pMainFrame->ShowWindow(m_nCmdShow);
	pMainFrame->UpdateWindow();

	return TRUE;
}

下面是窗口框架类的说明:

class CMainFrame : public CMDIFrameWnd //主窗口框架类
{
	DECLARE_DYNAMIC(CMainFrame)
public:
	CMainFrame();
public:
	virtual BOOL PreCreateWindow(CREATESTRUCT& cs); //窗口样式定义函数
public:
	virtual ~CMainFrame();
#ifdef _DEBUG
	virtual void AssertValid() const;
	virtual void Dump(CDumpContext& dc) const;
#endif
protected: 
	CStatusBar  m_wndStatusBar; //状态栏对象 
	CToolBar    m_wndToolBar; //工具栏对象


protected:
	afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); //创建对话框消息函数	
	DECLARE_MESSAGE_MAP()
};

主框架窗口类的实现如下:

static UINT indicators[] =   //状态栏指示器
{
	ID_SEPARATOR,           // 指示器状态行
	ID_INDICATOR_CAPS,
	ID_INDICATOR_NUM,
	ID_INDICATOR_SCRL,
};
创建主要步骤:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)//创建窗口
{
	if (CMDIFrameWnd::OnCreate(lpCreateStruct) == -1)
		return -1;
	//创建工具栏	
	if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
		| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
		!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
	{
		TRACE0("Failed to create toolbar\n");
		return -1;      // fail to create
	}
	//创建状态栏
	if (!m_wndStatusBar.Create(this) ||
		!m_wndStatusBar.SetIndicators(indicators,
		  sizeof(indicators)/sizeof(UINT)))
	{
		TRACE0("Failed to create status bar\n");
		return -1;      // fail to create
	}

	//设置工具栏停靠状态
	m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
	EnableDocking(CBRS_ALIGN_ANY);
	DockControlBar(&m_wndToolBar);

	return 0;
}

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
	if( !CMDIFrameWnd::PreCreateWindow(cs) )
		return FALSE;
//	通过修改cs变量修改窗口类或样式
//	定义窗口样式代码

	return TRUE;
}

子窗口类的生成代码如下:

BOOL CChildFrame::PreCreateWindow(CREATESTRUCT& cs)
{
	//通过修改cs变量修改窗口类或样式
	//定义窗口样式代码
	if( !CMDIChildWnd::PreCreateWindow(cs) )
		return FALSE;

	return TRUE;
}

文档类的结构如下:

class CTestDDoc : public CDocument
{
protected: 
	CTestDDoc();
	DECLARE_DYNCREATE(CTestDDoc)
public:
	public:
	virtual BOOL OnNewDocument();  //新建文档函数
	virtual void Serialize(CArchive& ar); //序列化操作
public:
	virtual ~CTestDDoc();
#ifdef _DEBUG
	virtual void AssertValid() const;
	virtual void Dump(CDumpContext& dc) const;
#endif
	DECLARE_MESSAGE_MAP()
};

实现代码如下:

BOOL CTestDDoc::OnNewDocument()
{
	if (!CDocument::OnNewDocument())
		return FALSE;
	return TRUE;
}
void CTestDDoc::Serialize(CArchive& ar)
{
	if (ar.IsStoring())
	{
		// 在此添加存储代码
	}
	else
	{
		// 在此添加载入代码
	}
}

视图类的结构如下:

class CTestDView : public CView
{
protected: 
	CTestDView();
	DECLARE_DYNCREATE(CTestDView)
public:
	CTestDDoc* GetDocument();  //获取文档对象指针
public:
	public:
	virtual void OnDraw(CDC* pDC);  //重绘视图
	virtual BOOL PreCreateWindow(CREATESTRUCT& cs); //定义窗口样式
	protected:
	virtual BOOL OnPreparePrinting(CPrintInfo* pInfo); //准备打印机
	virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);//开始打印
	virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);//结束打印
public:
	virtual ~CTestDView();
#ifdef _DEBUG
	virtual void AssertValid() const;
	virtual void Dump(CDumpContext& dc) const;
#endif
	DECLARE_MESSAGE_MAP()
};

#ifndef _DEBUG  
inline CTestDDoc* CTestDView::GetDocument() //GetDocument函数的内联实现。
   { return (CTestDDoc*)m_pDocument; }
#endif

实现部分:

BOOL CTestDView::PreCreateWindow(CREATESTRUCT& cs)
{
	return CView::PreCreateWindow(cs);
}
void CTestDView::OnDraw(CDC* pDC)
{
	CTestDDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
}

GetDocument()函数有两种实现方式,Debug版本下的方式和非Debug方式下的内联函数方式。


以上就是MFC框架的总体结构,具体细节等对深入浅出MFC进一步学习之后,在做扩展。


使用应用程序向导创建:

第2步可以设置数据库支持:通过单击Data Source按钮设置数据源。

第4步可以设置文件扩展名,文件类型ID,主框架标题,文档类型名,过滤器名等等。(在高级选项中)


实验七 :针对多文档窗口和对话框的编程

1.去掉开始子窗口

只需要在CApp类的InitInstance函数中添加如下代码:

	CCommandLineInfo cmdInfo;
	ParseCommandLine(cmdInfo);
	cmdInfo.m_nShellCommand = CCommandLineInfo::FileNothing ; //设置去掉子窗口
只是第三行是新增代码用于控制开始时,不产生子窗口。

2.自定义窗口样式。

函数PreCreateWindow可以定义窗口的样式。在CFrame类中增加代码,是修改的主窗口的样式,在CView中修改代码是修改的子窗口的样式。

窗口的样式通过CREATESTRUCT结构体定义的对象,修改其属性值,会产生不同风格的窗口。

CREATESTRUCT结构定义如下:

typedef struct tagCREATESTRUCT{
LPVOID lpCreateParams; //窗口创建参数指针
HANDLE hInstance;//新窗口使用的模块实例句柄
HMENU hMenu; //新窗口使用的菜单句柄
HWND hwndParent; //父窗口句柄,可以为null
int cy; //新窗口的高度
int cx;//新窗口的宽度
int y; //新窗口左上角y坐标值
int x; //新窗口左上角x坐标值
LONG style ;//新窗口样式
LPCSTR lpszName;//指定新窗口名称
LPCSTR lpszClass;新窗口类
DWORD dwExStyle; //新窗口扩展样式
}CREATESTRUCT;

本示例中对主窗口的修改如下:

	cs.cx=450;//新窗口宽度
	cs.cy=300;//新窗口高度
	cs.style=WS_POPUPWINDOW|WS_CAPTION;//新窗口去掉子窗口名,最大化和最小化按钮


对子窗口的修改如下:

	cs.lpszClass=AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW,     //改变窗口时重绘
		0,//光标
		(HBRUSH)::GetStockObject((BLACK_BRUSH)),//设置黑色背景
		0);//图标
	return CView::PreCreateWindow(cs);

3.创建不规则的窗口。

第一步:创建不规则区域Region

第二步:将窗口放进该Region中。

VC的MFC提供了CRgn类,相应的函数有CreateRectRgn,CreateEllipticRgn,CreatePolygonRgn,CreateRoundRectRgn,CombineRgn等。

示例:创建基于对话框的应用程序,并在OnInitDialog函数中,添加如下代码:

	CRgn rgn1,rgn2,rgn;
	rgn1.CreateEllipticRgn(0,0,50,100);
	rgn2.CreateEllipticRgn(300,150,50,50);
	rgn.CreateRectRgn(0,0,640,480);
	rgn.CombineRgn(&rgn1,&rgn2,RGN_OR);
	SetWindowRgn((HRGN)rgn,TRUE);

4.创建拆分窗口。

可以在创建的时候,在第四步时进行设置,Advanced选项,Windows Style选项卡选中Use split window(使用拆分窗口)

这样是格式化的拆分。

自动生成的拆分代码如下,可以在此基础上进行修改:

BOOL CChildFrame::OnCreateClient( LPCREATESTRUCT /*lpcs*/,
	CCreateContext* pContext)
{
	return m_wndSplitter.Create( this,
		2, 2,                 // TODO: adjust the number of rows, columns
		CSize( 10, 10 ),      // TODO: adjust the minimum pane size
		pContext );
}

运行程序,选择菜单项中的窗口--》分隔命令就可以分隔窗口了。

其中的m_wndSplitter是一个CSplitterWnd类的一个实例化对象。


5.对话框程序的结构:

CApp类:InitInstance()函数。

BOOL CTestDlgNewApp::InitInstance()
{
	AfxEnableControlContainer();
	//标准初始化
#ifdef _AFXDLL
	Enable3dControls();			// Call this when using MFC in a shared DLL
#else
	Enable3dControlsStatic();	// Call this when linking to MFC statically
#endif
	CTestDlgNewDlg dlg; //主对话框类实例对象
	m_pMainWnd = &dlg; //赋给应用程序类成员变量
	int nResponse = dlg.DoModal();//显示主对话框窗口
	if (nResponse == IDOK)
	{
		//OK按钮的处理代码
	}
	else if (nResponse == IDCANCEL)
	{
		//CANCEL按钮的处理代码
	}
	return FALSE;
}
其中m_pMainWnd是继承自CWinThread类的成员变量,通常使用如下的方式在程序中获得:

AfxGetApp()->m_pMainWnd

CDlg类:数据交换函数DoDataExchange()初始化对话框函数OnInitDialog()和绘制函数OnPaint()

CTestDlgNewDlg::CTestDlgNewDlg(CWnd* pParent /*=NULL*/)
	: CDialog(CTestDlgNewDlg::IDD, pParent)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); //载入对话框窗口图标
}

void CTestDlgNewDlg::DoDataExchange(CDataExchange* pDX) //动态数据交换
{
	CDialog::DoDataExchange(pDX);
}
BOOL CTestDlgNewDlg::OnInitDialog()
{
	CDialog::OnInitDialog();
	// 将About菜单项添加到系统菜单
	ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
	ASSERT(IDM_ABOUTBOX < 0xF000);
	CMenu* pSysMenu = GetSystemMenu(FALSE);
	if (pSysMenu != NULL)
	{
		CString strAboutMenu;
		strAboutMenu.LoadString(IDS_ABOUTBOX);
		if (!strAboutMenu.IsEmpty())
		{
			pSysMenu->AppendMenu(MF_SEPARATOR);
			pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
		}
	}
	//设置对话框图标
	SetIcon(m_hIcon, TRUE);			// Set big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon
	return TRUE;  
}

void CTestDlgNewDlg::OnSysCommand(UINT nID, LPARAM lParam) //系统命令消息响应函数
{
	if ((nID & 0xFFF0) == IDM_ABOUTBOX) //About按钮命令
	{
		CAboutDlg dlgAbout;
		dlgAbout.DoModal();
	}
	else  //其他按钮命令
	{
		CDialog::OnSysCommand(nID, lParam);
	}
}
void CTestDlgNewDlg::OnPaint() //窗口绘制
{
	if (IsIconic())     //包含图标
	{
		CPaintDC dc(this);  //申请设备
		SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0); //发送消息
		//将图标放置在客户区中间
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;
		//绘制图标
		dc.DrawIcon(x, y, m_hIcon);
	}
	else   //未包含图标
	{
		CDialog::OnPaint();
	}
}
在资源选项卡下,选中Dialog,右键选择properties可以设置资源语言。

在对话框中拖入控件之后,会激活Layout菜单,可以使用其中的菜单命令对对话框的摆放进行设置。比使用鼠标拖放要高效的多。

使用快捷键ctrl+D可以显示Tab键的顺序,然后做相应的修改。

使用ctrl+t可以测试对话框。


6.常用对话框

消息对话框MessageBox

int MessageBox(
LPCTSTR lpszText,  //提示信息内容
LPCTSTR lpszCaption, //消息对话框标题
UINT nType //消息对话框样式
);
消息对话框的样式有:图标样式,按钮样式,其他样式。MB_前缀说明是MessageBox样式

样式参数示例说明
MB_ICONHAND,MB_ICONSTOP,MB_ICONERROR×号
MB_ICONEXCLAMATION,MB_ICONWARNING!号
MB_ICONQUESTION?号
MB_ICONASTERISK,MB_ICONINFORMATIONi号
MB_OKOK
MB_OKCANCELOK ,Cancel
MB_YESNOYes No
MB_YESNOCANCELYes No Cancel
MB_RETRYCANCELRetry Cancel
MB_ABORTRETRYIGNOREAbort Retry Ignore
MB_DEFBUTTONx设置第x按钮为默认按钮.x=1~4
MB_HELP添加帮助按钮
MB_RIGHT提示信息内容右对齐
MB_SETFORGROUND显示在桌面最前面
MB_TOPMOST总是显示在桌面的最前面

消息对话框的返回值:

返回值含义
IDABORT单击放弃按钮
IDCANCEL单击取消按钮
IDIGNORE单击忽略按钮
IDNO单击No按钮
IDOK单击OK按钮
IDRETRY单击Retry按钮
IDYES单击Yes按钮

模式对话框和非模式对话框:

http://blog.csdn.net/sandyqy/article/details/3327863

这篇文章有比较详细的对这两种对话框的比较。


公用对话框:他们都是从CCommonDialog派生来的。

文件对话框:

CFileDialog(
BOOL bOpenFileDialog,  //打开为true,保存为false
LPCTSTR lpszDefExt=NULL, //默认文件扩展名
LPCTSTR lpszFileName=NULL,//文件名
DWORD dwFlags=OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,//文件操作标记
LPCTSTR lpszFilter=NULL,//过滤器
CWnd*pParentWnd=NULL //父窗口指针
);

常用的成员函数:

CString GetPathName:返回所选文件的完整路径名

CString GetFileName:返回所选文件的完整名称

CString GetFileExt:返回所选文件的扩展名

CString GetFileTitle :返回所选文件的标题名


字体对话框:

CFontDialog(
LPLOGFONT lplfInitial=NULL, //初始化字体字符
DWORD dwFlags=CF_EFFECTS|CF_SCREENFONTS,//字体选择标记
CDC*pdcPrinter=NULL, //打印设备
CWnd *pParentWnd=NULL //父窗口指针
);

常用的成员函数:

void GetCurrentFont:返回当前选择的字体

CString GetFaceName:返回所选字体的字体名称

CString GetStyleName:返回所选字体的样式名称

int GetSize :返回所选字体的大小

COLORREF GetColor:返回所选字体的颜色

int GetWeight:返回所选字体的磅数

BOOL IsStrikeOut:字体是否带有删除线

BOOL IsUnderLine:字体是否带有下划线

BOOL IsBold:字体是否为粗体

BOOl IsItalic:是否为斜体


颜色对话框:

CColorDialog(
COLORREF clrInit=0, //默认选择的颜色
DWORD dwFlags=0, //颜色选择标记
CWnd*pParentWnd=NULL //父窗口指针
);

成员函数:

COLORREF GetColor:返回当前选择的颜色。

COLORREF * GetSavedCustomColors:返回所选的颜色

void SetCurrentColor:设置当前的颜色

BOOL OnColorOK:验证输入到对话框中颜色


页面设置对话框。

CPageSetupDialog(
DWORD dwFlags=PSD_MARGINS|PSD_INWINININTLMEASURE,//定制页面设置对话框标记
CWnd*pParentWnd=NULL //父窗口指针
);

成员函数:

CString GetDeviceName:返回所选打印机设备的名称

LPDEVMODE GetDevMode:返回所选打印机的设备模式

CString GetDriverName:返回所选打印机的设备驱动

CString GetPortName:返回所选打印机的输出端口名称

CSize GetPaperSize:返回所选打印机的页面大小

void GetMargins:返回所选打印机的边距


打印对话框:

CPrintDialog(
BOOL bPrintSetupOnly , //是否带设置对话框
DWORD dwFlags=PD_ALLPAGES|PD_USEDEVMODECOPIES|PD_NOPAGENUMS|PD_HIDEPRINTTOFILE|PD_NOSELECTION //定制打印对话框标记
CWnd *pParentWnd=NULL //父窗口指针
);
成员函数:

int GetCopies:返回请求的副本数

BOOL GetDefaults:返回默认的设备

int GetFromPage:返回打印范围的开始页

int GetToPage:返回打印范围的结束页

HDC GetPrinterDC:返回打印设备句柄

BOOL PrintAll 确定是否打印全部文档。

BOOL PrintCollate是否自动分页

BOOL PrintRange:是否仅打印页面的指定范围

BOOL PrintSelection:是否仅打印当前选择的项目



查找替换对话框:CFindReplaceDialog类对象的创建函数:

BOOL Create(
BOOL bFindDialogOnly , //查找或查找替换对话框
LPCTSTR lpszFindWhat, //指定搜索的字符串
LPCTSTR lpszReplaceWith=NULL,//指定替换字符串
DWORD dwFlags=FR_DOWN, //制定查找替换标记
CWnd*pParentWnd=NULL //父窗口指针
);

成员函数:

BOOL FindNext:搜索下一个匹配的字符串

CFindReplaceDialog* PASCAL GetNotifier:返回当前对话框指针

CString GetFindString:返回搜索的字符串

CString GetReplaceString:返回替换字符串

BOOL IsTerminating:确定是否终止了对话框

BOOL MatchCase:确定字符串是否大小写完全匹配

BOOL MatchWholeWord:确定字符串是否整字匹配

BOOL ReplaceAll:确定是否全部替换所有搜索到的字符串

BOOl ReplaceCurrent:确定是否替换当前字符串

BOOL SearfchDown:是否向下搜索。












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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值