DDB:设备相关位图,属于GDI对象,跟画笔、画刷一样在GDI模块内部存储。
HBITMAP hBitmap;//存储一个指向DDB的句柄

1.创建DDB

(1)hBitmap=CreateBitmap(cx,cy,cPlanes,cBitsPixel,bits)
/
该函数是第一个创建GDI位图对象,也叫设备相关位图(DDB)。
该函数的第三、四个参数的设置有两种情况:
1.cPlanes,cBitsPixel都为1,代表该位图是单色位图。
2.cPlanes,cBitsPixel值等于某个设备环境中的值,通过PLANES和BITSPIXEL索引调用GetDeviceCaps函数获取这两个参数的值。
/

(2)hBitmap=CreateCompatibleBitmap(hdc,cx,cy)
/*
该函数创建一个与设备兼容的GDI位图对象。
什么是设备兼容?其实就是位图和真实的设备(视频显示器)有同样的颜色组织(cPlanes,cBitsPixel)。为什么要与设备兼容?你想想,如果我创建的位图的颜色组织和设备的颜色组织不一样,那么有可能位图想显示的颜色在设备中可能无法显示出来,那不就出错了。

该函数使用参数1指定的设备环境句柄获得GetDeviceCaps返回的信息,然后把这些信息传给CreateBitmap,再返回GDI位图对象句柄。
*/

(3)hBitmap=CreateBitmapIndirect(&bitmap)
/*
参数bitmap是BITMAP类型的结构,该类型结构定义如下:

typedef struct tagBITMAP {
  LONG   bmType;//位图类型,该字段必须为0
  LONG   bmWidth;//横向像素个数
  LONG   bmHeight;//纵向像素个数
  LONG   bmWidthBytes;//不需要设置,Windows会自动计算这个字段的值
  WORD   bmPlanes;//颜色平面数
  WORD   bmBitsPixel;//每个像素的颜色位数
  LPVOID bmBits;//设为NULL或某个带有值的字节类型数组的地址来初始化位图的所有像素位
} BITMAP, *PBITMAP;

可以利用GetObject函数获取指定位图对象的信息。例如:
BITMAP bitmap;
GetObject(hBitmap,sizeof(BITMAP),&bitmap);
//参数1:指定需要获取的GDI位图对象
*/

总结:这些函数都能够创建GDI位图对象,然后Windows为这些位图对象在内存中分配内存并初始化,以及存储位图对象的信息,例如:横纵像素数、颜色平面数、每个像素的颜色位数和每个像素的颜色值。注意,当创建GDI位图对象并使用完该对象后,必须调用DeleteObject函数删除在程序中创建的所有位图。

2.位图的像素位问题

我们都知道一个位图由许多矩形的像素组成,每个像素的颜色位数要么1位(单色位图)、要么多位(彩色位图),那么每个像素有着颜色值。Windows提供了两个函数能在位图被创建后设置或获取位图的所有像素位。
1.SetBitmapBits(hBitmap,cBytes,&bits);
2.GetBitmapBits(hBitmap,cBytes,&bits);

言归正传,假设某个DDB(每个像素8个颜色位)的某个像素的颜色值为0x37,这代表什么颜色。其实,DDB没有颜色表,但当这个DDB显示在屏幕上时,被显示的这个像素对应的是视频卡的颜色查找表中索引为0x37的RGB颜色,所以这就叫设备相关。位图中的像素的颜色与设备相关,所以这就叫设备相关性。分析到这里我们就可以想到,如果我们不知道视频卡的颜色查找表对应的颜色,那么我们在创建DDB时,可以自定义的初始化位图的所有像素位吗?不,这是不可以的。所以,Windows有了这样的规则:对于彩色DDB,不应该调用CreateBitmap或CreateBitmapIndirect或SetBitmapBits函数来设置位图的所有像素位,只有对单色DDB,才可以放心地设置像素位,因为单色只有0或1,谁都知道。

3.内存设备环境

我们创建了GDI位图对象(DDB)后,使用GDI位图对象就要用到内存设备环境,因为只有内存设备环境才可以选入位图,其他种类的设备环境都不可以。而内存设备环境却可以选入其他GDI对象(画笔、画刷、字体、调色板和区域)。一般地设备环境都是对应于特定的图形输出设备(视频显示器或打印机),而内存设备环境只存在于内存,它不是真实的图形输出设备,但它却和这些真实的设备“兼容”,意思是说内存设备环境和真实设备的设备环境的颜色组织相同。为了能实现内存设备环境和真实的设备“兼容”,那么在创建内存设备环境时,要有一个对应于真实设备的设备句柄,例如:
hdcMem=CreateCompatibleDC(hdc);//如果参数设为NULL,那么Windows将创建与视频显示器兼容的内存设备环境。

真实的设备,例如:视频显示器,都是一个点阵设备,而内存设备环境也有一个显示表面,然而这个显示表面只有1个像素宽,1个像素高,单色的。所以,如果要在内存设备环境搞事情,必须得增大显示表面,为此,可以把刚刚创建的GDI位图对象选入内存设备环境。例如:
SelectObject(hdcMem,hBitmap);

注意:SelectObject函数调用是否有效,取决于两种情况:
(1)位图是单色位图。
(2)位图与内存设备环境兼容的真实设备有同样的颜色组织。意思是说,在创建GDI位图对象时,只有位图和真实设备兼容,SelectObject函数才会有效。
一句话概括:如果位图是单×××,畅通无阻,可以直接选入内存设备环境。创建彩色位图需要兼容真实设备,而创建内存设备环境需要兼容真实设备,那么调用SelectObject函数将位图选入内存设备环境才会成功。

我们在内存设备环境中可以像在真实的设备环境中做任何事,例如:画图等。注意,绘图操作是在位图上绘画,原本的位图被新的位图覆盖了。

4.加载位图资源

除了利用位图创建函数来创建位图对象,也可以通过加载位图资源,这种方法可以保证的是加载进来的位图对象和视频显示器有着相同的颜色组织,即与视频显示器兼容。
例如:
hBitmap=LoadBitmap(hInstance,szBitmapName);
/
这个函数的使用跟加载图标、鼠标指针一样,但值得注意的是:使用系统位图时指定系统预设的标识符会报错,需要在windows.h头文件前添加#define OEMRESOURCE。
/

所有通过LoadBitmap函数加载的位图对象,最后都应该用DeleteObject函数删除。

代码例子:

#define OEMRESOURCE
#include
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR szCmdline, int iCmdShow)
{
    static TCHAR szAppName[] = TEXT("Bricks1");
    HWND hWnd;//窗口句柄
    MSG mSg;//消息结构体
    //创建窗口类
    WNDCLASSEX wndClass;

    //设置窗口类各类属性
    wndClass.cbSize = sizeof(WNDCLASSEX);//设置窗口类结构体大小
    wndClass.cbClsExtra = 0;//窗口类尾部的一部分额外的空间
    wndClass.cbWndExtra = 0;
    wndClass.hInstance = hInstance;//应用程序当前的实例句柄
    wndClass.hCursor = LoadCursor(NULL, IDC_HELP);
    wndClass.hIcon = NULL;
    wndClass.hIconSm = NULL;
    wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndClass.lpfnWndProc = WndProc;//回调函数的地址(窗口消息处理程序)
    wndClass.lpszClassName = szAppName;//窗口类的名字,也就是窗口的标识,后面用于创建窗口函数的参数。
    wndClass.lpszMenuName = NULL;//菜单的名字,没有为NULL。
    wndClass.style = CS_HREDRAW | CS_VREDRAW;//窗口类的样式,它的值可以是窗口样式值的任意组合。CS_HREDRAW  CS_VREDRAW,这个是垂直刷新和水平刷新,窗口尺寸改变,重画活动区域。

    //注册对话框类
    if (!RegisterClassEx(&wndClass))
    {
        DWORD error_code = GetLastError();
        MessageBox(NULL, TEXT("This program requires Windows NT!"), TEXT("NumRain"), MB_ICONERROR | MB_OKCANCEL);
        return 0;
    }

    hWnd = CreateWindow(szAppName, TEXT("The Hello Program"), WS_OVERLAPPEDWINDOW, 200, 200, 800, 500, NULL, NULL, hInstance, NULL);

    ShowWindow(hWnd, iCmdShow);
    UpdateWindow(hWnd);
    while (GetMessage(&mSg, NULL, 0, 0))
    {
        TranslateMessage(&mSg);
        DispatchMessage(&mSg);
    }
    return (int)mSg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    PAINTSTRUCT ps;
    int x, y;
    static int cxClient, cyClient,cxSource,cySource;
    static HBITMAP hBitmap;
    HDC hdc, hdcMem;
    HINSTANCE hInstance; 
    BITMAP bitmap;
    switch (message)
    {
    case WM_CREATE:
        //获取应用程序实例句柄
        hInstance = ((LPCREATESTRUCT)lParam)->hInstance;
        /*
        加载位图资源,该位图是一个8*8的单色位图
        除了加载自定义的位图资源,也可以加载系统位图资源。
        例如:
            hBitmap = LoadBitmap(NULL, MAKEINTRESOURCE(OBM_DNARROW));//注意,OBM开头的标识符会提示不存在,需要在windows.h头文件前添加#define OEMRESOURCE
        */
        hBitmap = LoadBitmap(NULL, MAKEINTRESOURCE(OBM_DNARROW));
        //hBitmap = LoadBitmap(hInstance, TEXT("Bricks"));
        //获取加载GDI位图对象的信息
        GetObject(hBitmap, sizeof(BITMAP), &bitmap);
        cxSource = bitmap.bmWidth;
        cySource = bitmap.bmHeight;
        return 0;
    case WM_SIZE:
        cxClient = LOWORD(lParam);
        cyClient = HIWORD(lParam);
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);//开始绘制窗口
        hdcMem = CreateCompatibleDC(hdc);
        SelectObject(hdcMem, hBitmap);

        for (y = 0; y < cyClient; y += cySource)
        {
            for (x = 0; x < cxClient; x += cxSource)
            {
                //将内存设备环境的位图对象传输到整个客户区,看上去像墙一样
                BitBlt(hdc, x, y, cxSource, cySource, hdcMem, 0, 0, SRCCOPY);
            }
        }
        DeleteDC(hdcMem);
        EndPaint(hwnd, &ps);
        return 0;
    case WM_CLOSE:
        if (IDOK == MessageBox(hwnd, TEXT("是否退出?"), TEXT("对话框"), MB_OKCANCEL | MB_DEFBUTTON1 | MB_ICONQUESTION))
        {
            DestroyWindow(hwnd);
        }
        else
        {
            return 0;
        }
    case WM_DESTROY:
        DeleteObject(hBitmap);
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}

5.单色位图格式

由前面可知,Windows提供了一个规则:对于彩色DDB,不应该调用CreateBitmap或CreateBitmapIndirect或SetBitmapBits函数来设置位图的所有像素位,只有对单色DDB,才可以放心地设置像素位,因为单色只有0或1,谁都知道。所以,如果位图只是单色位图,那我就可以自己手动创建一个位图对象,并用一个字节数组初始化位图的所有像素位,因为像素位都是1位,1(白色)或者0(黑色)而已。

代码例子:
/
下面的代码是在客户区重复显示内存设备环境的位图,形成一个真正的砖墙啦!
/

#include
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR szCmdline, int iCmdShow)
{
    static TCHAR szAppName[] = TEXT("Bricks2");
    HWND hWnd;//窗口句柄
    MSG mSg;//消息结构体
    //创建窗口类
    WNDCLASSEX wndClass;

    //设置窗口类各类属性
    wndClass.cbSize = sizeof(WNDCLASSEX);//设置窗口类结构体大小
    wndClass.cbClsExtra = 0;//窗口类尾部的一部分额外的空间
    wndClass.cbWndExtra = 0;
    wndClass.hInstance = hInstance;//应用程序当前的实例句柄
    wndClass.hCursor = LoadCursor(NULL, IDC_HELP);
    wndClass.hIcon = NULL;
    wndClass.hIconSm = NULL;
    wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndClass.lpfnWndProc = WndProc;//回调函数的地址(窗口消息处理程序)
    wndClass.lpszClassName = szAppName;//窗口类的名字,也就是窗口的标识,后面用于创建窗口函数的参数。
    wndClass.lpszMenuName = NULL;//菜单的名字,没有为NULL。
    wndClass.style = CS_HREDRAW | CS_VREDRAW;//窗口类的样式,它的值可以是窗口样式值的任意组合。CS_HREDRAW  CS_VREDRAW,这个是垂直刷新和水平刷新,窗口尺寸改变,重画活动区域。

    //注册对话框类
    if (!RegisterClassEx(&wndClass))
    {
        DWORD error_code = GetLastError();
        MessageBox(NULL, TEXT("This program requires Windows NT!"), TEXT("NumRain"), MB_ICONERROR | MB_OKCANCEL);
        return 0;
    }

    hWnd = CreateWindow(szAppName, TEXT("The Hello Program"), WS_OVERLAPPEDWINDOW, 200, 200, 800, 500, NULL, NULL, hInstance, NULL);

    ShowWindow(hWnd, iCmdShow);
    UpdateWindow(hWnd);
    while (GetMessage(&mSg, NULL, 0, 0))
    {
        TranslateMessage(&mSg);
        DispatchMessage(&mSg);
    }
    return (int)mSg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static BITMAP bitmap = { 0, 8, 8, 2, 1, 1 };
    static BYTE bits[8][2] = { 0xFF, 0, 0x0C, 0, 0x0C, 0, 0x0C, 0,
                               0xFF, 0, 0xC0, 0, 0xC0, 0, 0xC0, 0 };
    PAINTSTRUCT ps;
    int x, y;
    static int cxClient, cyClient,cxSource,cySource;
    static HBITMAP hBitmap;
    HDC hdc, hdcMem;
    switch (message)
    {
    case WM_CREATE:
        bitmap.bmBits = bits;
        hBitmap = CreateBitmapIndirect(&bitmap);
        cxSource = bitmap.bmWidth;
        cySource = bitmap.bmHeight;
        return 0;
    case WM_SIZE:
        cxClient = LOWORD(lParam);
        cyClient = HIWORD(lParam);
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);//开始绘制窗口
        hdcMem = CreateCompatibleDC(hdc);
        SelectObject(hdcMem, hBitmap);

        for (y = 0; y < cyClient; y += cySource)
        {
            for (x = 0; x < cxClient; x += cxSource)
            {
                //将内存设备环境的位图对象传输到整个客户区,看上去像墙一样
                BitBlt(hdc, x, y, cxSource, cySource, hdcMem, 0, 0, SRCCOPY);
            }
        }
        DeleteDC(hdcMem);
        EndPaint(hwnd, &ps);
        return 0;
    case WM_CLOSE:
        if (IDOK == MessageBox(hwnd, TEXT("是否退出?"), TEXT("对话框"), MB_OKCANCEL | MB_DEFBUTTON1 | MB_ICONQUESTION))
        {
            DestroyWindow(hwnd);
        }
        else
        {
            return 0;
        }
    case WM_DESTROY:
        DeleteObject(hBitmap);
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}

6.位图画刷

呃,这个其实没什么好说,就是将位图对象添加进画刷,然后用这个画刷绘制客户区背景而已。

代码例子:

#include
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR szCmdline, int iCmdShow)
{
    static TCHAR szAppName[] = TEXT("Bricks2");
    HWND hWnd;//窗口句柄
    MSG mSg;//消息结构体
    //创建窗口类
    WNDCLASSEX wndClass;
    HBITMAP hBitmap;
    HBRUSH hBrush;
    hBitmap = LoadBitmap(hInstance, TEXT("Bricks"));
    hBrush = CreatePatternBrush(hBitmap);
    DeleteObject(hBitmap);

    //设置窗口类各类属性
    wndClass.cbSize = sizeof(WNDCLASSEX);//设置窗口类结构体大小
    wndClass.cbClsExtra = 0;//窗口类尾部的一部分额外的空间
    wndClass.cbWndExtra = 0;
    wndClass.hInstance = hInstance;//应用程序当前的实例句柄
    wndClass.hCursor = LoadCursor(NULL, IDC_HELP);
    wndClass.hIcon = NULL;
    wndClass.hIconSm = NULL;
    wndClass.hbrBackground = hBrush;
    wndClass.lpfnWndProc = WndProc;//回调函数的地址(窗口消息处理程序)
    wndClass.lpszClassName = szAppName;//窗口类的名字,也就是窗口的标识,后面用于创建窗口函数的参数。
    wndClass.lpszMenuName = NULL;//菜单的名字,没有为NULL。
    wndClass.style = CS_HREDRAW | CS_VREDRAW;//窗口类的样式,它的值可以是窗口样式值的任意组合。CS_HREDRAW  CS_VREDRAW,这个是垂直刷新和水平刷新,窗口尺寸改变,重画活动区域。

    //注册对话框类
    if (!RegisterClassEx(&wndClass))
    {
        DWORD error_code = GetLastError();
        MessageBox(NULL, TEXT("This program requires Windows NT!"), TEXT("NumRain"), MB_ICONERROR | MB_OKCANCEL);
        return 0;
    }

    hWnd = CreateWindow(szAppName, TEXT("The Hello Program"), WS_OVERLAPPEDWINDOW, 200, 200, 800, 500, NULL, NULL, hInstance, NULL);

    ShowWindow(hWnd, iCmdShow);
    UpdateWindow(hWnd);
    while (GetMessage(&mSg, NULL, 0, 0))
    {
        TranslateMessage(&mSg);
        DispatchMessage(&mSg);
    }
    return (int)mSg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_CLOSE:
        if (IDOK == MessageBox(hwnd, TEXT("是否退出?"), TEXT("对话框"), MB_OKCANCEL | MB_DEFBUTTON1 | MB_ICONQUESTION))
        {
            DestroyWindow(hwnd);
        }
        else
        {
            return 0;
        }
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}

7.在位图上绘图

先创建一个位图对象,再将位图对象选入内存设备环境,然后在内存设备环境绘图,那么原本的位图就被绘图后的位图给覆盖了。接着调用BitBlt或者StretchBlt函数将内存设备环境的位图复制进客户区设备环境。

#include
#include "resource.h"
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR szCmdline, int iCmdShow)
{
    static TCHAR szAppName[] = TEXT("HelloBit");
    HWND hWnd;//窗口句柄
    MSG mSg;//消息结构体
    //创建窗口类
    WNDCLASSEX wndClass;

    //设置窗口类各类属性
    wndClass.cbSize = sizeof(WNDCLASSEX);//设置窗口类结构体大小
    wndClass.cbClsExtra = 0;//窗口类尾部的一部分额外的空间
    wndClass.cbWndExtra = 0;
    wndClass.hInstance = hInstance;//应用程序当前的实例句柄
    wndClass.hCursor = LoadCursor(NULL, IDC_HELP);
    wndClass.hIcon = NULL;
    wndClass.hIconSm = NULL;
    wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndClass.lpfnWndProc = WndProc;//回调函数的地址(窗口消息处理程序)
    wndClass.lpszClassName = szAppName;//窗口类的名字,也就是窗口的标识,后面用于创建窗口函数的参数。
    wndClass.lpszMenuName = szAppName;//菜单的名字,没有为NULL。
    wndClass.style = CS_HREDRAW | CS_VREDRAW;//窗口类的样式,它的值可以是窗口样式值的任意组合。CS_HREDRAW  CS_VREDRAW,这个是垂直刷新和水平刷新,窗口尺寸改变,重画活动区域。

    //注册对话框类
    if (!RegisterClassEx(&wndClass))
    {
        DWORD error_code = GetLastError();
        MessageBox(NULL, TEXT("This program requires Windows NT!"), TEXT("NumRain"), MB_ICONERROR | MB_OKCANCEL);
        return 0;
    }

    hWnd = CreateWindow(szAppName, TEXT("The Hello Program"), WS_OVERLAPPEDWINDOW, 200, 200, 800, 500, NULL, NULL, hInstance, NULL);

    ShowWindow(hWnd, iCmdShow);
    UpdateWindow(hWnd);
    while (GetMessage(&mSg, NULL, 0, 0))
    {
        TranslateMessage(&mSg);
        DispatchMessage(&mSg);
    }
    return (int)mSg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HBITMAP hBitmap;
    static HDC hdcMem;
    static int cxBitmap, cyBitmap, cxClient, cyClient, iSize = ID_SIZE_BIG;
    static TCHAR *szText = TEXT("Hello,world!");
    HDC hdc;
    HMENU hMenu;
    int x, y;
    PAINTSTRUCT ps;
    SIZE size;
    switch (message)
    {
    case WM_CREATE:
        hdc = GetDC(hwnd);
        hdcMem = CreateCompatibleDC(hdc);
        //获取文本字符串的横纵像素数
        GetTextExtentPoint32(hdc, szText, lstrlen(szText), &size);
        cxBitmap = size.cx;
        cyBitmap = size.cy;
        //创建和szText字符串一样宽度与高度的兼容性GDI位图对象
        hBitmap = CreateCompatibleBitmap(hdc, cxBitmap, cyBitmap);

        ReleaseDC(hwnd, hdc);
        SelectObject(hdcMem, hBitmap);
        TextOut(hdcMem, 0, 0, szText, lstrlen(szText));
        return 0;
    case WM_SIZE:
        cxClient = LOWORD(lParam);
        cyClient = HIWORD(lParam);
        return 0;
    case WM_COMMAND:
        hMenu = GetMenu(hwnd);
        switch (LOWORD(wParam))
        {
        case ID_SIZE_BIG:
        case ID_SIZE_SMALL:
            //在选择菜单项之前,初始时是选中ID_SIZE_BIG的
            CheckMenuItem(hMenu, iSize, MF_UNCHECKED);
            iSize = LOWORD(wParam);
            CheckMenuItem(hMenu, iSize, MF_CHECKED);
            InvalidateRect(hwnd, NULL, TRUE);
            break;
        }
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);//开始绘制窗口
        switch (iSize)
        {
        case ID_SIZE_BIG:
            StretchBlt(hdc, 0, 0, cxClient, cyClient, hdcMem, 0, 0, cxBitmap, cyBitmap, SRCCOPY);
            break;
        case ID_SIZE_SMALL:
            for (y = 0; y < cyClient; y += cyBitmap)
            {
                for (x = 0; x < cxClient; x += cxBitmap)
                {
                    //将内存设备环境的位图对象传输到整个客户区,看上去像墙一样
                    BitBlt(hdc, x, y, cxBitmap, cyBitmap, hdcMem, 0, 0, SRCCOPY);
                }
            }
        }
        EndPaint(hwnd, &ps);
        return 0;
    case WM_CLOSE:
        if (IDOK == MessageBox(hwnd, TEXT("是否退出?"), TEXT("对话框"), MB_OKCANCEL | MB_DEFBUTTON1 | MB_ICONQUESTION))
        {
            DestroyWindow(hwnd);
        }
        else
        {
            return 0;
        }
    case WM_DESTROY:
        DeleteDC(hdcMem);
        DeleteObject(hBitmap);
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}

8.阴影位图

按下鼠标左键然后拖动鼠标,就用黑色画笔绘画,按下鼠标右键然后拖动鼠标,就用白色画笔绘画。值得注意的是,我们在代码例子里调用了SetCapture(hwnd),说明当鼠标在客户区外,也能接收到鼠标消息,我们可以这样做个小测试,先注释掉WM_PAINT消息处理中的BitBlt(hdc, 0,0, cxClient, cyClient, hdcMem, 0, 0, SRCCOPY);,发现没有,如果按下鼠标左键并拖动鼠标,当拖动到客户区外,其实在内存设备环境里已经保存了在客户区外绘制的图,因为内存设备环境的尺寸是依据客户区放最大后的尺寸,然后我们改变窗口大小,客户区啥都没有了,这是因为没有继续从内存设备环境复制保存的位图到客户区设备环境上。

代码例子:

#include
#include
#include "resource.h"
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR szCmdline, int iCmdShow)
{
    static TCHAR szAppName[] = TEXT("Sketch");
    HWND hWnd;//窗口句柄
    MSG mSg;//消息结构体
    //创建窗口类
    WNDCLASSEX wndClass;

    //设置窗口类各类属性
    wndClass.cbSize = sizeof(WNDCLASSEX);//设置窗口类结构体大小
    wndClass.cbClsExtra = 0;//窗口类尾部的一部分额外的空间
    wndClass.cbWndExtra = 0;
    wndClass.hInstance = hInstance;//应用程序当前的实例句柄
    wndClass.hCursor = LoadCursor(NULL, IDC_HELP);
    wndClass.hIcon = NULL;
    wndClass.hIconSm = NULL;
    wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndClass.lpfnWndProc = WndProc;//回调函数的地址(窗口消息处理程序)
    wndClass.lpszClassName = szAppName;//窗口类的名字,也就是窗口的标识,后面用于创建窗口函数的参数。
    wndClass.lpszMenuName = NULL;//菜单的名字,没有为NULL。
    wndClass.style = CS_HREDRAW | CS_VREDRAW;//窗口类的样式,它的值可以是窗口样式值的任意组合。CS_HREDRAW  CS_VREDRAW,这个是垂直刷新和水平刷新,窗口尺寸改变,重画活动区域。

    //注册对话框类
    if (!RegisterClassEx(&wndClass))
    {
        DWORD error_code = GetLastError();
        MessageBox(NULL, TEXT("This program requires Windows NT!"), TEXT("NumRain"), MB_ICONERROR | MB_OKCANCEL);
        return 0;
    }

    hWnd = CreateWindow(szAppName, TEXT("The Hello Program"), WS_OVERLAPPEDWINDOW, 200, 200, 800, 500, NULL, NULL, hInstance, NULL);

    ShowWindow(hWnd, iCmdShow);
    UpdateWindow(hWnd);
    while (GetMessage(&mSg, NULL, 0, 0))
    {
        TranslateMessage(&mSg);
        DispatchMessage(&mSg);
    }
    return (int)mSg.wParam;
}

void GetLargestDisplayMode(int *pcxBitmap, int *pcyBitmap)
{
    DEVMODE devmode;
    int iModeNum = 0;
    *pcxBitmap = *pcyBitmap = 0;

    ZeroMemory(&devmode, sizeof(DEVMODE));
    devmode.dmSize = sizeof(DEVMODE);

    while (EnumDisplaySettings(NULL, iModeNum++, &devmode))
    {
        *pcxBitmap = max(*pcxBitmap, (int)devmode.dmPelsWidth);
        *pcyBitmap = max(*pcyBitmap, (int)devmode.dmPelsHeight);
    }
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static BOOL fLeftButtonDown, fRightButtonDown;
    static HBITMAP hBitmap;
    static HDC hdcMem;
    static int cxBitmap, cyBitmap, cxClient, cyClient, xMouse,yMouse;
    HDC hdc;
    PAINTSTRUCT ps;
    switch (message)
    {
    case WM_CREATE:
        GetLargestDisplayMode(&cxBitmap, &cyBitmap);
        hdc = GetDC(hwnd);
        hBitmap = CreateCompatibleBitmap(hdc, cxBitmap, cyBitmap);
        hdcMem = CreateCompatibleDC(hdc);
        ReleaseDC(hwnd, hdc);
        if (!hBitmap)
        {
            DeleteDC(hdcMem);
            return -1;
        }
        SelectObject(hdcMem, hBitmap);
        PatBlt(hdcMem, 0, 0, cxBitmap, cyBitmap, WHITENESS);
        return 0;
    case WM_SIZE:
        cxClient = LOWORD(lParam);
        cyClient = HIWORD(lParam);
        return 0;
    case WM_LBUTTONDOWN:
        if (!fRightButtonDown)
        {
            SetCapture(hwnd);
        }
        xMouse = GET_X_LPARAM(lParam);
        yMouse = GET_Y_LPARAM(lParam);
        fLeftButtonDown = TRUE;
        return 0;
    case WM_LBUTTONUP:
        if (fLeftButtonDown)
        {
            SetCapture(NULL);
        }
        fLeftButtonDown = FALSE;
        return 0;
    case WM_RBUTTONDOWN:
        if (!fLeftButtonDown)
        {
            SetCapture(hwnd);
        }
        xMouse = GET_X_LPARAM(lParam);
        yMouse = GET_Y_LPARAM(lParam);
        fRightButtonDown = TRUE;
        return 0;
    case WM_RBUTTONUP:
        if (fRightButtonDown)
        {
            SetCapture(NULL);
        }
        fRightButtonDown = FALSE;
        return 0;
    case WM_MOUSEMOVE:
        if (!fLeftButtonDown&&!fRightButtonDown)
        {
            return 0;
        }
        hdc = GetDC(hwnd);
        SelectObject(hdc, GetStockObject(fLeftButtonDown ? BLACK_PEN : WHITE_PEN));
        SelectObject(hdcMem, GetStockObject(fLeftButtonDown ? BLACK_PEN : WHITE_PEN));

        MoveToEx(hdc, xMouse, yMouse, NULL);
        MoveToEx(hdcMem, xMouse, yMouse, NULL);

        xMouse = GET_X_LPARAM(lParam);
        yMouse = GET_Y_LPARAM(lParam);

        LineTo(hdc, xMouse, yMouse);
        LineTo(hdcMem, xMouse, yMouse);

        ReleaseDC(hwnd, hdc);
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);//开始绘制窗口
        BitBlt(hdc, 0,0, cxClient, cyClient, hdcMem, 0, 0, SRCCOPY);
        EndPaint(hwnd, &ps);
        return 0;
    case WM_CLOSE:
        if (IDOK == MessageBox(hwnd, TEXT("是否退出?"), TEXT("对话框"), MB_OKCANCEL | MB_DEFBUTTON1 | MB_ICONQUESTION))
        {
            DestroyWindow(hwnd);
        }
        else
        {
            return 0;
        }
    case WM_DESTROY:
        DeleteDC(hdcMem);
        DeleteObject(hBitmap);
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}