VC菜单学习:
子菜单、菜单项等的概念与菜单项的定位
菜单项的图标、复选、系统的属性获取
MFC命令更新机制 CN_UPDATE_CCmdUI消息,捕获是用ON_UPDATE_CCmdUI()函数,可以用ClassWizard来;
去除菜单及局部变量的非法性
通过ID来与图标一一对应
右键弹出菜单 Project/Add to project/Components and Controls/Visual C++,
当然,也可以手动增加.这里就要将客户区坐标换成屏幕坐标,因为我们给出的是我们以为的客户区坐标,
但系统却把它当作屏幕坐标
**********以上是关于静态菜单 的*******************
***************以下是动态菜单(即用代码添加的)***************
在MainFrme的OnCreate()中创建
建立对象,创建某类型的菜单,再获取主框架的菜单,然后Append上面去或Insert上面去,注意参数的依赖
关系;
删除菜单用DeleteMenu()
Resource.h定义了资源的ID
手动完成消息响应:在对应头文件中写消息处理函数原型,在其实现文件中消息对应机制
接触集合类和在视图中输出、窗口重绘
VC ++ 开发过程中常见错误提示:在某个星号之前应增加分号,而其实是说星号前面的这个动西它不认识
只有源文件是参与编译的,头文件是不参与编译的
如何在框架类中去截获本由View类响应的消息。
对话框:
对话框:模态对话框和非模态
资源如何与一个类联?难道仅仅是通过Load?
当用Create创建非模态对话框时,还要调用一个showwindow将其显示出来,当然局部变量不行,但
为什么模态对话框行,因为其暂停在那,其生命周期未变;那么,要使非模态对话框能显示,解决的
办法有:变为成员;二在堆上分配,因为在堆上分配的变量,其生命周期与应用程序一样;
按钮的消息属于通告消息
在对话框按件上放控件:
“钉子图标”使保持显示和不保持
三种访问控件的方式:
第一种:
表态文本框:要获取其文本,也即获取窗口文本
获取对话框上控件的指针,GetDlgItem(),这感觉就有点像在主框架窗口当中,当时要得到菜单栏一样;
对于于静态文本框,它是不能接受消息并响应的,为了使它能接受,必须改变其属性,使其能够Notify
文本的形式转换成数值形式
第二种:
另外一个函数,GetDlgItemText()
第三种访问控件的方式:GetDlgItemInt()和SetDlgItemInt()
第四种:将控件与成员变量关联 -Member Varibles,并可以看看DDX_Text函数 Dialog Data Exchange
这里更涉及到DoDataExchange()和UpdateData()
还可以对控件变量关联控件变量本身,其实也即控件对象。
第六、从Windows消息的传递机制,我们可以考虑:只要我们知道获取文本的消息,它是WM_TEXT
我们可以通过发送这个SendMessage()这个Win32函数,那么在VC ++中,即通过加双冒号表示是调用Win 32
平台函数
SendMessage的多种实现 ,同时,注意怎样去获得句柄
第七种:直接给对话框子控件发送消息
获取文本框选的一个消息,当然,这里面还要设置其为焦点
完成对话框的收缩功能,这个功能与Windows画图时的扩展与收缩类似
它用Picture来画成一条线,并将其ID改为IDC_SEPARATOR,并设置属性为Sunken
既然扩展时要还原成原来大小,那么其初始状态要保存才好;而收缩时,要从图像控件下面部分切除掉,那么,就
要获取切除那一点的纵坐标。获取窗口大小,可以用GetWindowRect()来
完成窗口伸缩与扩展功能,用SetWindowPos()
那么,剩下的问题词是如何改变焦点方式及缺省按钮的设置:
而这不再是简单地按一个按钮,就响应什么了,那么,这里,其实是通过InitDialog()函数中来改变……
用自写的窗口过程来代替MFC写的窗口过程 SetWindowLong是用来改变窗口的属性,使其控件中使其下一个焦点是其下面的控件
这里又引起我的思考;强制类型转换底层是怎样的?
怎样去写窗口函数,可以用先要知道其类wndclass
那么更简单的,实现焦点依次往下传递是怎样的呢?
获取窗口句柄:GetWindow(),GetDlgNextItem()
焦点依次往下传递:GetFocus()->GetNextWindow()->SetFocus()
更安全的代码是GetNextDlgTabItem(GetFocus())->SetFocus()
基于对话框的应用程序:
逃跑按 钮的巧妙实现——
将两个按钮都关联到同一个新建的类,然后,类中可以建一个指针,来保存双方的地址。
********************————————********************************
属性表单和菜单项的创建
要想使视类具有修改编辑功能,我们可以选择CEditView或CRichEditView类作为其基类
一、属性表单是由一页页属性页组成的,那么我们需一页页的创建属性页,而每个属性页就
是一个窗口,那么,我们可以通过Insert/Dialog/IDD_……PropertyPage来创建一页页的属性页;
这里面本身有VC 6.0 的bug,我们要学会如何解决:使ClassWizard找到我们新增的类
我们先找到记录工程类的信息的文件,我们进入工程文件夹里,找到工程名.clw;我们可以将其删除,
然后重新打开工程,选择重新建立工程的clw文件即可;
通过菜单响应,在其中增加属性表单页表,来调用DoModal() 并显示
那么属性表单实际上是属性页的父窗口
根据MSDN来看,如果我们要得到向导视图一步步显示的效果,我们可以在CPropertySheet类调用DoModal()
之前,加上调用SetWizardMode()来实现。那么中间又有一些BUG,我们需要改变第一页,使其只有下一页按
钮,面最后来一页只有上一步按钮,那么,我们根据MSDN的提示,我们知道,解决的办法是:在CPropertyPage类
对象中,增加OnSetActive()函数,在这人函数中调用CPropertySheet类的SetWizardButtons()函数。
那么我们再完善,要求用户在进行了某一项选择之后再进行下一步:
这里面有一个小的注意,我们要能在ClassWizard中的属性页对应的类中看到控件的ID,我们需要将控件的
Group属性选中,那么表示与这个控件同一类的,紧跟在其后的控件值依次增1,直到遇到下一个具有Group属性的另
一个,那么这里我们还有一个问题,对于属性表单“下一步”的响应,我们是在哪里面响应呢,我们增加虚函数
OnWizardNext()
☆☆需要强调一下,我们是通过DoDataExchange()来与一个控件交换数据的,注意,是所有控件,只要我们想与之发生交换数据!☆☆
那么也即调用UpdateData();
☆☆而我们要使下拉列表框中有初始值,我们可以增加WM_INIT()消息响应处理函数☆☆
增加列表框里面的字符串,用到了AddString()函数
同样,对于第三个属性页,我们也可以增加OnWizardNext()虚函数,并在初始化其类之后,也对其增加AddString(),注意,
我们的ComboBox(组合框)有sort()属性,它能自排序,同时,我们可以设置让其初始显示一个默认值,调用SetCurSel()函数
为了使视类中输出用户的选择,我们要在视类中定义相应变量来接收用户的选择。
☆☆在这里有一个技巧,将BOOL数组变量赋初值为FALSE,用到C语言中memset()函数☆☆
用Invalidate()来使窗口无效,从而引起完成窗口的重绘
创建字体,然后将其选择进行设备环境,它会返回先前的字体,我们将其保存起来
那么我们输出时,针对具体我们的选择,我们应该利用我们在视图类定义的与属性页对应控件变量的变量来进行相应改变和输出,
当然,我们要知道它输出的位置,我们要调用GetTextMetrics()函数来
修改程序框架及其外观:
要想改变MFC应用程序的外观,应在其窗口创建之前。
同样,要想改变MFC应用程序的主框架外观,应在主框架之前,在CMainFrame类的PreCreateWindow()函数中改变。
那么我们在开始学习VC ++编程时,就已经知道,PreCreateWindow()函数是一个虚函数,按照VC ++机制,
如果我们传递了子类的指针,,那么它先调用子类的相应函数。而这个函数它有一个CREATESTRUCT类型的变量,而且是
引用的,那么对其的修改,将是全局性的。
***********************************************************************************************************
如果我们要在窗口创建之后改变窗口外观,行不行?其实,这个函数我们也已经学了,即SetWindowLong(),同样也别忘了猜
测一下有没有GetWindowLong()这个函数即其相应功能。
#############################################################################################################
修改窗口的图标、光标和背景,我们应如何去修改呢?
窗口的类型和大小,是在创建和显示窗口时设定的,而要改变窗口的图标和光标、背景,则是在设计窗口类时改变的,那是MFC的
底层代码,但是我们有其它办法,可以对其修改:我们可以自己去设计窗口类,注册它、显示它
★★在直接用VC ++写的应用程序,我们获得句柄可以由其WinMain()参数来,而在MFC中,由于应用程序完全是由AppWizard生成
的,那么,我们要获得其句柄,需要用到一个全局函数,可以用来获取当前一个应用程序的句柄。叫做AfxGetInstanceHandle()
★★
★在LoadCursor()函数中,如果我们是要标准光标,我们需要将第一个参数设为NULL
★因为菜单的创建其实并不是在设计窗口类时完成,而是在底层完成,我们知道:在MFC中,
★在InitInstance()函数中,其在单文档模板中将主菜单的资源标识号传给了我们的模板,
★那么在底层代码下,其完成资源标识与ID的转换
#############################################################################################################
接下来我们要完成让主框架窗口的显示按我们设计的类来,那么,我们要做的是用cs参数来
但是,我们要注意,对于单文档应用程序来说,我们看到的是View视图类的窗口覆盖在主框架窗口之上的,所以,如果我们要看到改变的
效果,我们应是在视图类的PreCreateWindow()函数中来
我们可见,为了修改MFC应用程序图标,我们几乎重新设计了一个类,这显然是不划算的,那么MFC又提供了一个全局函数,它是
AfxRegisterWndClass()类名
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
那么如果我们是在窗口创建之后,可不可以修改窗口的图标、光标等呢?可以,我们可以利用一个全局的API函数,SetClassLong()
完成不断变换的图标:
利用定时器和SetClassLong()即可完成:
先准备三幅图标,放到res文件夹下
然后定义存储三幅图标的句柄数组,并在CMainFrame类中将数组元素初始化,用到了当前应用程序的实例句柄
☆获得应用程序当前的实例句柄,AfxGetInstanceHandle()这个非常有用,别忘了!当然,我们也可以
通过CWinApp这个类本身的成员来实现,当然我们在源文件中要
用到另一个源文件中定义的全局变量,我们要用extern 声明一下;还可以利用GetApp()函数辗转来得到当前应用程序的实例句柄☆+
★怎样将资源的ID号转换为资源的字符串呢,这里面就有一个宏,MAKEINTRESOURCE()★
接下来,我们设置一个定时器,在OnCreate()函数中调用SetTimer(),然后在另外增加WM_TIMER消息,在OnTimer()函数中使用SetClassLong();
那么,当然,我们需要在响应定时器的函数中,图标的index是在合理的变化范围内
●这里有一个技巧:要使一个值始终在一个范围内,最好的办法就是取模●
###################################################################################################################
工具栏的编程:
在工具栏上增加相应图标——>具栏上的相应图标与菜单栏上的某菜单项同样的ID号
在工具栏上要想增加分割符的话,我们可以左点击控钮,拖动一段距离;要删除按钮的话,拖动它到任意位置
自创建工具栏,我们先看一下MFC的WizardApp它的是怎样创建的,我们应好好模仿
在我们MFC中,允许一人ID号对应多种资源
EnableDocking()函数,对于CControlBar类的,是设置其可以停靠,而对于主框架类的,则设置其为可以被停靠!
下面实现工具栏由复选工具箱控制显示与隐藏:—>IsWindowVisible()来判断一个窗口是否是可视的,然后在里面调用ShowWindow()来控制显示或
隐藏,但是,还需使用RecalcLayout()来调整窗口显示。还有一个问题,当我们拖动新工具栏,使其成为浮动窗口时,我们的点击毫无意义,为此,
还要使其停靠
另一种显示工具栏的方法是利用CFrameWnd的方法来,即ShowControlBar()
那么为了更符合我们的使用习惯,我们可以将菜单上打对勾,在对应菜单上面增加UPDATE_UI_COMMAND的消息处理,在里面可以通过调用pCmdUI->SetCheck(),
并在SetCheck()中调用IsWindowVisible()来同步我们的显示
#######################################################################################################################
状态栏的编程:
状态栏分为提示栏和状态指示器:而对于MFC已经建立的状态栏,我们可以其全局变量数组中增加一些内容——>
首先,我们建立一个在字符串资源,在其中增加字符串及其ID,然后,我们可以通过函数来获取相应内容,比如说,CTime来获得当前时间,再次,我们定义一
中间临时变量来存放我们想获得的内容,从而并显示;利用StatusBar的函数SetPaneText()来显示我们想要显示的内容,对于SetPaneText()中要获得其索引,我们
可以通过命令标题来获得其索引,即CommandToIndex()
有时我们要改变窗格的宽度,我们可以通过CStatusBar中的方法来,这个方法是SetPaneInfo()函数,而我们要获得适当字符串的宽度,我们要得适当字符串的宽度,
我们在前面已经讲过,可以用GetTextExtent(),它会返回CSize的对象,注意:CSize里面的成员变量cx才是其宽度;
但是,为了显示时时的最新时间,我们可以通过定时器里来做
#########################################################################################################################
进度栏的编程实现:
类是CProgressCtrl,根据MFC的MSDN,步骤先是构造一个CProgressCtrl对象,然后通过Create()来创建具体的进度条对象;
要想将这个进度栏放到状态栏里面,我们要获取窗格的大小,我们可以用GetItemRect()来获取,然后只是要在Create()函数中将其父窗口设置为状态栏
★★★★这里面,调试时Insert BreakPoints后,再观察,发现rect没获得想获得的值班。那么,实际上,这与消息响应的先后顺序有关。★★★★
★★★★★★★★我们应自定义消息,这样一来,因为消息是放在消息队列当中的,WM_CREATE消息先响应,这之后,才是我们自定义的消息,那么在★★★★★★★★
★★★★★★★★OnCreate()执行之后,这样一来,我们才可能达到我预期的目的,由于系统中消息都是用一个整数表示感谢的★★★★★★★★
★★★★★★★★是#define UM_Message WM_USER(WM_USER宏为以上允许用户自定义的消息范围)★★★★★★★★
★★★★★★★★然后作消息映射,对于消息来说,我们是用ON_MESSAGE这个宏★★★★★★★★
★★★★★★★★然后声明消息处理函数afx_msg …,如果我们发送消息要顺带有参数,那么参数在消息处理函数中声明★★★★★★★★
★★★★★★★★然后在要发送消息的函数中调用SendMessage()函数★★★★★★★★
在以上操作,插入断点,调试,仍是有问题;事实上,因为用SendMessage()函数,那么消息马上进入到消息处理之后,然后再返回
因此我们要换另一个函数:PostMessage(),它是将消息放到消息队列中,然后通过GetMessage()一条一个条取出运行
当然,我们需要解决当拖动窗口时,进度条在状态栏里又出现显示不当的情况;这里,其实它在OnPaint()函数中,那么还要判断是否这个状态栏是第一次创建,若是,则
创建;否则,就要移动窗口。移动窗口,有个函数,叫MoveWindow();而要移动进度条,可以用StepIt()函数,放在OnTimer()函数中
▲▲在状态栏中放置文本,可以调用SetWindowText()函数或直接用CFrameWnd类的SetMessageText()函数直接放在状态栏里;也可以用CFrameWnd类的GetMessageBar()▲▲
▲▲函数来获得状态栏的指针;第四种方式,可以用CWnd类的GetDescendantWindow()函数来获取子孙窗口,直到找到指定ID的窗口▲▲
临时窗口和永久窗口的概念:临时对象是任意通过FromHandle()函数创建的没有封装过的C++对象
######################################################################################################################
启动画面的创建:利用VC给我们提供的组件库Project->Add to Project->Components and Controls…Visual C++ Components,这里面都是C++里面有用的类
用类名::函数名,那么这个函数就是类的静态函数
MFC文件操作:
在C语言中,指向文件的指针很多都是指向常量的指针和其它
那么,先预习前提知识:指向常量的指针和指向指针的常量——>
指向常量的指针:表示其指向的内容是常量,不允许通过指针来修改指向的内容.const char *pStr="iosi";或者写为:char const * pStr="iosi";
那么此时若做这个操作——*pStr='W';//error 但是做pStr="wang";//OK
当然,我们仍可通过它本身这个字符数组来修改其里面的内容
指针常量——>指针的值不能改变,但是指针所指的内容可以改变.char str[5]="isao"; char * const pStr=str;//也即形式是const放在*号的后面
那么,我们此时若做这个操作——*pStr='W';//OK 但是做pStr="wnag";//error
那么指针常量必须在定义的时候即初始化
*****文件的操作*****
要获取指向文件的指针,我们通过查MSDN,可知有函数fopen()来。C语言对文件的操作采用缓冲区来进行,所以我们如果没有用fclose(pFile)来在操作完
之后关闭文件,那么,我们只有在结束运行之后,才能看到我们写成功的文件
★★如果我们要对文件频繁操作,那么,像上述那样,每次打开、关闭较麻烦,我们有函数fflush()来刷新缓冲区,让缓冲区的内容写入到磁盘★★
☆☆但是,有时我们有一些特殊的应用,比如说我们有服务器端软件,它监听了很多设备,它们不断把日志信息发送给服务器端软件,并写入服务器端磁盘,☆☆
☆☆如果我们每次去打开、关闭,那么很可能会丢失许多有用信息,那么我们此时可以使用fflush()函数来不断刷新缓冲区进行写入☆☆
※※那么,对于我们C语言文件来说,它其实有一个文件指针,每次自动移动,指向我们要写入的地址※※
如果我们想移动文件指针,有许多函数可用fseek()、rewind()函数等,对文件的读入,有时会因为找不到"\0"结束符而出现乱码,对此,我们有多种方法解决
第一种:是在sizeof()后加1;第二种:使用memset()函数
☆★有时,我们需要获取文件的长度,用ftell()可以得到文件当前指针的位置,那么,如果我们先用fseek()函数来使文件指针指向文件结尾处,然后我们再用★☆
☆★在对文件进行操作时,我们经常容易犯的错误是移动了指针,等到输出时却忘了要将文件指针移到我们想要开始读出的位置★☆
★☆文件操作常犯的失误:当以文本方式去读与以二进制去读时注意回车与换行的不同★☆
比如说,如果有一个面试题,让你将整数值,例如98341存储到文件中,打开时,还要是98341,那么,我们要明白这是考察你对文本文件与二进制文件的理解。
文件的存储底层是以二进制的ASCII码方式存储,如果我们直接输出98341,因为它占四个字节,且编码是另外一种形式,等我们输出,它就不是98341,而是按照
ASCII码的形式输出为其它字符,那么,具体地解决办法是:一、定义一个char[5],然后用char[0]=9+48;//48为0的ASCII编码
二、采用itoa(i,ch,10);
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
接下来,我们来看C++中对文件的操作,它是几个类,比如说ofstream,ifstream或者iostream类
如果我们要向文件当中写入数据,我们要用ofstream这个类,并用其方法write()来写入数据,并用其close()方法来关闭文件
我们在读取的时候,用ifstream这个类,当然,使用它们我们都要包含库头文件#include<fstream.h>
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
那么,下面我们来看一下,Win32中对文件的操作,我们要注意几个函数
CreateFile()用来创建或打开读取文件等诸多对象,WriteFile()用于向这些对象写入内容,ReadFile()用于读取数据,CloseHandle()去关闭这个对象句柄
同步IO:即当我们写入或读取文件的时候,如果我们没有写入完,程序就会挂起
异步IO:重叠IO,
/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
接下来,我们来看一下MFC中提供的对文件的操作的类CFile。其打开文件的方法为根据其构造函数来选择CFile::modeCreate或其它属性
写出有方法:write(),读入有方法:read(),关闭有方法:Close()
要实现“打开”、“保存”对话框有CFileDialog这个类,同时,要过滤掉某些文件,需要用到其数据成员m_ofn的相应属性,同时,在过滤字符串后面加上\0*.txt等,且最后一个
字符串以两个空字符结尾,对于文件过滤器lpstrfilter的设置,选两种格式中间的分割仅用'\0'分开
\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//
配置文件的读写
一般文件都写到注册表中,如果我们要写到W.ini文件中,要用到一个函数WriteProfileString(),然后我们要得到其写入的配置文件内容,我们用函数GetProfileString()函数
//那么如果我们写入到Win.ini文件中,我们要使用Win 32 API函数::WriteProfileString() 和相应的::GetProfileString()函数,放在APP源文件的InitInstance()函数中
//当然,我们也可以用CWinApp的成员函数WriteProfileString()和GetProfileString()函数来
/\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\///\\\
下面我们来看一下注册表的编程:
对注册表的操作都是通过几个函数来完成的,RegCreateKey()和RegSetValue()和最后关闭RegCloseKey()函数,如果我们要读取数据,我们有一个函数RegQueryValue(),
当然,其中RegSetValue()函数只能设置字符串类型的值。如果我们要设置整型的值,则要用其扩展函数叫RegSetValueEx()函数,对应打工用的函数RegOpenKey()函数,
然后再用RegQueryValueEx()函数来取出
/\///\\\/\///\\\/\///\\\/\///\\\/\///\\\/\///\\\/\///\\\/\///\\\/\///\\\/\///\\\/\///\\\/\///\\\/\///\\\/\///\\\/\///\\\/\///\\\/\///\\\/\///\\\/\///\\\
使用CArchive类对文件进行操作:
MFC文件框架内部实现机制和串行化操作: 文档与串行化
另一种文件操作方式——>用CArchive这个类来,其"<<"和">>"这个符号用于写出和读入,注意,写出的顺序是怎样的,那么读入的顺序也是一样,其使用,具体参照MSDN来
注意,对于我们的文件保存和文件打开选项,它对应的CWinApp类的OnFileNew()和OnFileOpen()函数,而且对于单文档用户程序来说,它的pDocument对象只与一个文件相关联。
下面我们要用CArchive类和Serialize()函数来实现文档的保存和其它操作:
要按照MSDN提示,可见CArchive类的MSDN说明文档的下部分链接文档。我们可以拷贝资源
这里完成一个绘图的程序:
它增加了一个CGraph类,这个类由CObject派生出来,目的是为了串行化。那么,同时在菜单中增加了绘制几个图形的命令,在…View类中实现,而在CGraph中写了一个方法
OnDraw(CDC *pDC),它对应…View类中几乎同样的功能,而在…View类的OnLButtonUP()函数中通过CObArray类型的成员方法Add增加了一个CObject的指针。
现在,我们要做的是:在…Doc类中将我们保存在CObArray集合类中的绘图信息保存到文件中;那么,首先,我们要在…Doc类中获得视类的指针,我们有一个方法
GetFirstViewPosition()先获得其位置,然后利用GetNextView()来获得其视类指针
MFC文件框架内部实现机制和串行化操作: 文档与串行化
另一种文件操作方式——>用CArchive这个类来,其"<<"和">>"这个符号用于写出和读入,注意,写出的顺序是怎样的,那么读入的顺序也是一样,其使用,具体参照MSDN来
注意,对于我们的文件保存和文件打开选项,它对应的CWinApp类的OnFileNew()和OnFileOpen()函数,而且对于单文档用户程序来说,它的pDocument对象只与一个文件相关联。
下面我们要用CArchive类和Serialize()函数来实现文档的保存和其它操作:
要按照MSDN提示,可见CArchive类的MSDN说明文档的下部分链接文档。我们可以拷贝资源
这里完成一个绘图的程序:
它增加了一个CGraph类,这个类由CObject派生出来,目的是为了串行化。那么,同时在菜单中增加了绘制几个图形的命令,在…View类中实现,而在CGraph中写了一个方法
OnDraw(CDC *pDC),它对应…View类中几乎同样的功能,而在…View类的OnLButtonUP()函数中通过CObArray类型的成员方法Add增加了一个CObject的指针。
现在,我们要做的是:在…Doc类中将我们保存在CObArray集合类中的绘图信息保存到文件中;那么,首先,我们要在…Doc类中获得视类的指针,我们有一个方法
GetFirstViewPosition()先获得其位置,然后利用GetNextView()来获得其视类指针;而要在视类中获得文档类的指针,我们可以利用其本身有的成员函数GetDocument()方法
★★在我们保存可串行化的数据对象时,实际上是利用我们对象本身的Serialize()函数本身去实现★★
线程同异步及套接字:
事件内核对象、关键代码段(临界区)的讲解,以及在多线程同步中的应用。在Windows下编写基于消息的网络应用程序,掌握阻塞与非阻塞网络程序的编写,
理解在Windows平台下,采用★★异步选择机制可以提高网络应用程序的性能。★★
事件对象:
创建一个事件对象,我们要用到一个函数CreateEvent()函数
下面我们用事件对象来创建线程同步应用程序。
创建事件对象,然后在内部循环中,调用事件对象。
注意:人工重置的事件对象,它一旦有信号后,所有等待该事件对象的线程都可运行,而自动重置的事件对象,它只能供一个线程使用,且一旦一个线程
等待到它时,操作系统内部自动将其设为非信号状态。人工重置的事件对象需要显示调用Reset()函数,才将事件对象设为非信号状态。
通过创建一个命名的事件对象,我们可以完成让一个应用程序一次只能有一个实例运行这样的功能。
接下来,我们来看另一种同步方式:————》使用关键代码段
关键代码段工作在用户方式下,它是可运行的一个小段代码,它必须一次性独立完整地执行完。
我们要想进入关键代码段,我们需要调用一个函数EnterCriticalSection()。我们的临界区对象它是由系统内部自动回复的。当我们访问完系统资源以后,它需要
离开临界区对象,我们又用到一个函数LeaveCriticalSection()。当我们这个公用电话亭(临界区对象)不需要再存在时,我们就删除它,用到函数DeleteCriticalSection()
函数。★★注意:删除临界区对象是在所有程序主线程最后退出之前,否则,在多线程程序中会出现内存不能为written的BUG★★
所以,我们在做多线程程序时,一定要避免线程间死锁的发生。
互斥对象和事件对象属于内核对象,利用内核对象进行线程同步,速度较慢,但利用互斥对象和事件对象这样的内核对象,可以在多个进程中的各个线程间进行同步。
关键代码段是工作在用户方式下,同步速度较快,但在使用关键代码段时,很容易进入死锁状态,因为在等待进入关键代码段时无法设定超时值。
我们在MFC编程时,可以在类的构造函数中调用InitializeCriticalSection()函数,在其析构函数中调用DeleteCriticalSection()函数。但是,当有多个共有资源时,我们
要防止发生死锁。
Windows套接字在两种模式下执行I/O操作,阻塞和非阻塞。在阻塞模式下,在I/O操作完成前,执行操作的Winsock函数会一直等待下去,不会立即返回程序(将控制权交还给程序)。而在非阻塞模式下,Winsock函数无论如何都会立即返回。
Windows Sockets为了支持Windows消息驱动机制,使应用程序开发者能够方便地处理网络通信,它对网络事件采用了基于消息的异步存取策略。
Windows Sockets的异步选择函数WSAAsyncSelect()提供了消息机制的网络事件选择,当使用它登记的网络事件发生时,Windows应用程序相应的窗口函数
将收到一个消息,消息中指示了发生的网络事件,以及与事件相关的一些信息。也就是说,我们可以登记多种事件,当登记的事件发生时,我们可以发送消息,然后系统进行处理。
int WSAEnumProtocols( LPINT lpiProtocols, LPWSAPROTOCOL_INFO lpProtocolBuffer, ILPDWORD lpdwBufferLength );
Win32平台支持多种不同的网络协议,采用Winsock2,就可以编写可直接使用任何一种协议的网络应用程序了。通过WSAEnumProtocols函数可以获得系统中安装的网络
协议的相关信息。lpiProtocols,一个以NULL结尾的协议标识号数组。这个参数是可选的,如果lpiProtocols为NULL,则返回所有可用协议的信息,否则,只返回数组
中列出的协议信息。lpProtocolBuffer,[out],一个用WSAPROTOCOL_INFO结构体填充的缓冲区。 WSAPROTOCOL_INFO结构体用来存放或得到一个指定协议的完整信息。
lpdwBufferLength,[in,out],在输入时,指定传递给WSAEnumProtocols()函数的lpProtocolBuffer缓冲区的长度;在输出时,存有获取所有请求信息需
传递给WSAEnumProtocols()函数的最小缓冲区长度。这个函数不能重复调用,传入的缓冲区必须足够大以便能存放所有的元素。这个规定降低了该函数的
复杂度,并且由于一个 机器上装载的协议数目往往是很少的,所以并不会产生问题。
接下来,我们采用异步套接字编写一个网络聊天室程序:
首先,我们要加载套接字库,但是,采用AfxSocketInit()函数,我们加载的1.1版本的的套接字库,而在我们这里,要用到WinSock2版本的的套接字,因此,我们还是调用
WSAStartup()这个函数。我们将其放到CWinApp的Initinstance()这个函数中。同时,我们要加载库文件#include<Winsock2.h>,和库Ws2_32.lib这个库。
然后我们给应用程序类加一个析构函数,用来调用WSACleanup()函数。然后对对话框类加一个套接字成员变量。接下来,按部就班,绑定套接字,绑定之后,就可以
调用WSAAsyncSelect()函数了,接收数据,发送数据。
★★在套接字库中,对增加的函数,函数名前面都是以WSA开头★★
什么叫重叠套接字??异步选择机制??
将主机名转换成IP地址,我们可以用一个函数gethostbyname()函数。反之,则有另一个函数,叫gethostbyaddress()函数。
多线程与聊天程序:
多线程的编写与对象的互斥:
一、互斥命名对象的概念与创建
多线程运行在程序进程的空间中,每个线程占用CPU时间片,OS以某种方式能安排时间片给不同的线程使用。
采用多线程的好处是:线程占用的资源较少,同时,进程与进程之间切换花费的时间长,代价大。
创建一个线程,首先,我们要用Windows操作系统提供的API函数,所以,我们要包含头文件#include<windows.h>
其次,按顺序,我们先要用到函数CreateThread()这个函数,完成后,我们要调用CloseHandle()来完成。注意,我们
调用CloseHandle()并没有终止线程,只是关闭了句柄,同时,让这个线程的内核引用计数递减。显而易见,当我们的主线程
退出时,从线程也就不可能再运行了。当然,这里肯定有BUG,为此,要使线程同步,也即要使一个线程在操作共有资源时,必须其全部过程完成之后,另一个线程
才能操作共有资源 ★为了调试多线程的程序,我们可以在对共有资源操作时,可以调用Sleep()函数,进行调试来发现错误★
我们要做线程的同步,要用到一个函数,CreateMutex()函数。
★★★互斥对象(mutext)属于内核对象,它能确保线程拥有对单个资源的互斥访问。
互斥对象包含一个使用数量、一个线程ID和一个计数器。
那么,在线程中,我们就要去请求这个互斥对象,要用到WaitForSingleObject()函数,而释放互斥对象,我们又要用到一个函数。
这个互斥对象是谁拥有,谁去释放。★★★
正因为互斥对象有线程相关的这个特性,我们才能用它来作为“钥匙”,来作线程同步用。
★★所有与窗口有关的类,都有一个成员m_hWnd,这是一个与窗口相关的句柄★★
下面,我们用多线程来创建一个网络聊天室程序:
当然,我们肯定是基于套接字的,为此,要加载并协商套接字,我们在MFC中用到的函数是AfxSocketInit()函数,而用到个函数,我们要加载
<AfxSock.h>头文件,AfxSocketInit()函数是要放在CWinApp的InitInstance()函数中。
当然,注意,在调用CreateThred()函数时,线程运行时,第三个参数需要知道已经声明的函数和其实现细节,但是,在VC++编程中,要知道其实现细节,则需要已经创建了对象。解决的话,可以将作为其成员函数时,将其作为静态成员函数。
为什么采用多线程? 一般的,我们的接收窗口只有在有数据时才会有所响应,否则,将一直暂停,显然,这在聊天室程序中不行,因此必须采用多线程。
怎样进入新线程然后又发回到主线程?
网络编程:
网络的相关知识,网络程序的编写,Socket连接应用程序并bind()网络驱动程序:
网络及通信——>通信双方与规则(协议),还要用端口号标识是哪个命令终端(也即网络应用程序);
封装就是在数据的前面加上特定的协议头部。
运输层提供使运行中的进程进行通信的能力。为了标识通信的进程TCP/IP提出了协议端口的概念。端口是一种抽象的软件结构。
socket(套接字)是网络应用程序访问通信协议的操作系统调用。存在于通信域中。在网络协议中需要确定网络字节的顺序。
套接字的类型:
1、流式套接字(SOCK_STREAM),提供面向连接的、可靠的、无重复的、按顺序接收的通信
2、数据报式套接字(SOCK_DGRAM),提供无连接式服务。
3、原始套接字(SOCK_RAW)。
△▲客户端/服务器模式▲△
在TCP/IP网络应用中,通信的两个进程间相互作用的主要模式是客户机/服务器模式(client/server),即客户向服务器提出请求,服务器接收到请求后,提供相应的服务。
客户机/服务器模式的建立基于以下两点:首先,建立网络的起因是网络中软硬件资源、运算能力和信息不均等,需要共享,从而造就拥有众多资源的主机提供服务,资源
较少的客户请求服务这一非对等作用。其次,网间进程通信完全是异步的,相互通信的进程间既不存在父子关系,又不共享内存缓冲区,因此需要一种机制为希望通信的
进程间建立联系,为二者的数据交换提供同步,这就是基于客户机/服务器模式的TCP/IP。
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
★★基于TCP的网络应用程序socket编程(面向连接的)★★
★服务器端程序:
1、创建套接字(socket)。
2、将套接字绑定到一个本地地址和端口上(bind)。
3、将套接字设为监听模式,准备接收客户请求(listen)。
4、等待客户请求到来;当请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept)。
5、用返回的套接字和客户端进行通信(send/recv)。
6、返回,等待另一客户请求。
7、关闭套接字。
★客户端程序:
1、创建套接字(socket)。
2、向服务器发出连接请求(connect)。
3、和服务器端进行通信(send/recv)。
4、关闭套接字。
★★基于UDP的网络应用程序socket编程(面向无连接的)★★
★服务器端(接收端)程序:
1、创建套接字(socket)。
2、将套接字绑定到一个本地地址和端口上(bind)。
3、等待接收数据(recvfrom)。
4、关闭套接字。
★客户端(发送端)程序:
1、创建套接字(socket)。
2、向服务器发送数据(sendto)。
3、关闭套接字。
下面我们来编写命令终端式的面向连接的应用程序
要建立这类应用程序,首先是我们要加载Socket库,这里竟不是用预处理命令,而是用到一个平台函数WSAStartup()函数;
然后根据上面函数的MSDN参考资料,我们完成之后,接下来要创建套接字,我们用到函数socket();
创建套接字成功后,我们需要将其绑定到本地的端口上,要用到bind()函数。注意除了safamily这个参数以外,其它的都要转化为网络字节序,为此,我们要用到htol()函数和
htons()函数
接下来我们应该调用一个listen()函数来将我们的套接字设置为监听模式,然后做一个死循环,不断等待连接的到来,这即要调用一个函数,等待客户的连接到来,此函数即为
accept()函数。接下来,在建立连接之后,我们可以进行通信了,我们可以用send()函数来发送数据。与此同时,我们也可以接收一个数据,用到方法recv()。接收完数据后,
我们需要closesocket()方法去关闭socket套接字。释放为套接字所分配的资源。
当然,在这个程序当中,因为我们使用了套接字的函数,我们需要包含Winsock.h头文件,同时还要链接一个库文件Ws2__32.lib
接下来我们去编写客户端程序:
当然前半部分我们与服务器端程序差不多,不过,我们不用bind()SOCKET套接字,而是直接用connect()函数去连接。
当我们连接之后,就可接收服务器端发送过来的数据recv(),当然,我们也可以向服务器端发送数据send(),同样,当我们通信完成之后,我们也要释放套接字closesocket(),
最后,我们也需要终止对套接字库的使用,我们用WSACleanup();
下面我们来编写命令终端式的面向无连接的应用程序
对于服务器端:
同样,顺序是先加载套接字库,然后,建立套接字socket(),并与本地地址绑定bind(),此后,这里与面向连接的不同,不再需要设置为什么监听模式,而是直接用recvfrom()
函数来开始获取信息。
对于客户端:
它加载了套接字后,建立了套接字socket()后,同样不需要绑定,而是直接可以发送各接受消息。发送消息要调用的是sendto()函数。
这个客户端设置的端口为服务器端设置的接收消息的端口。
下面我们完成一个基于字符界面的聊天程序:
对于聊天程序,我们常用基于面向无连接的的程序,采用UDP,实时性好。注意,要从键盘获取输入信息,我们有一个函数gets(),它可以从标准I/O获得输入
钩子程序与数据库编程:
Hook编程。如何安装钩子过程,如何编写全局钩子,动态连接库里的全局变量数据共享问题分析。ADO数据库编程。在VB中利用ADO控件和ADO对象访问数据库,在VC中利用ADO技术访问数据库。
Hook编程:两种钩子——》一种是进程内钩子,一种是全局钩子。
什么叫Hook程序,即钩子程序?由我们的Windows消息驱动机制,我们的应用程序由于用户的操作,产生信号,OS将对应的消息放到消息队列,并在一定时间,将消息取出,交由我们的应用程序按照消息对应的过程进行处理。那么,如果我们想在于这个过程中截取消息并作我们特殊的处理,那么,实现我们这个目的过程即是钩子程序。
安装钩子,我们有对应的函数,叫SetWindowsHookEx(),其具体要求做哪些准备,我们可以从MSDN其函数本身说明中可知。
那么,有可能我们涉及到要建库,我们有一个建立动态链接库的需要,而在这个库中,我们要得到DLL库的实例句柄的话,我们可以可以通过DLLMain()函数的参数得到,另外,我们也可用方法GetModuleHandle()。
★★原来,动态链接库它也有一个入口,即DLLMain()函数。★★
我们可以自建一个动态链接库,然后,在其同样的文件夹下,建一个模块定义文件.def.注意,我们导出的文件有一个序号。
动态链接库不能独立运行,我们必须写一个程序,然后加载这个库。
★★动态链接库的调试:★★
比如说,我们要查看SetHook()(该函数是放在动态链接库中),我们可以在该函数处设置断点。然后我们在调试时可以选择Debug\Step Into(F11键)
★★我们如果想在一个函数中获得主窗口的句柄的话,我们除了可以在函数中想到用什么函数外,我们也可以将主窗口的句柄通过函数参数传过来★★
★★动态链接库中,对于我们的数据,Windows 2000采用的是写入式拷贝,因此,如果我们想使所有进程都共享数据,我们须在动态库自己建一个结,将数据写在结中,用到一个#pragma data_seg("输入名字"),并再用一个指令#pragma data_seg()作为结尾,将我们想共享的数据放在这两个代码的中间,注意,这个数据必须赋初值;然后将结设置为共享。我们又用到另一个#pragma comment(linker,"/section:上面输入的名字,RWS")★★
★★除了用以上方式呢,我们也可以在模块定义文件中写上SEGMENTS ┓结名 其权限,如READ WRITE SHARED★★
★★我们可以用钩子来获取密码等重要信息,这是它本身经常的角色。★★
★★★★★★★★★★★★★★★★★★★★★★★数据库的编程★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
不同的数据库厂商,会在其提供数据产品时,也要给程序员们提供访问其数据库的接口,即API,那么,当我们要编写一个数据库时,我们需要学习这些新的API
微软数据访问技术有:
ODBC(开放数据互连),可以访问各种支持ODBC的关系型数据库
DAO(数据访问对象)——基于Microsoft Access/Jet引擎,主要是用于VB开发面向Access数据库的
RDO(远程数据访问对象)其实也是基于ODBC
OLE DB(对象链接与嵌入数据库),对ODBC进行了扩展,可以访问关系型数据库及非关系型。OLE DB程序的基本结构是:OLE DB提供程序和用户程序
ADO(Activex Data Object),它建立在OLE DB之上,是OLE DB用户程序。提供了对自动化的支持,连一些脚本语言也可以用其来访问数据库
下面,我们来编写ADO数据库应用程序
ADO有三个核心对象:
一、Connection对象:用于管理用户程序与数据库通信,表示了到数据库的链接。
二、Command对象:用来处理重复执行的查询
三、Recordset对象:用来获取数据库中的数据
注意,在编写数据库应用程序时,在显示时,一定不能忘了移动游标,否则,是一个死循环了。★★我们要记住,对数据库的访问,总是按行进行的。★★
★VB中,若是在组件旁右击,想添加组件,则我们可以右击选择组件;当然,若是直接想在编码中用数据库相应对象,则我们在Projects/下选择的是Reference★
★★在VC中,我们要用到ADO去访问数据库,我们首先要导入一个库msado15.dll,我们可以在StdAfx.h这个预编译头文件中导入,用到#import 完整路径名 no_namespace★★
★★为避免命名误会,我们可以将用rename将一个名字改为另一个名字。因为我们说过,OLE DB是基于COM组件的,因此,如果我们要用ADO访问控件时,我们要加载COM★★