您的位置:首页 > 理论基础 > 数据结构算法

孙鑫VC学习笔记:第一讲

2006-07-23 10:53 513 查看
到现在才学VC,感觉太晚了; 到现在才看孙鑫VC视频教程,感觉太菜了。在这高手云集的地方,写这些东西有点让人发笑。唉,现在也不需要什么脸皮了,写就写吧,做下一些记录,以后可以看看自己是怎么成长的。希望有菜鸟们一起来学习,交流,更希望有大师指点指点,发发评论什么的!本来笨鸟要先飞的,我这笨鸟却这么晚才起飞,大家都飞出好远了,我唯有加倍的努力了!至于学习方法,还得请各位多多指教。下面开始我的教程:

<<< 第一讲   
◇ 句柄(HANDLE)是资源的标识,类似于指针,通过它可以找到内存中各种对应的资源。
◇ 句柄可以分为:
 图标句柄(HICON)
 光标句柄(HCURSOR)
 窗口句柄(HWND)
 应用程序实例句柄(HINSTANCE)

◆ 消息
 typedef struct tagMSG{
  HWND  hwnd;
  UINT  message;//操作系统定义了很多以 WM_ 开头的宏来对应这些消息。
  //详细请看 http://hi.baidu.com/cwin/blog/item/495dd61baeac1dd5ad6e75ff.html
  WPARAM wParam; //其中WPARAM和LPARAM都是整型数据
  LPARAM lParam;
  DWORD  time;//WORD 是16位的整数,DWORD 是32位的整数,记录消息传递的时间
  POINT pt;
 }  MSG, *PMSG;
 
◆ WinMain参数的含义
int WINAPI WinMain(
  HINSTANCE hInstance,  //handle to current instance
  HINSTANCE hPrevInstance, //handle to previous instance
  LPWSTR lpCmdLine,  //command line,传递命令行
// LPSTR LongPointSTR 是一个指向字符串首地址的指针。类似于char *
  int nShowCmd   //show state,指定窗口如何显示,如最大化显示,最小化显示,或者隐藏显示
);

 lpCmdLine 命令行参数的值可以如下设定 "工程(project)->设置(setting)->debug->程序变量U中设置(program argument):[比如输入 wenxin.txt]"
◇注意:Winmain()函数由操作系统来调用。上面的各个参数也是由操作系统进行赋值的。
  
◆创建一个完整的窗口需要经过下面四个操作步骤:
1.设计一个窗口
2.注册一个窗口
3.创建窗口
4.显示及更新窗口

◇设计窗口类,是一个结构体。
 typedef struct _WNDCLASS {  //可以这样实例化一个窗口类:WNDCLASS wndclass
  UINT  style; //可以有这些值:CS_DBLCLKS | CS_HREDRAW水平重画 | CS_NOCLOSE | CS_PARENTDC | CS_VREDRAW垂直重画
  WNDPROC lpfnWndProc; //指向一个窗口过程函数(回调函数),用来接受一个函数指针
  int  cbClsExtra;  //类的额外的数据,wndclass.cbClsExtra = 0;
  int  cbWndExtra;  //窗口的额外的数据,
  HANDLE hInstance;  //当前应用程序实例号,wndclass.hInstance;
  HICON hIcon;  //图标句柄:wndclass.hIcon = LoadIcon (NULL,IDI_APPLICATION);
  HCURSOR hCursor; //光标句柄:wndclass.hCursor(NULL,IDC_ARROW);
  HBRUSH hbrBackground; //画刷句柄:wndclass.hbrBackground=(HBRUSH)GetstockObject(DKGRAY_BRUSH);
  LPCTSTR lpszMenuName;  //设置菜单:wndclass.lpszMenuName = NULL;
  LPCTSTR lpszClassName; //给一个窗口类取一个名字
 }WNDCLASS ;
 
1. 在我们的程序中经常要用到一类变量,这个变量里的每一位(bit)都对应某一种特性。当该变量的某位为1时,表示有该位对应的那种特性,当该位为0时,即没有该位所对应的特性。当变量中的某几位同时为1时,就表示同时具有几种特性的组合。一个变量中的哪一位代表哪种意义,不容易记忆,所以我们经常根据特征的英文拼写的大写去定义一些宏,该宏所对应的数值中仅有与该特征相对应的那一位(bit)为1,其余的bit都为0。我们使用goto definition就能发现CS_VREDRAW=0x0001,CS_HREDRAW=0x0002,CS_DBLCLKS =0x0008,CS_NOCLOSE=0x0200。他们的共同点就是只有一位为1,其余位都为0。如果我们希望某一变量的数值既有CS_VREDRAW特性,又有CS_HREDRAW特性,我们只需使用二进制OR(|)操作符将他们进行或运算相组合,如style=CS_VREDRAW | CS_HREDRAW | CS_NOCLOSE。如果我们希望在某一变量原有的几个特征上去掉其中一个特征,用取反(~)之后再进行与(&)运算,就能够实现,如在刚才的style的基础上去掉CS_NOCLOSE特征,可以用style & ~CS_NOCLOSE实现。

2. 回调函数的原理是这样的,当应用程序收到给某一窗口的消息时(还记得前面讲过的消息通常与窗口相关的吗?)(消息结构中的参数HWND hwnd;),就应该调用某一函数来处理这条消息。这一调用过程不用应用程序自己来实施,而由操作系统来完成,但是,回调函数本身的代码必须由应用程序自己完成。对于一条消息,操作系统到底调用的是哪个函数来处理呢? 操作系统调用的就是接受消息的窗口所属的类型中的pfnWndProc 成员指定的函数。每一种不同类型的窗口都有自己专用 的回调函数,由lpfnWndProc成员指定。

3. 应用程序注册一个窗口类的时候,可以让windows操作系统分配和追加一个额外的内在空间( cbClsExtra  ),称之为类的附加内存,由属于之个窗口类的所有窗口所共享,我们也可以为之个参数设置额外的字节,通常情况下将之个额外的字节设置为0.(wndcls.cbClsExtra=0;)
 windows系统会为每一个窗口管理一个内部数据结构,可以指定窗口附加内存cbWndExtra,通常也设置为0.(wndcls.cbWndExtra=0;) 如果设置的字节不为0, 在初始化窗口的时候,系统会初始化cbClsExtra和cbWndExtra的值为0.
4. LoadIcon 是一个API 函数, 同样地,LoadCursor()也是一个API函数。
 HICON LoadIcon(
   HINSTANCE hInstance, //当使用标准的图标时,这个值用NULL
   LPCTSTR lpIconName   //可以有如下的值:IDI_APPLICATION,IDI_ASTERISK,IDI_ERROR,IDI_EXCLAMATION,IDI_HAND,IDI_INFORMATION 等
 );
5. LPCTSTR A32-bit pointer to a constant character string that is protable for Unicode and DBCS.

◇在创建完窗口类之后,接下来的事情是用RegisterClass()函数注册一个窗口:RegisterClass(&wndclass)
 
◇ 下面创建窗口,首先要定义一个句柄:
 HWND hwnd; //用来保存新创建的窗口的标识
 hwnd = CreateWindow ("wenxin",//一定要是注册后的窗口类名,
  "温馨", //窗口的名字,标题。
  WS_OVERLAPPEWINDOW, //是一个宏,是一些类型的组合.它用来指定窗口的类型,为DWORD类型的变量
  CW_USEDEFAULT, //指定窗口水平坐标。CW_USEDEFAULT 表示系统会自动选择一个默认的位置,这时下面的垂直坐标失效,系统也会自动定位
  CW_USEDEFAULT, //指定窗口垂直坐标,
  CW_USEDEFAULT, //窗口宽度,CW_USEDEFAULT 为系统自动设定
  CW_USEDEFAULT, //窗口高度
  NULL,   //父窗口句柄,
  NULL,   //菜单句柄
  hInstance,  //当前实例句柄
  NULL );   //是一个指针,主要用做附加参数
 
 下面我们来看一下MSDN里面CreateWindow函数的定义:
  HWND CreateWindow(
     LPCTSTR lpClassName,
     LPCTSTR lpWindowName,
     DWORD dwStyle,
     int x,
     int y,
     int nWidth,
     int nHeight,
     HWND hWndParent,
     HMENU hMenu,
     HANDLE hInstance,
     PVOID lpParam
   );
 
 返回去看看 WS_OVERLAPPEDWINDOW 这个宏是怎么组合的:
 #define WS_OVERLAPPEDWINDOW (WS_OVERLAPPED  |/  //表示产生一些层叠的窗口,也就是窗口具有标题栏和一个边框
        WS_CAPTION  |/ //创建一个有标题栏的窗口
        WS_SYSMENU  |/ //创建一个有系统菜单的窗口
        WS_THICKFRAME |/ //创建一个可调边框的窗口
        WS_MINIMIZEBOX |/ //有最小化按钮,必须同时设置WS_SYSMENU
        WS_MAXIMIZEBOX )
 
 如果要去掉最大化按钮,可以对WS_MAXIMIZEBOX取反,再进行与操作,如:
  WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX
  
◇ 好了,现在要进行的是窗口创建的最后一步,那就是显示及更新窗口,这就相当于汽车生产出来了,需要进行一个新闻发布会。调用ShowWindow()来显示窗口。
 BOOL ShowWindow(  //指定窗口的显示状态
    HWND hWnd,  //指定要显示的窗口
    int nCmdShow  //显示的状态,
  );
 看看都有那些显示状态:
 SW_SHOWMINIMIZED 最小化显示
 SW_SHOWMAXIMIZED  最大化显示,
 SW_SHOWNORMAL  正常显示
 其实这些状态前缀SW_就表示ShowWindow
 
◇ 显示完成之后,还需要调用一个UpdateWindow(hwnd),用来更新窗口。 
 其实UpdateWindow()可有可无。至此,窗口可以显示出来了。
 

 

 

再来看看孙老师程序中的回调函数:
LRESULT CALLBACK WinSunPro( //LRESULT 是一个long型,CALLBACK将在最后面介绍
  HWND  hwnd,
  UINT  uMsg,
  WPARAM wParam,
  LPARAM  lParam
 )//可以发现上面的四个参数跟消息结构体中的前四个参数是一样的,
 //这方便将消息的前4个参数传递给该函数。而消息结构体中的后面
 //两个参数 消息产生时间和位置这里没有用到。
{
 HDC hdc; //HDC 是一个DC句柄,表示divce context 设备上下文,是一个内部维护的数据结构。
 PAINTSTRUCT ps;
 switch (uMsg)
 {
  case WM_PAINT:
   hdc = BeginPaint (hwnd, &ps);  //BeginPaint()可以获得一个DC句柄。
   //hwnd是一个窗口句柄,ps是PAINTSTRUCT类型的指针,由系统自动维护。
   TextOut(...);
   EndPaint(hwnd, &ps );
 //注意:BeginPaint和EndPaint只能在WM_PAINT消息中应用,不能用于其他地方。
 //同样,GetDC()和RleaseDC()不能在这里使用,只能在其他位置使用。
   break;
  case WM_CHAR:
   char szChar[20];
   sprintf(szChar,"char is %d",wParam);//格式化输出到szChar中。
    ......

  case WM_LBUTTONDOWN:
   ......
   HDC hDC;
   hDC = GetDC (hwnd); //hwnd是窗口句柄,DC与hwnd窗口相关
   TextOut ( hDC, 0/*输出位置横坐标*/, 50/*纵坐标*/, "计算机编程语言培训",strlen("计算机编程语言培训") );
  //往窗口输出一段文本。
  ReleaseDC (hwnd,hDC);//释放DC所占用的资源
  case WM_CLOSE:
   if (IDYES == MessageBox (hwnd, "你是否要退出程序?", "wenxin",MB_YESNO))
   {//
    DestroyWindow (hwnd);//销毁窗口(但不一定退出程序)并发出WM_DESTROY消息,
   }//endof if
   break;
  case WM_DESTROY://消息处理
   PostQiutMessage(0);//通知系统线程请求终止,发出WM_QUIT消息到消息队列,这样消息循环的
   //GetMessage()获得WM_QUIT消息返回0值,从而使消息循环停止,应用程序结束。
   break;
  default:
   return DefWindowProc (hwnd, uMsg, wParam, lParam );//缺省的窗口过程
   //这条语句是必不可少的,让系统处理默认的消息。
 }//endof switch
 
 return 0;
 
}//endof WinSunPro()

介绍一个编程习惯,用if函数判断一个变量是否等于某个常量值时,将常量放在变量前面,如写成:if(1== x)。这样就可以防止将 if( x == 1 )误写成if( x = 1)。

◇介绍一下 CALLBACK
 #define CALLBACK  __stdcall  //是一个宏定义,是一种pascal调用约定
 与__stdcall相类似的还有__cdecl,__cdecl是C语言的调用约定。
 这些约定定义了参数传递的顺序,堆栈清除的顺序。在VC下默认的约定是__cdecl。
 要设定VC的默认约定为__stdcall,可以如下设置:
 “工程->设置->C/C++->分类->Code Generation->Calling convention”
 要得到更详细的解释,可以参考这个网站:
 
  http://topic.csdn.net/t/20050410/00/3923401.html
  http://topic.csdn.net/t/20050406/18/3914624.html
  
好了,今天的课程就到这里。有兴趣的话,可以再看看别人做的笔记:http://www.blog.edu.cn/user2/hxwwf/archives/2006/1256911.shtml
 

照孙老师的步骤做了一个例子,但并不是那么顺利,出现了下面的问题: 
   Linking...
   LIBCD.lib(crt0.obj) : error LNK2001: unresolved external symbol _main
   Debug/winMain.exe : fatal error LNK1120: 1 unresolved externals
   执行 link.exe 时出错.

   winMain.exe - 1 error(s), 0 warning(s)
 
后来在网上查找资料,发现有两个地方有问题,
 
第一、WinMain写成winMain了,小写了W。 
第二、Windows项目要使用Windows子系统, 而不是Console, 可以这样设置:
   [Project] --> [Settings] --> 选择"Link"属性页,
   在Project Options中将/subsystem:console改成/subsystem:windows 
  对于LNK2001的错误的详细解决办法可以参见这个网址:
 http://hi.baidu.com/cwin/blog/item/e98755da25e53edbb6fd4815.html

 

 

 

◆ 下面要讲的是最重要的一点,那就是消息循环,先看看孙老师的例子吧:
 MSG msg; //定义一个消息变量
 while (GetMessage (&msg, NULL, 0, 0) )
 {
  TranslateMessage ( &msg);//对取到的消息进行转换翻译消息,意思是对取到的
  //消息对进行转换,当我们按下键盘的某个键的时候,系统将发出一个WM_KEYDOWN
  //和WM_KEYUP 两个消息,参数当中提供的是我们键盘的一个虚拟扫描码,
  //该函数可以将它们转换成WM_CHAR 消息。并将转换后的新消息投递到消息队列中。
  //这些过程不会影响原来的消息,只会产生新的消息。
  
  DispatchMessage (&msg); //将取到的消息传到窗口回调函数去处理,
  //也就是将消息路由给了操作系统,操作系统再调用
  //窗口过程函数(在设计窗口类时指定)
  
 }//endof while()
 
◇ GetMessage 从调用线程的消息队列中获取消息 
 BOOL GetMessage( //调用这个函数的,会从消息队列里中取出一条消息,利用消息结构体的变量lpMsg返回
    LPMSG lpMsg,  //是一个消息结构体的一个指针
    HWND hWnd,  //窗口句柄,指定是要获取那一个窗口的的消息,如果将之设置为NULL,表示想要获取属于调用线程的任何窗口的消息
    UINT wMsgFilterMin,  //指示最小的一个消息值。
    UINT wMsgFilterMax  //指示消息的最大值
  );
 wMsgFilterMin 可以用WM_KEYFIRST 来指示第一个键盘消息,用 WM_MOUSEFIRST 来指示第一个鼠标消息
 wMsgFilterMax   也可用 WM_KEYLAST 指示最后一个键盘消息,用 WM_MOUSELAST 来指示最后一个鼠标消息。
 如果将 wMsgFilterMin 和 wMsgFilterMax 设为0,则返回所有可以利用的消息,没有范围的过滤。
   
 在MSDN中这样解释lpMsg:
  lpMsg
  [out] Pointer to an MSG structure that receives message information from the thread's message queue.
  注意:[out]表明在传参的时候我们没有必要对结构体内部的数据成员初始化,我们只需要定义一个结构体的变量,将它的地址放在这里就可以了。通过函数的调用,会自动填充消息结构体当中的内部的成员变量。
  
◇ 回顾一下:
 当一个应用程序建立的时候,操作系统会为这个应用程序分配一个消息队列,与该程序相关的消息操作系统都会将它们放到这个消息队列当中,应用程序利用GetMessage()从消息队列当中取出一条具体的消息,利用TranslateMessage()将WM_KEYDOWN 和 WM_KEYUP 转换为一个WM_CHAR 消息,放到消息队列当中。利用DispatchMessage()将这个消息投递出去,分发出去,可以理解为分发给了操作系统,操作系统再利用在设计窗口类时指定的回调函数结消息进行处理。这个回调函数对不同的消息回有不同的响应,这样就完成了整个消息的循环。
 
下面来看看回调函数要怎么写,先看MSDN:
  LRESULT CALLBACK WindowProc( //LRESULT是一个long型
    HWND hwnd,  //handle to window
    UINT uMsg,  //message identifier
    WPARAM wParam, //first message parameter
    LPARAM lParam  //second message parameter
  );
注意:窗口回调过程函数就是要写成上面的形式,函数名字可以更改,参数类型不能变。
 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息