《VC++深入详解–学习笔记》(15)多线程

Filed Under (VC++ 学习笔记) by panmaoru on 20-12-2009

本章主要介绍多线程程序的编写,并利用多线程技术创建一个网络聊天室。

15.1基本概念

程序是计算机指令的集合,它以文件形式存储在磁盘上。

进程被定义为一个正在运行的程序的实例,是一个程序在其自身的地址空间中的一次执行活动。

进程的组成:操作系统用来管理进程的内核对象和地址空间。进程不执行任何东西,它只是程序的实例,线程的容器或执行环境。创建一个进程之后,操作系统会自动为这个进程创建一个主线程(执行main或者winmain函数的线程)。

线程是进程中的一个实体,是被系统独立调度和分派的基本单位。

线程的组成:线程的内核对象(操作系统用来存放线程统计信息)和线程栈(维护线程在执行代码时候所需要的所有的函数和局部变量)。一个线程之后一个内核对象和一个栈。

15.2线程创建函数

可使用系统提供的API函数CreateThread来创建一个线程。

该函数的原形如下:

HANDLE CreateThread(

  LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD

  DWORD dwStackSize,                        // initial stack size

  LPTHREAD_START_ROUTINE lpStartAddress,    // thread function

  LPVOID lpParameter,                       // thread argument

  DWORD dwCreationFlags,                    // creation option

  LPDWORD lpThreadId                        // thread identifier

);

根据CreateThread函数的参数说明,可以基本了解创建一个线程的规则。

15.3简单的多线程示例

#include <windows.h>

#include <iostream.h>

DWORD WINAPI Function1(LPVOID lpParamete);//申明过程函数

void main()

{

       HANDLE hThreadl;

       hThreadl=CreateThread(NULL,0,Function1,NULL,0,NULL);

       CloseHandle(hThreadl);

       cout<<"main thread is running"<<endl;

       Sleep(10);//主线程执行完毕后,休眠10毫秒,启动子线程

}

DWORD WINAPI Function1(LPVOID lpParamete)

{

       cout<<"thread1 is running"<<endl;

       return 0;

}

在创建一个线程的时候需要指定过程函数,因此需要在main函数之前申明Function1,也可以将Function1函数的实现放到main函数之前,这样就不需要声明了。

15.4线程同步

操作系统为每一个运行线程安排一定的CPU时间—时间片。在一个多线程的程序中,系统通过一种循环的方式为线程提供时间片,线程在自己的时间片內运行。这就可以会出现一种情况:当多个线性同时对一个全局的对象进行访问时,一个线程已经进入了循环体,这时候时间片结束,该线程进入睡眠状态,系统转向执行另外一个线程。而当另外一个线程执行的时候对全局变量做了修改。当返回到第一个线程的时候,接着从上次时间片结束的地方执行,这时候会出现一些异常。

DWORD WINAPI Function1(LPVOID lpParamete)

{

       while(true) {

              if(count>0) {

                     //当线程执行到此的时候,时间片到了,线程1Sleep,开始执行线程2,当线性2完毕之后再回到线程1,接着从此处执行。这时候有可能线程count的值已经为非正常值,如:0。

                     cout<<"thread1 sell the ticket:"<<count--<<endl;

              }

              else

                     break;

       }

       return 0;

}

DWORD WINAPI Function2(LPVOID lpParamete)

{

       while(true) {

              if(count>0)

              {cout<<"thread2 sell the ticket:"<<count--<<endl; }

              else

                     break;

       }

       return 0;

}

15.4.1互斥对象实现线程同步

互斥对象mutex属于内核对象,它能够确保线程对当资源的互斥访问权。创建互斥对象的CreateMutex函数的声明如下:

HANDLE CreateMutex(

  LPSECURITY_ATTRIBUTES lpMutexAttributes,  // 互斥对象安全性,默认NULL

  BOOL bInitialOwner,                       // 指定互斥对象的拥有者,默认FALSE

  LPCTSTR lpName                            // 指定互斥对象的名称,默认NULL

);

《VC++深入详解–学习笔记》(14)网络编程

Filed Under (VC++ 学习笔记) by panmaoru on 18-12-2009

网络程序的实现可以有多种方式,Windows Socket就是其中的一种比较简单的实现方式。Socket是连接应用程序和网络驱动程序的桥梁,Socket在应用程序中创建,通过绑定操作和应用程序建立连接关系。

14.1计算机网络的基本知识

计算机网络是相互连接的独立自主的计算机的集合,最初建立的目的是实现资源的共享。其中比较重要的概念是协议,其目的是为了进行网络中的数据交换而建立的规则、标准或约定。不同的层有不同的协议。

14.1.1ISO/OSI七层参考模型

OSI(Open System Interconnection)参考模型将网络的不同功能分成了七层。由高到底的分层及其功能分别如下:

  1. 应用层:为用户的网络应用程序提供网络通信服务;
  2. 表示层:处理被传输数据的表示问题,即信息的语法和语义;
  3. 会话层:在两个相互通信的应用程序之间建立连接,组织和协调之间的通信;
  4. 传输层:为源短主机和目的端主机提供可靠的传输服务,隔离网络的上下层协议,使得网络和上下层协议无关;
  5. 网络层:提供IP寻址和路由,负责找到最佳的数据传输路线;
  6. 数据链路层:提供介质访问,加强物理层的传输功能;
  7. 物理层:提供二进制传输,确定在通信信道上如何传输比特流。

七层参考模型知识一个功能上的划分,不是物理上的划分,各层之间是严格的单向依赖,通信实体的对等层之间不容许出现直接通信,下次向上层提供服务,上层使用下层提供的服务。

14.1.2数据封装

一台计算机向另外一台计算机发送数据,必须将该数据打包,打包的过程被称为数据封装。由应用层向下,逐步为数据进行封装,在头部或尾部或同时添加本层的头部或尾部。当数据包到达目标计算机时,再从物理层开始,逐步去除头部或尾部,完成拆分。

14.1.3TCP/IP模型

OSI的七层参考模型比较复杂,通常采用TCP/IP模型,该模型有四个层次与OSI七层模型的对应关系为:

  1. 应用层:对应应用层+表示层+会话层;
  2. 传输层:对应传输层;
  3. 网络层:对应网络层;
  4. 网络接口层:对应数据链路层+物理层。

14.1.4端口

为了标识通信实体中进行通信的进程,TCP/IP提供了协议端口的概念。端口是一种抽象的软解结构,应用程序通过系统调用和某端口建立连接后,传输层传给该端口的数据都会被相应得应用程序接收,相依进程发送给传输层的数据也通过该端口输出。(端口存在与传输层?)

端口的范围是0~65355,1024以下的端口号通常都被系统应用程序占用。如http的80,ftp的21.

14.1.9套接字Socket的引入

Socket最早出现在UNIX系统中,后来被引入Windows。Windows Socket只支持一个通信区域:网际域,这个域被使用网际协议簇通信的进程使用。

14.2Windows Socket的实现

套接字分为三种类型,其中主要使用的由2种:

流式套接字SOCK_STREAM:提供面向连接,可靠的传输服务,数据无差错,无重复发送,按发送顺序接收,流式套接字是基于TCP协议实现的;

数据报式套接字SOCK_DGRAM:提供无连接服务,数据包独立发送,基于UDP协议;

14.2.1基于TCPsocket编程

基于TCP的socket编程的服务器端的流程如下:

  1. 创建套接字socket;
  2. 将套接字绑定到一个本地的端口上bind;
  3. 将套接字设置为监听模式,准备接收客户端请求listen;
  4. 等待客户端请求,当请求到达后,接受连接要求,返回一个新的对应于此连接的套接字accept;
  5. 用返回套接字和客户端进行通信send/recv;
  6. 返回,等待另一个请求;
  7. 关闭套接字。

示例代码如下:

#include <Winsock2.h>
#include <stdio.h>
void main()
{
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	wVersionRequested = MAKEWORD( 2, 2 );
	err = WSAStartup( wVersionRequested, &wsaData );
	if ( err != 0 ) {
		return;
	}
	if ( LOBYTE( wsaData.wVersion ) != 2 ||
        HIBYTE( wsaData.wVersion ) != 2 ) {
		WSACleanup( );
		return;
	}
	//create socket
	SOCKET socksrv=socket(AF_INET,SOCK_STREAM,0);
	SOCKADDR_IN addsrcv;
	addsrcv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
	addsrcv.sin_family=AF_INET;
	addsrcv.sin_port=htons(6000);

	//bind
	bind(socksrv,(SOCKADDR*)&addsrcv,sizeof(SOCKADDR));
	//listen
	listen(socksrv,5);

	SOCKADDR_IN addrClient;
	int len=sizeof(SOCKADDR);

	while(1)
	{
		//wait for client
		SOCKET sockConn=accept(socksrv,(SOCKADDR*)&addrClient,&len);
		char buf[100];
		sprintf(buf,"welcome %s to www.colsir.com",inet_ntoa(addrClient.sin_addr));
		//send
		send(sockConn,buf,strlen(buf)+1,0);
		char recvbuf[100];
		recv(sockConn,recvbuf,100,0);
		printf("%s\n",recvbuf);
		closesocket(sockConn);
	}
}

基于TCP的socket编程的客户端端的流程如下:

  1. 创建套接字 socket;
  2. 向服务器发送请求connect;
  3. 和服务器端进行通信send/recv;
  4. 关闭套接字。

示例代码如下:

#include <Winsock2.h>
#include <stdio.h>
void main()
{
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	wVersionRequested = MAKEWORD( 2, 2 );
	err = WSAStartup( wVersionRequested, &wsaData );
	if ( err != 0 ) {
		return;
	}
	if ( LOBYTE( wsaData.wVersion ) != 2 ||
        HIBYTE( wsaData.wVersion ) != 2 ) {
		WSACleanup( );
		return;
	}
	//create socket
	SOCKET sockclient=socket(AF_INET,SOCK_STREAM,0);
	SOCKADDR_IN addsrcv;
	addsrcv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
	addsrcv.sin_family=AF_INET;
	addsrcv.sin_port=htons(6000);

	//connect
	connect(sockclient,(SOCKADDR*)&addsrcv,sizeof(SOCKADDR));

	//recv
	char recvbuf[100];
	recv(sockclient,recvbuf,100,0);
	printf("%s\n",recvbuf);
	//send
	send(sockclient,"send message",strlen("send message")+1,0);
	closesocket(sockclient);
	WSACleanup();
}

14.2.2基于UDPsocket编程

基于UDP的socket编程的服务器端的流程如下:

  1. 创建套接字socket;
  2. 将套接字绑定到一个本地地址和端口上bind;
  3. 等待接收数据,接收完毕之后可以选择返回,等待再一次接收recvfrom;
  4. 关闭套接字。

示例代码如下:

#include <Winsock2.h>
#include <stdio.h>
void main()
{
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	wVersionRequested = MAKEWORD( 2, 2 );
	err = WSAStartup( wVersionRequested, &wsaData );
	if ( err != 0 ) {
		return;
	}
	if ( LOBYTE( wsaData.wVersion ) != 2 ||
        HIBYTE( wsaData.wVersion ) != 2 ) {
		WSACleanup( );
		return;
	}
	//create socket
	SOCKET sockSrv=socket(AF_INET,SOCK_DGRAM/*UDP*/,0);
	SOCKADDR_IN addsrcv;
	addsrcv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
	addsrcv.sin_family=AF_INET;
	addsrcv.sin_port=htons(6000);

	//bind
	bind(sockSrv,(SOCKADDR*)&addsrcv,sizeof(SOCKADDR));

	//recv
	while(1)
	{
		SOCKADDR_IN addServer;
		int len=sizeof(SOCKADDR);
		char recvbuf[100];
		recvfrom(sockSrv,recvbuf,100,0,(SOCKADDR*)&addServer,&len);
		printf("%s\n",recvbuf);
	}

	closesocket(sockSrv);
	WSACleanup();
}

基于UDP的socket编程的客户端端的流程如下:

  1. 创建套接字 socket;
  2. 向服务器发送数据sendto;
  3. 关闭套接字。

示例代码如下:

#include <Winsock2.h>
#include <stdio.h>
void main()
{
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	wVersionRequested = MAKEWORD( 2, 2 );
	err = WSAStartup( wVersionRequested, &wsaData );
	if ( err != 0 ) {
		return;
	}
	if ( LOBYTE( wsaData.wVersion ) != 2 ||
        HIBYTE( wsaData.wVersion ) != 2 ) {
		WSACleanup( );
		return;
	}
	//create socket
	SOCKET sockclient=socket(AF_INET,SOCK_DGRAM,0);
	SOCKADDR_IN addsrcv;
	addsrcv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
	addsrcv.sin_family=AF_INET;
	addsrcv.sin_port=htons(6000);

	sendto(sockclient,"hello",strlen("hello")+1,0,(SOCKADDR*)&addsrcv,sizeof(SOCKADDR));

	closesocket(sockclient);
	WSACleanup();
}

14.3小结

看完了书本上的介绍,把代码照着书本抄了一遍,对于socket编程有了一个初步的认识。真正完成能实用的应用程序要有很远的路。过程中遇到的问题做个简单的小结:

  1. 因为本程序使用了winsock库中的函数,按照动态链接库的使用规则,需要为程序链接相应的lib文件:ws2_32.lib;
  2. 在创建TCP和UDP套接字的时候需要注意类型的区别,二者分别对应:SOCKET sockSrv=socket(AF_INET,SOCK_STREAM/*TCP*/,0);和SOCKET sockSrv=socket(AF_INET,SOCK_DGRAM/*UDP*/,0);
  3. 换行符为”\n”,经常不小心就给写成了”/n”

《VC++深入详解–学习笔记》(13)文档与串行化

Filed Under (VC++ 学习笔记) by panmaoru on 17-12-2009

本章首先介绍了一种新的读写文件方式,使用MFC提供的CArchive类来实现。可以利用CArchive类将对象数据保存到永久设备上。这样即使应用程序关闭,再次启动后仍然可以从磁盘上读取对象数据,然后在内存中重构相应的对象。让数据持久性的过程成为串行化或序列化。

CArchive对象不仅可以处理基本类型的数据,还可以处理CObject类的派生对象。CArchive类重载了提取(>>)和插入(<<)操作符。通过这些重载的函数,可以利用CArchive对象完成对文件的读写操作。

CArchive构造函数的声明如下:

CArchive( CFile* pFile, UINT nMode, int nBufSize = 4096, void* lpBuf = NULL );

pFile:指向文件对象的指针,该文件对象是持久数据的来源或目的地;

nMode:文件对象的标示符(load,store)读取还是存储;

CArchive类写入数据的实例:

void CFileView::OnFileWrite()
{
       CFile file("7.txt",CFile::modeCreate|CFile::modeWrite);
       CArchive ar(&file,CArchive::store);
       int i=1;
       char ch='1';
       float f=1.3f;
       CString str="www.colsir.com";
       ar<<i<<ch<<f<<str;
}

完成写入操作之后,用记事本打开7.txt,发现基本上都是乱码。这是因为CArchive对象是一种二进制流。

CArchive类读取数据的实例:

void CFileView::OnFileRead()
{
       CFile file("7.txt",CFile::modeRead);
       CArchive ar(&file,CArchive::load);
       int i;
       char ch;
       float f;
       CString str;
       CString strResult;
       ar>>i>>ch>>f>>str;
       strResult.Format("%d,%c,%f,%s",i,ch,f,str);
       MessageBox(strResult);
}

对象读取的顺序必须和保存的顺序一致,有些搞不懂,那CArchive类还有什么优势?暂时理解不了。

MFC新建,保存文件时的格式过滤

在字符串表中一个ID为IDR_MAINFRAME的行,用来实现一些初始值的设置。该项的值的优先级低于OnNewDocument函数中的设置。通过查看OnNewDocument函数的申明,可以看到OnNewDocument函数是一个虚函数。

文档串行化

Serialize?看了半天没看懂什么意思。这两天心情有点浮躁,先平静下再补充。

《VC++深入详解–学习笔记》(12)文件和注册表操作

Filed Under (VC++ 学习笔记) by panmaoru on 16-12-2009

很多与文件操作相关的函数其参数类型都是const char*,本章首先介绍指向常量的指针const char*和指针常量char *const这两种类型的区别。

12.1char const * 和char * const

char const * p;
const char * p;
char * const p;

这三个有什么不同?const char * p;和char const * p;在实现上都是一样的,定义一个指向常量的指针,由于指针指向的是一个常量,常量的值不能改变,但是可以改变指针的指向。char * const p;这是一个指针常量,也就是指针的指向不能改变,但是可以改变指针指向的值。区别指针常量和常量指针的关键看const 和* 的位置。

12.2C语言对文件操作的支持

C语言中,对文件的操作是利用FILE结构体实现的。

12.2.1文件的打开

MSDN中有一个fopen函数:

FILE *fopen( const char *filename, const char *mode );

其中的第一个参数是要打开的文件名称,第二个参数是打开的模式。MSDN中对打开文件的模式进行了如下的定义:

r 为读取而打开,如果文件不存在或者不能找到,函数调用失败
w 为写入文件而打开一个空文件,如果文件已经存在,怎清空内容
a 为写入打开一个文件,如果文件存在,则在尾部添加新数据,在写入之前,不会清除文件中已经有的EOF标记,如果文件不存在,新建一个文件
r+ 打开文档用户写入操作和读取操作,文件必须存在
w+ 为写入操作和读取操作打开一个空的文件,如果给定的文件已经存在,则清空内容
a+ 打开文件用于读取和添加操作,在写入之前移除文件已有的EOF标记,写入之后再恢复EOF标记,如果指定文件不存在,那么先创建这个文件

打开一个文件的实例代码如下:

FILE *pFile=fopen(“1.txt”,”w”);//该操作返回一个FILE类型的指针

12.2.2文件的写入

size_t fwrite( const void *buffer, size_t size, size_t count, FILE *stream );

文件写入有四个参数,分别表示:要写入的数据,以字节为单位的项的大小,写入项的最大数目,指向FILE类型的指针,该参数可以由fopen函数获取到,也就是fopen函数的返回值。写入文件的实例代码如下:

fwrite("new buffer",1,strlen("new buffer"),pFile);

由于C语言对文件操作采用缓存文件系统,从内存从磁盘写入的文件首先会被放到内存缓冲区中,只有当内存缓冲区满了或者当写入操作的文件关闭之后,才把要写入的数据存的磁盘文件上。如果要在执行写操作之后立即将数据存放到磁盘文件上,可以先调用fflush函数刷新缓冲区或者调用fclose函数将打开的文件关闭。

12.2.3文件指针的定位

int fseek( FILE *stream, long offset, int origin );

该函数的第三个参数有三种默认的值,SEEK_CUR,SEEK_END,SEEK_SET,实际上是三个宏定义值,在STDIO.h文件中可以看到它们的值分别是1,2,0。分别表示定位到文件的当前位置,结束位置和开始位置。

fseek(pFile,0,SEEK_SET);
fwrite("haha",1,strlen("haha"),pFile);

将指针定位到已经打开的文件的其实位置,并写入新的数据。完成此操作时候会发现文件中起始位置的四个字符已经被替换掉了。尝试了下改变文件的打开模式,但是都不能实现a+b的效果。

12.2.3文件的读取

size_t fread( void *buffer, size_t size, size_t count, FILE *stream );

由于C语言中字符串是以”\0”符号结束的。在显示字符串的时候,系统会寻找”\0”字符,因此在写入数据的时候需要在结尾添加一个”\0”,在读取数据的时候,可以动态创建一个数据长度+1的字符数组,然后将最后一个元素设置为”\0”。

写入文件时:

void CFileView::OnFileRead()
{
       // TODO: Add your command handler code here
       FILE *pFile=fopen("1.txt","w");
       //fwrite("new buffer",1,strlen("new buffer"),pFile);
       char buf[22]="www.colsir.com";
       buf[21]='\0';
       fwrite(buf,1,22,pFile);
       fclose(pFile);
}

读取文件时:

void CFileView::OnFileWrite()
{
       // TODO: Add your command handler code here
       FILE *pFile=fopen("1.txt","r");
       char *pbuf;
       fseek(pFile,0,SEEK_END); //为了获取数据的长度,将指针指向文件的末尾
       int len=ftell(pFile);
       pbuf =new char[len+1];
       //rewind(pFile); //下同fseek(pFile,0,SEEK_SET);
       fseek(pFile,0,SEEK_SET); //读取数据前,需要将指针再指回起始位置
       fread(pbuf,1,len,pFile);
       pbuf [len]='\0';
       fclose(pFile);
       MessageBox(pbuf);
}

12.2.3二进制文件和文本文件

字太多,有点啰嗦,不写了,慢慢体会。

12.3C++对文件操作的支持

void CFileView::OnFileWrite()//写入
{
       ofstream ofs("3.txt");
       ofs.write("www.colsir.com",strlen("www.colsir.com"));
       ofs.close();
}
void CFileView::OnFileRead() //读取
{
       // TODO: Add your command handler code here
       ifstream ifs("3.txt");
       char buf[100];
       memset(buf,0,100);
       ifs.read(buf,100);
       ifs.close();
       MessageBox(buf);
}

12.4Win32 API对文件操作的支持

void CFileView::OnFileWrite()//写入
{
       HANDLE hFile;
       hFile=CreateFile("4.txt",GENERIC_WRITE,0,NULL,CREATE_NEW,FILE_ATTRIBUTE_NORMAL,NULL);
       DWORD dwWrites;
       WriteFile(hFile,"www.colsir.com",strlen("www.colsir.com"),&dwWrites,NULL);
       CloseHandle(hFile);
}
void CFileView::OnFileRead()//读取
{
       // TODO: Add your command handler code here
       HANDLE hFile;
       hFile=CreateFile("4.txt",GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
       char buf[100];
       DWORD dwWrites;
       ReadFile(hFile,buf,100,&dwWrites,NULL);
       buf[dwWrites]=0;
       CloseHandle(hFile);
       MessageBox(buf);
}

12.5MFC对文件操作的支持

MFC中提供的支持文件操作的基类是:CFile,该类提供了没有缓存的二进制格式的磁盘文件输入输出功能,通过其派生类能够间接的支持文本文件和内存文件。

利用MFC的CFile类实现文件写入操作:

void CFileView::OnFileWrite()
{
       CFile file("5.txt",CFile::modeCreate|CFile::modeWrite);
       file.Write("www.colsir.com",strlen("www.colsir.com"));
       file.Close();
}

利用MFC的CFile类实现文件读取操作:

void CFileView::OnFileRead()
{
       CFile file("5.txt",CFile::modeRead);
       char *pBuf;
       DWORD dwFileLen;
       dwFileLen=file.GetLength();
       pBuf=new char[dwFileLen+1];
       pBuf[dwFileLen]=0;
       file.Read(pBuf,dwFileLen);
       file.Close();
       MessageBox(pBuf);
}

12.5注册表操作

注册表存储在二进制文件中,Win32 API提供了大量的函数以便应用程序访问注册表。

1、  创建键:RegCreateKey

2、  打开键:RegOpenKey

3、  写入注册表:RegSetValue(默认REG_SZ类型),RegSetValueEx(其他类型)

4、  由注册表中读数据:RegQueryValue(默认REG_SZ类型),RegQueryValue Ex(其他类型)

void CFileView::OnRegWrite()
{
       HKEY hKey;
       RegCreateKey(HKEY_LOCAL_MACHINE,"SoftWare\\www.colsir.com\\admin",&hKey);
       RegSetValue(hKey,NULL,REG_SZ,"pan",strlen("pan"));
       RegCloseKey(hKey);
}
void CFileView::OnRegRead()
{
       LONG lValue;
       RegQueryValue(HKEY_LOCAL_MACHINE,"SoftWare\\www.colsir.com\\admin",NULL,&lValue);
       char *pBuf=new char[lValue];
       RegQueryValue(HKEY_LOCAL_MACHINE,"SoftWare\\www.colsir.com\\admin",pBuf,&lValue);
       MessageBox(pBuf);
}

《VC++深入详解–学习笔记》(11)绘图的保存与重绘

Filed Under (VC++ 学习笔记) by panmaoru on 16-12-2009

看了书本上的内容,太多啰嗦,没耐心看,先放过,等兴趣来了再整理吧。

《VC++深入详解–学习笔记》(10)绘图控制

Filed Under (VC++ 学习笔记) by panmaoru on 15-12-2009

在软件的运行过程中,用户可以根据软件提供的设置对话框,颜色对话框和字体对话框等用户接口来对软件进行制定。本章主要介绍对图形绘制的制定,此外,也介绍了如何获取对话框的返回值。

10.1获取一个对话框的返回值

创建一个Setting对话框,在View类中添加Commad消息响应函数。然后再Setting对话框类中添加一个public变量m_nWidth。
在View中添加一个变量m_nWidth1,然后就可以设置和获取对话框的变量值了。

//#include "SettingDlg.h"
void CGraphicView::OnSetting()
{
	// TODO: Add your command handler code here
	CSettingDlg dlg;
	dlg.m_nWidth=m_nWidth1;
	if(IDOK==dlg.DoModal())
	{
		m_nWidth1=dlg.m_nWidth;
	}
}

10.2获取颜色对话框的值

MFC为我们提供了一个颜色对话框类CColorDialog,可以在View类中直接创建该类对象。为了设置和获取颜色对话框的RGB默认值,需要在View类中建一个COLORREF类型变量。

void CGraphicView::OnColor()
{
	// TODO: Add your command handler code here
	CColorDialog dlg;
	dlg.m_cc.rgbResult=m_ccr;// m_cc为一个COLORREF变量
	dlg.m_cc.Flags|=CC_RGBINIT;
	if(IDOK==dlg.DoModal())
	{
		m_ccr=dlg.m_cc.rgbResult;
	}
}

10.3获取字体对话框的值

MFC同样为我们提供了一个字体对话框类CFontDialog,可以在View类中直接创建该类对象。为了获取字体对话框的值,需要在View类中建一个CFont类型变量。

void CGraphicView::OnFont()
{
	// TODO: Add your command handler code here
	CFontDialog dlg;
	if(IDOK==dlg.DoModal())
	{
		// m_font为一个CFont变量
		if(m_font.m_hObject)// 判断m_font是否已经和某个字体资源相关联了
			m_font.DeleteObject();//如果已经关联,删除关联资源
		m_font.CreateFontIndirect(dlg.m_cf.lpLogFont);
		m_strFontName=dlg.m_cf.lpLogFont->lfFaceName;
	}
}

10.4绘图

设置绘图的一些参数之后,便可以开始实现绘图。将设置的一些参数选择到画笔,并完成相应的图像绘制。

void CGraphicView::OnLButtonUp(UINT nFlags, CPoint point)
{
   CClientDC dc(this);
   CPen pen(m_nLineStyle,m_nWidth,m_ccr);
   dc.SelectObject(&pen);
   CBrush *pBrush=CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));//透明画刷
   dc.SelectObject(pBrush);
   //画点
   dc.SetPixel(point,m_ccr);
   //画线
   dc.MoveTo(m_pOrigin);
   dc.LineTo(point);
   //画矩形
   dc.Rectangle(CRect(m_pOrigin,point));
    //画椭圆
   dc.Ellipse(CRect(m_pOrigin,point));
}

《VC++深入详解–学习笔记》(9)定制应用程序外观

Filed Under (VC++ 学习笔记) by panmaoru on 14-12-2009

本章主要讲解如何修改MFC AppWizard自动生成的应用程序的外观,包括工具栏和状态栏的变成。

9.1在窗口创建之前修改

第三章介绍了一些MFC AppWizard应用程序的执行流程,如果要在窗口创建之前修改应用程序的外观,可以再MainFrame类的PreCreateWindow()函数中修改窗口的外观属性。

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=800;
cs.cy=500;
cs.style&=~FWS_ADDTOTITLE;
cs.lpszName="www";
return TRUE;
}

由于MFC应用程序的View类是覆盖在Frame类之上的,在此处对外观做的修改将只显示在Frame类部分。如窗口的大小,标题。

一个MFC单文档应用程序窗口的默认窗口样式是WS_OVERLAPPEDWINDOW和WS_ADDTOTITLE,要去掉其中的一种样式,可以对其做取反操作。

cs.style=cs.style&~WS_ADDTOTITLE;

或者直接设置窗口的样式

cs.style=WS_OVERLAPPEDWINDOW;

9.2在窗口创建之后修改

在窗口创建之后修改窗口的样式,在Create函数中执行。在Create函数中调用SetWindowLong函数。

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
//。。。
SetWindowLong(m_hWnd,GWL_STYLE,WS_OVERLAPPEDWINDOW);
SetClassLong(m_hWnd,GCL_HICON,(LONG)LoadIcon(NULL,IDI_ERROR));
}

9.3动态加载Icon

如果要设置窗口的Icon为系统提供的格式,可以通过SetClassLong来实现,如果要动态的加载一个自定义的Icon,首先需要在Frame类的头文件中创建一个HICON类型的变量。

HICON m_icon;可以同时加载多个文件,则需要创建的HICON数组HICON m_iconl[],

然后调用LoadIcon函数。

hicon[0]=LoadIcon(AfxGetInstanceHandle(),MAKEINTRESOURCE(IDI_ICON1));
hicon[1]=LoadIcon(AfxGetInstanceHandle(),MAKEINTRESOURCE(IDI_ICON2));

书中给出了三种加载icon文件的方法,选择一个自己看着顺眼的方式。加载Icon文件之后,设置窗口的icon:

SetClassLong(m_hWnd,GCL_HICON,(LONG)hicon[0]);

9.4设置定时器

SetTimer函数属于标准消息,在Frame类上右键,可以看到所有的标准消息。

void CMainFrame::OnTimer(UINT nIDEvent)
{
// TODO: Add your message handler code here and/or call default
static int index=0;
index=++index%4;
CFrameWnd::OnTimer(nIDEvent);
}

这里有2个概念,一是创建一个静态变量的时候,在内存区域的全局对象与静态变量区域分派内存,只会创建一次。二是如果要在一个循环中将值限定在一个范围之内,可以将循环值和这个值取模。

创建一个定时器之后,需要在OnCreate函数中添加定时器的响应。在MSDN中可以看到SetTimer函数的说明:

UINT SetTimer( UINT nIDEvent, UINT nElapse, void (CALLBACK EXPORT* lpfnTimer)(HWND, UINT, UINT, DWORD) );

SetTimer函数的返回值也即是函数的编号(第一个参数),第二个参数为定时器的响应时间,以毫秒为单位,第三个参数通常设置为NULL,具体为什么,没看懂。

SetTimer(1,1000,NULL);

9.5创建进度条

MFC中,有一个CProgressCtrl的类,如果要创建一个进度条,首先需要创建一个CProgressCtrl类的对象,然后调用Create成员函数来创建。MSDN中给出了一个相关的实例程序:

CProgressCtrl myCtrl;
// Create a smooth child progress control.
myCtrl.Create(WS_CHILD|WS_VISIBLE|PBS_SMOOTH, CRect(10,10,200,30),
pParentWnd, 1);

如何要在状态栏中创建一个进度条?

状态栏属于框架类,在状态栏中创建进度条,首先同样需要获取创建的区域。这之前需要String表中添加进度条的字串。然后将字串ID添加到

static UINT indicators[] =
{
ID_SEPARATOR,           // status line indicator
ID_INDICATOR_CAPS,
ID_INDICATOR_NUM,
ID_INDICATOR_SCRL,
IDS_PROGRESS,//用来显示进度条区域
};

为了获取进度条所处于的区域,可以调用GetItemRect函数。

void GetItemRect(index,LPRECT lprect) const;其中第一个参数为状态栏中网格的在indicators[]中的索引号。由于OnCreate函数实际上是响应的WM_ OnCreate函数,只有在这个函数完成之后才能获取到状态栏的矩形区域。因此需要在此处添加一个消息响应函数,等窗口创建完成之后,发送生成进度条消息。

MFC的每个标准消息都有一个消息编号,要创新一个新的消息,首先要创建消息编号。

1、 在Frame的头文件中添加一个消息标识宏#define UM_PROGRESS WM_USER+1;

2、 按照标准消息的格式和流程,分别在头文件和源文件中添加消息映射

头文件:

//{{AFX_MSG(CMainFrame)
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnTimer(UINT nIDEvent);
afx_msg void OnPaint();
afx_msg void OnProgress();//消息
//}}AFX_MSG

源文件:

//{{AFX_MSG_MAP(CMainFrame)
ON_WM_CREATE()
ON_WM_TIMER()
ON_WM_PAINT()
ON_MESSAGE(UM_PROGRESS,OnProgress) //消息
//}}AFX_MSG_MAP

3、 在源文件中添加消息响应函数

void CMainFrame::OnProgress()
{
CRect rect;
m_wndStatusBar.GetItemRect(5,&rect);
m_cpc.Create(WS_CHILD|WS_VISIBLE|PBS_SMOOTH,rect,&m_wndStatusBar,123);
m_cpc.SetPos(50);
}

4、 在源文件的OnCreate函数中添加PostMessage(UM_PROGRESS);

这样便可以在状态栏动态创建一个进度条。这里有个一SendMessage函数和PostMessage函数的区别说明:

1、 SendMessage:直接把消息发送给消息响应函数,等消息响应函数处理完之后再返回。

2、 PostMessage:把消息发送到消息队列,然后立即返回。

由于OnProgress函数需要在OnCreate执行完之后再响应,因此此处采用PostMessage函数来发送该消息。

创建进度条完成之后,显示正常,但是如果此时拉动窗口,改变大小,会发现进度条的位置不正确了,为了保证进度条的位置正确,需要添加OnPaint函数。

void CMainFrame::OnPaint()
{
CPaintDC dc(this); // device context for painting
// TODO: Add your message handler code here
CRect rect;
m_wndStatusBar.GetItemRect(5,&rect);
if(!m_cpc.m_hWnd)
{
m_cpc.Create(WS_CHILD|WS_VISIBLE|PBS_SMOOTH,rect,&m_wndStatusBar,123);
}
else
{
m_cpc.MoveWindow(rect);
}
m_cpc.SetPos(50);
// Do not call CFrameWnd::OnPaint() for painting messages
}

因此WM_PAINT消息是自动执行的,这样就不需要在OnCreate函数中再添加PostMessage函数来发送消息了。

完成进度条的创建之后,可以再OnTimer函数中添加代码,实现进度条的动态效果,代码就不贴上来了,都是调用相关函数来实现。

9.5在状态栏上显示鼠标

在View类上添加WM_MOUEOVER函数:

void CStyleView::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
CString str;
str.Format("x=%d y=%d",point.x,point.y);
((CMainFrame*)GetParent())->m_wndStatusBar.SetWindowText(str);
CView::OnMouseMove(nFlags, point);
}

这之前需要在View类中引入Frame类的头文件,并将Frame类的m_wndStatusBar设置为public.

《VC++深入详解–学习笔记》(8)对话框2

Filed Under (VC++ 学习笔记) by panmaoru on 13-12-2009

本章节继续讲解对话框的有关知识,以及属性表单和向导的创建。

8.1属性表单和向导的创建

一个属性表单是由一个或者多个属性页组成的。要创建一个属性表单,首先要创建一个CPropertySheet属性页和几个IDD_PROPPAGE_类型的选项页,创建页面完成之后。在PropertySheet类页面添加需要引入选项页的头文件,同时创建该类的对象,并调用AddPage函数,将选项页添加到属性表单中。

1、  添加头文件 #include “head.h”;

2、  创建该类的对象 head head1;

3、  将对象添加到属性列表中 AddPage(&head1)。

当添加多个选项页的时候,最后显示的顺序和添加的顺序一致。

如果要显示一个属性表单,可以通过调用模态对话框的形式显示。比如说一个按钮事件:

void CMenuListDlg::OnButton1()
{
       // TODO: Add your control notification handler code here
       CPropSheet cps("list");
       //cps.SetWizardMode();//设置表单显示方式
       cps.DoModal()
}

在创建一个属性表单对象的时候,必须初始化对象的一个名称参数。

8.2属性向导的按钮属性

当把一个属性表单的导航设置为上一步,下一步的浏览方式的时候,需要对当前选项卡下的导航做一些设置,如第一个页面不需要上一步,最后一个页面不需要下一步。要设置这些按钮的显示属性,可以通过SetWizardButtons函数来设置这些按钮的显示属性。

SetWizardButtons( DWORD dwFlags );

在属性表单的选择过程中,当一个页面变成活动页的时候,应用程序就会调用该选项卡的OnSetActive()函数,也就是可以在这个时候设置按钮属性。要在一个选项卡的中访问父框架,可以使用GetParent()来获取父框架指针,可以需要对返回的指针做一个强制转化为CPropertySheeet类型的。设置顶一个活动页的按钮属性为。

BOOL CProp1::OnSetActive()
{
       // TODO: Add your specialized code here and/or call the base class
       ((CPropertySheet*)GetParent())->SetWizardButtons(PSWIZB_NEXT);
       return CPropertyPage::OnSetActive();
}

由于在第一个页面设置了父框架的按钮属性,在之后的每一个页面都要做对应本页面的属性设置。

在设置了按钮的显示属性之后,可以同时添加按钮的响应事件。

LRESULT CProp1::OnWizardNext()
{
       // TODO: Add your specialized code here and/or call the base class
       UpdateData();//需要更新一下数据
       if(判断事件)
       {
              //响应结果
              return -1;
       }
       return CPropertyPage::OnWizardNext();
}

在执行判断事件之前,需要先更新下页面上的一些操作而引发的一些初始数据的变化(如选择框的选择等等)。

8.3属性表单数据的动态添加和获取

对于一些输入框,文本框,可以通过SetDlgItemText等函数来动态设置值,这里需要主要在获取这些对象的时候,有些可能需要做指针的类型转换。对于一些列表控件(CListBox,CComboBox,CToolBarBox),可以通过AddString函数来动态添加列表的值。

       ((CListBox*)GetDlgItem(IDC_LIST2))->AddString("1");
       ((CListBox*)GetDlgItem(IDC_LIST2))->AddString("2");
       ((CListBox*)GetDlgItem(IDC_LIST2))->AddString("3");
       ((CListBox*)GetDlgItem(IDC_LIST2))->AddString("4");

这些动态数据的创建在下面2个函数中都可以完成,在2个函数中都做了下测试,显示方面都是正常的,OnSetActive和OnInitDialog,从2个函数的定义上来看,还是OnInitDialog更合适些。

要获取属性表单的数据,需要记清楚一个概念:当一个DoModal函数返回之后,对话框窗口就被销毁了,但是窗口类的对象并没有消失,因此任然何以用这个窗口类的对象去访问它的数据成员。

访问方式如下

CPropSheet cps("list");
cps.SetWizardMode();
if(ID_WIZFINISH==cps.DoModal())
{
       m_iin=cps.m_prop1.m_iint;
       Invalidate();//让类视图无效,引发重绘操作
}

好了,数据已经获取到了,可以执行相应的操作了。

《VC++深入详解–学习笔记》(7)对话框1

Filed Under (VC++ 学习笔记) by panmaoru on 12-12-2009

记得以前上学的时候听一个老师说过,计算机技术的核心是输入输出和二进制。Windows应用程序工作的基本流程就是从用户那里得到数据,经过相应的处理之后,再把处理结果输出到屏幕和其他的输出设备上。为了实现这个过程,就需要一个很重要的接口—对话框。

7.1对话框的基本知识

在MFC中,所有的控件类都是从CWnd类派生而来。以前做过一些其他开发技术的开发工作,相对其他IDE而言,VC下提供的控件很少,但是基本可以满足开发需要了。

对话框的种类主要分2中,模态对话框和非模态对话框。

  1. 模态对话框:指当前显示时,程序会暂停执行,知道关闭这个模态对话框后才能继续执行其他任务(个人感觉这个只是一个相对暂停的概念,暂停的只是针对用户操作部分,我们运行一个杀毒软件程序,杀毒过程中如果有一些参数设定,会出现一些模态对话框,但是这个时候杀毒程序还是会继续执行的)。
  2. 非模态对话框:对其他操作没有影响,这个容易理解些。

7.2 对话框的创建和显示

在Resources选项卡Dialog目录下,可以添加一个新的对话框。添加一个对话框之后,类视图可以看到也相应添加了一个新的类。在创建一个MFC单文档应用程序的时候,系统为默认创建一个CAboutDlg类,在Dialog文件夹下可以看到IDD_ABOUTBOX这个对话框。在View类和Frame类中操作对话框的时候,首先需要在cpp文件中引入该对话框的头文件。

显示一个对话框:

void CMyboleView::OnDialog()
{
       // TODO: Add your command handler code here
       CTestDlg dlg;
       dlg.DoModal();//DoModal是一个函数,刚不小心忘了括号,记清楚了

}

如果要显示一个非模态对话框,需要调用Dialog类的Create函数。MSDN中,Create类的声明如下:

BOOL Create( LPCTSTR lpszTemplateName, CWnd* pParentWnd = NULL );
BOOL Create( UINT nIDTemplate, CWnd* pParentWnd = NULL );

由于非模态对话框显示的时候程序不会暂停等待用户操作,如果使用临时变量的话,对象的生命周期在OnDialog()执行完之后就结束,这样就无法正常显示,所以可采用创建对象指针的方式,由于动态创建的指针对象是分配在堆内存上,堆内存上的变量周期可应用程序相同。

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

这里又涉及一个动态内存释放的问题。。。

7.3 动态创建按钮

参见MSDN,CButton的Create函数。

CButton myButton1, myButton2, myButton3, myButton4;
// Create a push button.
myButton1.Create(_T("My button"), WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
   CRect(10,10,100,30), this, 1);//当前窗口
// Create a radio button.
myButton2.Create(_T("My button"), WS_CHILD|WS_VISIBLE|BS_RADIOBUTTON,
   CRect(10,40,100,70), this, 2); //当前窗口
// Create an auto 3-state button.
myButton3.Create(_T("My button"), WS_CHILD|WS_VISIBLE|BS_AUTO3STATE,
   CRect(10,70,100,100), GetParent( ), 3); //父窗口
// Create an auto check box.
myButton4.Create(_T("My button"), WS_CHILD|WS_VISIBLE|BS_AUTOCHECKBOX,
   CRect(10,100,100,130), GetParent( ), 4); //父窗口

7.4 访问控件

访问控件的方式很多,按照书上的例子简单的抄了几种,感觉这三种最简单,使用上也方便些。

void CTestDlg::OnButton1()
{
       // TODO: Add your control notification handler code here
       int num1,num2,num3;
       //char buf1[10],buf2[10],buf3[10];1,2
       //GetDlgItem(IDC_EDIT1)->GetWindowText(buf1,10);1
       //GetDlgItem(IDC_EDIT2)->GetWindowText(buf2,10);1
       //GetDlgItemText(IDC_EDIT1,buf1,10);2
       //GetDlgItemText(IDC_EDIT2,buf2,10);2

       //num1=atoi(buf1);1,2
       //num2=atoi(buf2);1,2
       num1=GetDlgItemInt(IDC_EDIT1);
       num2=GetDlgItemInt(IDC_EDIT2);
       num3=num1+num2;
       //itoa(num3,buf3,10);1,2

       //GetDlgItem(IDC_EDIT3)->SetWindowText(buf3);1
       //SetDlgItemText(IDC_EDIT3,buf3);2
       SetDlgItemInt(IDC_EDIT3,num3);
}

《VC++深入详解–学习笔记》(6)菜单

Filed Under (VC++ 学习笔记) by panmaoru on 10-12-2009

菜单栏,工具栏,状态栏是组成Windows程序图形界面的三个主要元素。大多数的Windows应用程序都提供了菜单,作为用户和程序之间的一种交互途径。

6.1 菜单栏

在设置菜单栏的菜单项的时候,Pop-up属性的为弹出菜单,这种菜单不能响应命令,没有ID号。

6.2 菜单命令的路由

相应菜单项命令的顺序是:视类(View),文档类(Doc),框架类(Frame),应用程序类(App)。

Windows消息的分类:

  1. 标准消息:除了WM_COMMAND消息之外,所有以WM开头的消息都是标准消息,从CWnd派生的类都可以响应这类消息。
  2. 命令消息:来至菜单、加速键和工具栏按钮的消息,这类消息都是以WM_COMMAND形式呈现,在MFC中,通过菜单项的标示ID来区分不同的命令消息,在SDK中,通过消息的wParam参数标示,从CCmdTarget派生的类都可以接收这类消息。
  3. 通告消息:有控件产生的消息,例如按钮的单击,列表框的选择等都会产生这类消息,目的是向其父窗口通知事件的发生。这类消息以WM_COMMAND形式发送,从CCmdTarget派生的类都可以接收这类消息。

CWnd派生于CCmdTarget,从CWnd派生的类可以接收所以类型的消息(View,Frame),其他从CCmdTarget派生的类只能接收命令消息和通告消息(Doc,App)。

6.3 基本菜单操作

Pop-up0 Pop-up1 Pop-up2 Pop-up3
0-0 1-0 2-0 3-0
0-1 1-1 2-1 3-1
0-2 1-2 2-2 3-2

可以通过CWnd的成员函数GetMenu来获取指向菜单栏的指针,对菜单子项的访问可以通过菜单的索引标识来实现。

操作一个菜单项的2种实例:

GetMenu()->GetSubMenu(0)->CheckMenuItem(0,MF_BYPOSITION | MF_CHECKED);
GetMenu()->GetSubMenu(0)->CheckMenuItem(ID_FILE_OPEN,MF_BYCOMMAND | MF_CHECKED);

MSDN中关于MF_BYCOMMAND 和MF_BYPOSITION的解释:

MF_BYCOMMAND   Specifies that the parameter gives the command ID of the existing menu item. This is the default.

MF_BYPOSITION   Specifies that the parameter gives the position of the existing menu item. The first item is at position 0.

GetSubMenu有很多成员函数,用于菜单的操作。

6.3.1 快捷菜单

工程-添加到工程-Components and Controls,从这里可可以选择VC提供的一些组件和控件,添加一个POPUP MENU,选择所属于View类,此时View类中便添加了一个OnContextMenu函数。

void CMenuView::OnContextMenu(CWnd*, CPoint point)
{
       // CG: This block was added by the Pop-up Menu component
       {
              if (point.x == -1 && point.y == -1){
                     //keystroke invocation
                     CRect rect;
                     GetClientRect(rect);
                     ClientToScreen(rect);
                     point = rect.TopLeft();
                     point.Offset(5, 5);
              }
              CMenu menu;
              VERIFY(menu.LoadMenu(CG_IDR_POPUP_MENU_VIEW));
              CMenu* pPopup = menu.GetSubMenu(0);
              ASSERT(pPopup != NULL);
              CWnd* pWndPopupOwner = this;
              while (pWndPopupOwner->GetStyle() & WS_CHILD)
                     pWndPopupOwner = pWndPopupOwner->GetParent();
              pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y,
                     pWndPopupOwner);
       }
}

运行之后,在View类区域右键,会出现一个弹出窗口。