孙鑫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
);
注意:窗口回调过程函数就是要写成上面的形式,函数名字可以更改,参数类型不能变。
<<< 第一讲
◇ 句柄(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
);
注意:窗口回调过程函数就是要写成上面的形式,函数名字可以更改,参数类型不能变。
相关文章推荐
- 孙鑫VC学习笔记:第一讲 Windows程序内部运行原理
- 孙鑫VC学习笔记:第二十讲 Hook编程
- 孙鑫VC学习笔记:ActiveX 控件
- 孙鑫VC学习笔记:第二讲 掌握C++
- 孙鑫VC学习笔记:第十二讲 用C++函数读写文件
- 孙鑫VC学习笔记:第十六讲 利用关键代码段实现线程间的同步
- 孙鑫VC学习笔记:第十七讲 用匿名管道实现进程间的通信
- 孙鑫VC学习笔记:第十八讲 ActiveX 控件
- 孙鑫VC学习笔记:第十六讲 (一) 利用事件对象实现线程间的同步
- 孙鑫VC学习笔记:第十三讲 (五) 保存可串行化的类对象 如何获取文档与视类指针
- 孙鑫VC学习笔记:第十二讲 (五) 往win.ini的文件中写入数据
- 孙鑫VC学习笔记:第八讲 逃跑按钮的巧妙实现和MFC中指针的获取
- 孙鑫VC++视频学习笔记-第3课
- 孙鑫vc学习笔记_第一课
- 孙鑫VC学习笔记:第二十讲 ado数据库编程
- 孙鑫VC++视频学习笔记之7: 对话框编程(2)
- 孙鑫vc学习笔记_第10课
- 孙鑫VC++视频课程学习笔记.
- 孙鑫VC学习笔记:第十一讲 (一) 坐标空间与各种转换的概念
- 孙鑫VC学习笔记:第十二讲 用API函数、CFile类操作文件