Timers and Animation

theForger's Win32 API教程第二版(简体中文)

主页

基础
  1. 开始学习
  2. 一个简单的窗口
  3. 处理消息
  4. 理解消息循环
  5. 使用资源
  6. 菜单和图标
  7. 对话框
  8. 非模态对话框
  9. 标准控件
  10. 对话框常见问题
创建一个简单应用
  1. 在运行时创建控件
  2. 文件与常用对话框
  3. 工具栏与状态栏
  4. 多文档界面
图形设备接口
  1. 位图,设备上下文
  2. 透明位图
  3. 定时器与动画
  4. 文本,字体与顏色
工具与文档
  1. 参考
  2. 免费的Visual C++(最新更新)
附表
  1. 常见错误的解決方法
  2. API vs. MFC
  3. 关于资源文件的说明

理解消息循环

  在写任何有实际价值的程序之前都要理解整个消息循环和windows程序传递息的结构当前为止我们已经试了一些息的处理我还应该更进一步的看一下这个过程因为你如果不理解它们的话后面的內容将会对你很难

什么是息?

  一个息就是一个整数如果你到头文件看一下了解API的好地方你就会发现这样的內容

#define WM_INITDIALOG                   0x0110
#define WM_COMMAND                      0x0111

#define WM_LBUTTONDOWN                  0x0201

...

等等..消息用来进行windows系统中的所有通信至少在基方面是这样的如果你想要你的窗口或是控件其实就一种特別的窗口做点什么你就给它发个如果另外一个窗口要你做点什么它也发给你一个息.如果发生了用戶按动了键盘移动了鼠标点击了一个按钮之类的事件系统就向相关的窗口发送你要是就是个被传至消息窗口你就要处理这些

  每个windows息可能拥有至多两个参数wParam和lParam最初wParam是16bitlParam是32bit但在Win32平台下两者都是32bit.不是每个息使用了这些参数而且每个息以不同的方式来使用比如WM_CLOSE不使用它们任一个所以你应该忽略它们WM_COMMAND息两个都使用wParam有两个部分HIWORD(wParam)中含有提示消息如果有的话),LOWORDwParam含有发送息的控件或菜单的标识号lParam含有发送息的控件的HWND窗口的句柄或者为NULL,当消息不是由控件发送

  HIWORD()和LOWORD()是windows定义的两个宏用来从一个32bit值中分离出高字(0xffff0000)和低字(0x0000ffff)的两字节在Win32中,一个WORD是16bit,所以一个DWORD为32bit

  可以用PostMessage()或SendMessage()来发送消息.PostMessage()把消息放入消息队列再立即返回.就是说你调用了PostMessage()后消息可能被处理了,也可能还沒有被处理. SendMessage()则真接把消息送往窗口并且在窗口沒有结束处理消息之前不返回.如果我们想关闭一个窗口我们可以发送一个WM_CLOSE消息:PostMessage(hwndWM_CLOSE00);这跟我们点击窗口顶部的 [x] 按钮一样的效果.注意wParam和lParam都为0.这是因为我们刚才说了WM_CLOSE并不用它们.

对话框

  一旦你开始使用对话框,你将需要向此控件发送消息以与它们通信.你可以先使用GetDlgItem()来用ID得到控件的句柄然后用SendMessage()也可以直接用SendDlgItemMessage()这个一步到位的函数.你给它一个窗口的句柄和一个子窗口的ID,它就会得到字窗口的句柄并向它发送消息.SendDlgItemMessage()和类似的API,如GetDlgItemText()可以用在所有的窗口上面,而不是仅仅在对话框上.

什么是消息队列

  打个比方,你正在处理WM_PAINT消息,此时突然用戶在键盘上敲了一大堆的东西.这时候会怎样?你应该中止你的工作去响应键盘的输入,还是简单地把这些输入给忽略掉? 错!显然两种方式都不能接受,所以我们引入了消息队列的概念,消息被发送时就被放入队列,被处理后就从队列中删除.这就保证了你不会丟掉消息,你在处理某个的时候,另外的就在队列中等待你来取走它们.

什么是消息循环

while(GetMessage(&Msg, NULL, 0, 0) > 0)
{
    TranslateMessage(&Msg);
    DispatchMessage(&Msg);
}
  1. 消息循环调用了GetMessage()它到你的消息队列中去看.如果队列是空的你的程序就在那里等(阻塞在那里).

  2. 当一个事件发生导致一个消息加到队列中去比如系统注冊了一次鼠标点击GetMessage()就返回一个正值表示有一个消息待处理并且它将我们传递的MSG结构体填充如果遇到了WM_QUIT它就返回0如果有错误发生就返回负值

  3. 我们拿到信号在Msg变量中并传给TranslateMessage()它进行一些额外的处理将虛键值转为字符信息这一步其实是可有可无的但是有些地方会依赖这个步骤

  4. 一旦上面的工作完成了我们把消息传给DispatchMessage()DispatchMessage()先看看信号是给哪个窗口的再找到那个窗口的窗口过程. 再调用那个过程参数为窗口的句柄消息wParam和lParam

  5. 在你的窗口过程中你得到了那些参数就可以为所欲为了如果你沒有处理某些特定的消息那你就调用DefWindowProc()来为你做一些默认的操作一般就是什么都不做).

  6. 一旦你完成了对消息的处理你的窗口过程就返回了DispatchMessage()返回了我们就再次循环

这是windows程序中一个非常重要的概念.你的窗口过程并不是由系统来神奇地调用的实际上你在DispatchMessage()中自己间接地调用了它如果你愿意你可以对消息调用GetWindowLong()得到它的窗口过程再直接调用它

while(GetMessage(&Msg, NULL, 0, 0) > 0)
{
    WNDPROC fWndProc = (WNDPROC)GetWindowLong(Msg.hwnd, GWL_WNDPROC);
    fWndProc(Msg.hwnd, Msg.message, Msg.wParam, Msg.lParam);
}

  我对前面的例子用了这种方法可以工作但是有很多的方面这种方法沒有注意到比如Unicode/ANSI转換时钟回调函数等等它很可能在我们的简单试验中可以工作所以试下就可以了不要在实际的代码中用它)

  注意我们用GetWindowLong()来从窗口来查找它的窗口过程为什么我们不直接调用WndProc()因为我们的消息循环为我们程序中的所有窗口的消息服务包括按钮列表框这样拥有自己的窗口过程的窗口所以我们要我们调用了正确的窗口过程因为多个窗口可能使用一个窗口过程第一个参数窗口的句柄告诉窗口过程某个消息是为那个窗口的

  你可以看到你的应用程序的主要时间就是在消息循环这里打转你很高兴地向那些快乐的窗口发消息让他们处理如果你要退出程序你怎么办因为我们使用了一个while循环如果GetMessage()返回了FLASE就是0),循环就结束我们就可以到WinMain()的结束点这就是PostQuitMessage()做的工作它向队列发送一个WM_QUIT消息GetMessage()就向Msg结构体填充数据并返回0而不返回正的值. 这个地方Msg的wParam成员含有你传向PostQuitMessage()的数据你可以忽略它也可以从WinMain()返回当这个进程的结束码.

要点:GetMessage()遇到错误后返回-1.你要记住这点,说不定哪次你在这里会犯错...即使GetMessage()被定义为返回一个BOOL值,它还是会返回TRUE和FLASE之外的值,因为BOOL被定义为UINTunsigned int.下面的代码可能看起来可工作,但是不能正确处理某些情況:

    while(GetMessage(&Msg, NULL, 0, 0))
    while(GetMessage(&Msg, NULL, 0, 0) != 0)
    while(GetMessage(&Msg, NULL, 0, 0) == TRUE)

上面的写法都是错误的!可能你注意到我在这个教程中使用了第一个,刚刚提到了,如果GetMessage()不失败,并不会出错,你代码要是正确的话是不会出错.但是你要是在读本教程的话我并不能以此做为前提,你的代码可能有很多错误,GetMessage()会在某些点出错:)这种写法我已经更正了,如果我漏了某些地方请原谅.

    while(GetMessage(&Msg, NULL, 0, 0) > 0)

应该始终使用这段拥有相同的效果的代码

  我希望你对windows消息循环有了进一步的了解如果沒有不用怕你使用它们一些时间后就会更清楚了

Copyright © 1998-2008, Brook Miles (forgey). All rights reserved.