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. 关于资源文件的说明

一个简单的窗口[images/simple_window.gif]

范例:simple_window

 

  有时候有人在IRC上问:”我怎么才实现一个窗口?”...我觉得不是一句两句能说得清楚.虽然一旦你搞清楚你要做什么后并不难,但是你的确需要做一些事情来使显示一个窗口;这些事情不是在聊天室中一下子说得清楚的.

  我总是喜欢先做一件事情然后来理解它...所以先给出一个简单窗口的代码稍后再做解释.
#include <windows.h>

const char g_szClassName[] = "myWindowClass";

// Step 4: the Window Procedure
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASSEX wc;
    HWND hwnd;
    MSG Msg;

    //Step 1: Registering the Window Class
    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = 0;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = g_szClassName;
    wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

    if(!RegisterClassEx(&wc))
    {
        MessageBox(NULL, "Window Registration Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    // Step 2: Creating the Window
    hwnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        g_szClassName,
        "The title of my window",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
        NULL, NULL, hInstance, NULL);

    if(hwnd == NULL)
    {
        MessageBox(NULL, "Window Creation Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    // Step 3: The Message Loop
    while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
    return Msg.wParam;
}
  简单来说这是你想创建一个窗口能写的最简单的程序,大致上70行左右的代码.如果第一个范例你通过了编译,这个也应该不成问题.

第一步:注冊窗口类

  一个 窗口类 存储关于一个窗口的消息包括控制窗口的窗口过程窗口的大小图标以及背景顏色.用这种方式你可以先注冊一个窗口类然后从它创建任意数目的窗口而不需要一次次的指定这些参数如果需要大多数属性能针对单个的窗口来攺变.

  这里说的窗口类与C++中的类是完全不同的概念.

const char g_szClassName[] = "myWindowClass";
上面的变量存储了我们窗口类的名字,马上会用来向系统注冊窗口类.
    WNDCLASSEX wc;
    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = 0;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = g_szClassName;
    wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

    if(!RegisterClassEx(&wc))
    {
        MessageBox(NULL, "Window Registration Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

  这是我们在WinMain()中注冊我们的窗口类的代码.我们填写一个WNDCLASSEX结构体的成员并调用RegisterClassEx().

  结构体的成员对窗口类的影响如下:
cbSize
  结构体的大小.
style
  类的式样(CS_*),不要跟窗口式样(WS_*)混淆了.这个一般设置为0.
lpfnWndProc
  指向这个窗口类的窗口过程的指针.
cbClsExtra
  配置给这个类的额外內存.一般为0.
cbWndExtra
  配置给这个类的每个窗口的额外內存.一般为0.
hInstance
  应用程序实例的句柄.(从WinMain()第一个参数传递来.)
hIcon
  当用戶按下Alt+Tab组合时候显示的大图标(一般为32*32).
hCursor
  在我们的窗口上显示的光标.
hbrBackground
  设置我们窗口背景顏色的背景刷子.
lpszMenuName
  这个类的窗口所用的菜单资源的名字.
lpszClassName
  类的名字.
hIconSm
  在任务栏和窗口的左上角显示的小图标(一般为16*16).


  如果看得不很明白先不要担心,马上会有讲解.另外要记住的一件事情是不要试图去记下这些东西.我基本上不(从来不)记结构体或函数的参数,这样做是浪费精力和更重要的时间.如果你知道要调用的函数你就去花几秒钟在你的帮助文档中查一下.要是沒有帮助文档,就是想法弄到.沒有它们是很郁闷的.随著你使用多次后也许你会记住那些最常用的函数的参数.

  然后我们调用RegisterClassEx()并检查是否成功,如果失败我们弹出一条提示消息并从WinMain()函数退出程序.

第二步:创建窗口

  一旦类注冊完我们即可从它创建一个窗口你应该去查一下CreateWindowEx()的参数列表在你要用一个新API的时候你总是要这样做)但是我会在这里做个简单的介绍

    HWND hwnd;
    hwnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        g_szClassName,
        "The title of my window",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
        NULL, NULL, hInstance, NULL);

  第一个参数WS_EX_CLIENTEDGE是扩展的窗口式样这里我设置它想得到一个內部下陷的边框效果你可以设成0看看效果要习惯于试不同的值效果

  接下来我设置了类的名字g_szClassName),告诉系统我们要创建什么样的窗口因为我们要从刚刚注冊的类创建窗口我们使用了该类的名字之后我们指定了我们窗口的名字或是标题用来显示在我们窗口的外观或是标题栏上

  我们设置的WS_OVERLAPPEDWINDOW是一个窗口式样参数这里有很多东西你可以查找并做试验在下文中将详加说明

  接下来的四个参数CW_USEDEFAULTCW_USEDEFAULT320240是我们窗口的左上角的XY坐标和其宽度和高度我把XY坐标设为CW_USEDEFAULT来让系统自己选择在屏幕的哪个地方来放置窗口记住屏幕的最左边的X坐标为0并向右加屏幕的顶部的Y坐标为0并向底加.单位是像素这是屏幕在特定的分辨率下能显示的最小单位

  再接下来的四个NULLNULLg_hInstNULL分別是父窗口的句柄菜单句柄应用程序实例句柄和窗口创建数据的指针.windows系统中你屏幕上的窗口是以分层次的父窗口子窗口的形式来组织的当你看到一个窗口中有一个按钮时候按钮就是子窗口包含它的窗口就是父窗口.我们的例子中父窗口的句柄为NULL因为这里沒有父窗口这个是是我们的主窗口或是顶层窗口.菜单也是NULL因为我们现在也沒有菜单.实例句柄设为我们从WinMain()得到的第一个参数.窗口的创建数据(我们几乎沒有使用)可以用来向创建的窗口发送额外数据在这里也设为NULL

  如果你想知道神奇的NULL是什么它实际上被定义为0().而且在C它是被定义为((void*)0)因为是用来作指针用的.所以你有可能在把NULL当整数使用时会得到警告跟你的编译器的警告级別设置有关你可以忽略这个警告也可以用0来代替

  不检查调用是否成功几乎是程序员为找不来程序究竟错在哪里而烦恼的最常发生的情況CreateWindow()可能会调用失败即使你是一个用经验的程序员原因就是可能犯错误的地方实在太多了除非你学会了如何快速查出错误最少你应该给自己一个机会知道是哪里出了错一定记住检查返回值

    if(hwnd == NULL)
    {
        MessageBox(NULL, "Window Creation Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

  在我们创建了窗口并作检查以确定我们有一个正确的句柄后我们使用WinMain()最后的参数来显示窗口再更新它以确认它在屏幕上正确地重画了自己

   ShowWindow(hwnd, nCmdShow);
   UpdateWindow(hwnd);

  nCmdShow是可选的参数,你可以简单地传递SW_SHOWNORMAL即可.但是用从WinMain()传来的参数可给予运行此程序的用戶选择以可视,最大化,最小化等选项...来引导程序的自由.你可以在windows快捷方式的属性中看到这些选项,参数由选项来決定.

第三步消息循环

这是整个程序的心臟程序中几乎所有的控制都从这个点传递

    while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
    return Msg.wParam;

  GetMessage()从你应用的消息队列中取一个消息.任何时候用戶移动鼠标,敲击键盘,点击你窗口的菜单,或做別的什么事,系统会产生消息并输入到你的程序的消息队列中去.调用GetMessage()时你请求将下一个可用的消息从队列中删除并返回给你来处理.如果队列为空,GetMessage()阻塞.如果你不熟悉这个术语,它意味著一直等待直到得到一个消息,才返回给你.

  TranslateMessage()为键盘事件做一些额外的处理,如随著WM_KEYDOWN消息产生WM_CHAR消息.最后DispatchMessage()将消息送到消息应该被送到的窗口. 可能是我们的主窗口或另外一个,或一个控件,有些情況下是一个被系统或其它程序创建的窗口.这不是你需要担心的,因为我们主要关心我们得到消息并送出,系统来做后面的事以确认它到达正确的窗口.

第四步窗口过程

  如果说消息循环是程序的心臟那个窗口过程就是程序的大脑这里所有送到窗口的息被处理

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

  窗口过程在每个息到来时被调用一次HWND参数是息相应的窗口的句柄这很重要因为你可能用相同的类创建了两个或多个窗口并且它们用相同的窗口过程(WndProc())不同点就在不同的窗口有不同的hwnd参数比如我们得到WM_CLOSE消息我们就要销毀那个窗口我们使用了窗口句柄作为我们得到的第一个参数其它的窗口都不会受影响除了那个我们想要操作的之外

  WM_CLOSE是在我们按下关闭按钮或按下Alt+F4组合时产生的这默认会使窗口销毀但我喜欢显式处理它因为这是在程序退出之前做清除检查或询问用戶是否保存文件等事情的绝佳的位置

  当我们调用DestoryWindow()系统向要销毀的窗口送出WM_DESTORY消息这里是我们的窗口并在从系统移除我们的窗口之前删除它剩下的所有的子窗口因为这是我们的程序中唯一的窗口我们準备好了并希望程序退出所以我们调用了PostQuitMessage()这样会向消息循环发出WM_QUIT消息我们不永远收不到这个消息因为它使GetMessage()返回FALSE而且你也可以看到我们的消息循环代码中这时候我们停止处理息并返回最终的结果码我们传递给PostQuitMessage()WM_QUIT中的wParam部分这个返回值有在你的编程为被別的程序调用并且你需要一个确定的返回值时候才有真正有用

第五步沒有第五步

  Phew(喘一口气),到这先告一段落.如果上面还沒有解释清楚的话还是先放下也许我们在讲解更有用的程序时这些东西会变得清晰起来

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