第14章 位图和位块传输_14.4 GDI位图对象(2)

14.4.7 在位图上绘图

(1)在内存设备环境中绘图(与真实DC不同的是,内存DC的显示表面是个位图)

(2)GetTextExtentPoint32函数:用于确定文本字符串的像素大小。(此大小就是与视频显示兼容的位图的尺寸)。

  参数

说明

hdc

设备环境句柄

lpString

文本字符串,如szText

cbString

文本字符串中字符的个数。如lstrlen(szText)

lpSize

指向一个结构体,用来存放结果

(3)当显示器的颜色深度和大小改变时,windows会自动改变内存设备环境的彩色分辩率。也就是内存设备环境与视频设备环境仍然会保持兼容。所以在WM_DISPLAYCHANGE消息中不用去关心这个问题。

【HelloBit程序】

 效果图

  

/*------------------------------------------------------------
HELLOBIT.C -- Bitmap Demostration
(c) Charles Petzold, 1998
------------------------------------------------------------*/
#include <windows.h>
#include "resource.h"
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   PSTR szCmdLine, int iCmdShow)
{
    static TCHAR szAppName[] = TEXT("HelloBit");
    HWND         hwnd;
    MSG          msg;
    WNDCLASSEX     wndclass;
    wndclass.style = CS_HREDRAW | CS_VREDRAW;
    wndclass.cbSize = sizeof(WNDCLASSEX);
    wndclass.lpfnWndProc = WndProc;
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hInstance = hInstance;
    wndclass.hIcon = LoadIcon(hInstance, szAppName);
    wndclass.hIconSm = LoadIcon(hInstance, szAppName);
    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndclass.lpszMenuName = szAppName;
    wndclass.lpszClassName = szAppName;
    if (!RegisterClassEx(&wndclass))
    {
        MessageBox(NULL, TEXT("This program requires Windows NT!"),
                   szAppName, MB_ICONERROR);
        return 0;
    }

    hwnd = CreateWindow(szAppName,                  // window class name
                        TEXT("HelloBit"), // window caption
                        WS_OVERLAPPEDWINDOW,        // window style
                        CW_USEDEFAULT,              // initial x position
                        CW_USEDEFAULT,              // initial y position
                        CW_USEDEFAULT,              // initial x size
                        CW_USEDEFAULT,              // initial y size
                        NULL,                       // parent window handle
                        NULL,                       // window menu handle
                        hInstance,                  // program instance handle
                        NULL);                     // creation parameters

    ShowWindow(hwnd, iCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    HDC            hdc;
    static  HDC    hdcMem;
    PAINTSTRUCT    ps;
    static int     cxBitmap, cyBitmap, cxClient, cyClient, iSize = IDM_BIG;
    static HBITMAP hBitmap;
    static TCHAR*  szText = TEXT("Hello,World!");
    SIZE size;
    HMENU          hMenu;
    switch (message)
    {
    case WM_CREATE:
        hdc = GetDC(hwnd);
        hdcMem = CreateCompatibleDC(hdc);
        GetTextExtentPoint32(hdc, szText, lstrlen(szText), &size);
        cxBitmap = size.cx;
        cyBitmap = size.cy;
        hBitmap = CreateCompatibleBitmap(hdc, cxBitmap, cyBitmap);
        ReleaseDC(hwnd, hdc);
        SelectObject(hdcMem, hBitmap);
        TextOut(hdcMem, 0, 0, szText, lstrlen(szText));
        return 0;
    case WM_COMMAND:
        hMenu = GetMenu(hwnd);
        switch (LOWORD(wParam))  //菜单ID
        {
        case IDM_BIG:
        case IDM_SMALL:
            CheckMenuItem(hMenu, iSize, MF_UNCHECKED);
            iSize = LOWORD(wParam);
            CheckMenuItem(hMenu, iSize, MF_CHECKED);
            InvalidateRect(hwnd, NULL, TRUE);
            break;
        }
        return 0;
    case WM_SIZE:
        cxClient = LOWORD(lParam);
        cyClient = HIWORD(lParam);
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);

        switch (iSize)
        {
        case IDM_BIG:
            StretchBlt(hdc, 0, 0, cxClient, cyClient,
                       hdcMem, 0, 0, cxBitmap, cyBitmap, SRCCOPY);
            break;
        case IDM_SMALL:
            for (int y = 0; y < cyClient; y += cyBitmap)
                for (int x = 0; x < cxClient; x += cxBitmap)
                {
                    BitBlt(hdc, x, y, cxBitmap, cyBitmap, hdcMem, 0, 0, SRCCOPY);
                }
            break;
        }

        EndPaint(hwnd, &ps);
        return 0;

    case WM_DESTROY:
        DeleteDC(hdcMem);
        DeleteObject(hBitmap);
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}

//resource.h

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by HelloBit.rc
//
#define IDM_BIG                         40001
#define IDM_SMALL                       40002

// Next default values for new objects
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        102
#define _APS_NEXT_COMMAND_VALUE         40003
#define _APS_NEXT_CONTROL_VALUE         1000
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif

//HelloBit.rc

//Microsoft Developer Studio generated resource script.
//
#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "afxres.h"

/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS

/////////////////////////////////////////////////////////////////////////////
// English (U.S.) resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
#ifdef _WIN32
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
#endif //_WIN32

#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//

1 TEXTINCLUDE DISCARDABLE 
BEGIN
    "resource.h"
END

2 TEXTINCLUDE DISCARDABLE 
BEGIN
    "#include ""afxres.h""
"
    ""
END

3 TEXTINCLUDE DISCARDABLE 
BEGIN
    "
"
    ""
END

#endif    // APSTUDIO_INVOKED


/////////////////////////////////////////////////////////////////////////////
//
// Menu
//

HELLOBIT MENU DISCARDABLE 
BEGIN
    POPUP "&Size"
    BEGIN
        MENUITEM "&Big",                        IDM_BIG, CHECKED
        MENUITEM "&Small",                      IDM_SMALL
    END
END

#endif    // English (U.S.) resources
/////////////////////////////////////////////////////////////////////////////



#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//


/////////////////////////////////////////////////////////////////////////////
#endif    // not APSTUDIO_INVOKED
14.4.8 阴影位图

(1)EnumDisplaySettings:该函数用来获取显示设备的参数信息,要想获取所有显示设备的所有信息,就要循环使用该函数。

参数

说明

lpszDeviceName

设备的名称,以NULL结尾的设备名称字符串。

当NULL表示当前使用的显示设备

iModeNum

①获取设备类型信息。该值可以是索引号或也可以使下值之一:

ENUM_CURRENT_SETTINGS:获取当前显示设备的配置

ENUM_REGISTRY_SETTINGS:注册表中所有当前存储的显示设备的配置。

②如果从0开始,要获得所有显示设备的物理模式,就要循环调用EnumDisplaySettings函数,直到返回返回值为FALSE。

③当以iModeNum为0而调用函数时,操作系统将初始化和填充相关显示设备信息。

④当以iModeNum为非0而调用函数时,操作系统将返回最后一次填充的信息。

lpDevMode

该参数是一个结构体用来接收物理模式的信息参数,在使用此结构体前要先初始化该结构体。函数会获取到以下五个字段的值:dmBitsPerpel、dmPelsWidth、dmPelsHeight(设备宽度和高度,以像素为单位,不会随分辨率而改变,可以用来测量显示器的大小!)、dmDisplayFlags、dmDisplayFrequency。

(2)【Sketch程序】——先将图绘到内存设备,再绘到显示设备的DC环境。
效果图

 

/*------------------------------------------------------------
SKETCH.C -- Shadow Bitmap Demonstration
(c) Charles Petzold, 1998
------------------------------------------------------------*/
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   PSTR szCmdLine, int iCmdShow)
{
    static TCHAR szAppName[] = TEXT("Sketch");
    HWND         hwnd;
    MSG          msg;
    WNDCLASSEX     wndclass;
    wndclass.style = CS_HREDRAW | CS_VREDRAW;
    wndclass.cbSize = sizeof(WNDCLASSEX);
    wndclass.lpfnWndProc = WndProc;
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hInstance = hInstance;
    wndclass.hIcon = LoadIcon(hInstance, szAppName);
    wndclass.hIconSm = LoadIcon(hInstance, szAppName);
    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndclass.lpszMenuName = NULL;
    wndclass.lpszClassName = szAppName;
    if (!RegisterClassEx(&wndclass))
    {
        MessageBox(NULL, TEXT("This program requires Windows NT!"),
                   szAppName, MB_ICONERROR);
        return 0;
    }

    hwnd = CreateWindow(szAppName,                  // window class name
                        TEXT("Sketch"), // window caption
                        WS_OVERLAPPEDWINDOW,        // window style
                        CW_USEDEFAULT,              // initial x position
                        CW_USEDEFAULT,              // initial y position
                        CW_USEDEFAULT,              // initial x size
                        CW_USEDEFAULT,              // initial y size
                        NULL,                       // parent window handle
                        NULL,                       // window menu handle
                        hInstance,                  // program instance handle
                        NULL);                     // creation parameters

    //因为在CreateWindow会发送WM_CREATE,在WM_CREATE消息中会创建一个位图
    //如果位图太大,会导致创建失败,WM_CREATE会返回-1,如下处理这种情况.
    if (hwnd == NULL)
    {
        MessageBox(NULL, TEXT("Not enough memory to create bitmap!"),
                   szAppName, MB_ICONERROR);
        return 0;
    }
    ShowWindow(hwnd, iCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}
//获得最大可显示的位图
void GetLargestDisplayMode(int* pcxBitmap, int* pcyBitmap)
{
    DEVMODE devmode;
    int iModeNum = 0;
    *pcxBitmap = *pcyBitmap = 0;
    ZeroMemory(&devmode, sizeof(DEVMODE));
    devmode.dmSize = sizeof(DEVMODE);   //这个要记得设置!
    //EnumDisplaySettins第1个参数为NULL,表示当前正在使用的显示设备,可以不止一个。
    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:
        hdc = GetDC(hwnd);
        GetLargestDisplayMode(&cxBitmap, &cyBitmap);
        hBitmap = CreateCompatibleBitmap(hdc, cxBitmap, cyBitmap);
        hdcMem = CreateCompatibleDC(hdc);
        ReleaseDC(hwnd, hdc);
        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 = LOWORD(lParam);
        yMouse = HIWORD(lParam);
        fLeftButtonDown = TRUE;
        return 0;
    case WM_LBUTTONUP:
        if (fLeftButtonDown)
            //SetCapture(NULL);
            ReleaseCapture();//课本用SetCapture(NULL);

        fLeftButtonDown = FALSE;
        return 0;

        //右键用来擦图(用白色绘图,等于在擦图)
    case WM_RBUTTONDOWN:
        if (!fLeftButtonDown) //因为当左键按下时,说明正在用白色擦图,此时鼠标是被捕捉的
            SetCapture(hwnd);
        xMouse = LOWORD(lParam);
        yMouse = HIWORD(lParam);
        fRightButtonDown = TRUE;
        return 0;
    case WM_RBUTTONUP:
        if (fRightButtonDown)
            //SetCapture(NULL);
            ReleaseCapture();
        fRightButtonDown = FALSE;
        return 0;
    case WM_MOUSEMOVE:
        //己知问题:当鼠标在客户区内按下一个键时并拖动绘图,到客户区外依次释放两个键,
        //重回客户区时,这里鼠标己经松开,但仍会画图——原因是当客户区外释放时,就ReleaseCapture
        //了,导致其中一个鼠标按键的ButtonUp消息收不到,即仍处于fButtonDown状态,从而下列的判断会通过,
        //所以仍会执行后面的代码
        if (!fLeftButtonDown && !fRightButtonDown) //左右键都没按下时,退出
            return 0;
        hdc = GetDC(hwnd);
        //左键按下时,选黑色画笔,右键时白色画笔。同时按下时仍然是黑色的画笔
        SelectObject(hdc, GetStockObject(fLeftButtonDown ? BLACK_PEN : WHITE_PEN));
        SelectObject(hdcMem, GetStockObject(fLeftButtonDown ? BLACK_PEN : WHITE_PEN));
        //同时在屏幕dc与内存dc中绘图
        MoveToEx(hdc, xMouse, yMouse, NULL);
        MoveToEx(hdcMem, xMouse, yMouse, NULL);
        xMouse = LOWORD(lParam);
        yMouse = HIWORD(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_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}

14.4.9 在菜单中使用位图

(1)GetMenuItemInfo(hMenu,uMenuItem,fByPosition,lpmii);

①uMenuItem:菜单ID或位置索引(看fByPositon是否是TRUE)

②lpmii,指一个MENUITEMINFO结构体

MENUITEMINFO结构体

字段

说明

cbSize

结构的大小(字节)

fMask

MIIM_CHECKMARKS:获取或设置hbmpChecked和hbmpUnchecked成员

MIIM_DATA:获取或设置dwItemData成员

MIIM_ID :获取或设置wID成员

MIIM_STATE: 获取或设置fState成员

MIIM_SUBMENU: 获取或设置hSubMenu成员

MIIM_TYPE :获取或设置fType和dwTypeData成员

fType

菜单项类型

MFT_BITMAP:使用一个位图显示菜单项.dwTypeData低位字是该位图的句柄.并且cch被忽视

MFT_MENUBARBREAK:放置菜单项在新行上(适用于菜单栏)或在新列内

MFT_SEPARATOR:指定那个菜单项是一个分隔条

MFT_STRING:用一个文本字符串显示菜单项.dwTypeData成员指示一个以NULL结尾的字符串

MFT_RADIOCHECK:如果hbmpChecked成员是NULL,显示选中的菜单项使用一个单选按钮来代替一个复选标记

fState

菜单项的状态

MFS_CHECKED:复选的菜单项.至于更多关于菜单项选中的信息,看hbmpChecked成员

MFS_DISABLED:菜单项无效并变灰使得它不能被选择.等效于MFS_GRAYED

MFS_ENABLED: 激活菜单项使它可以被选择

MFS_GRAYED:菜单项无效并变灰使得它不能被选择.等效于MFS_DISABLED

MFS_HILITE:菜单项高亮显示

MFS_UNCHECKED:取消复选菜单项

MFS_UNHILITE:移除菜单项的高亮显示,这是默认状态

wID

菜单项ID, 只有在设置了fMask的MIIM_ID时才能使用

hSubMenu

菜单项相关联的下拉菜单或子菜单的的句柄。如果菜单项不是一个打开的下拉菜单或子菜单,那这个成员是NULL, 该项只有在设置了fMask的MIIM_SUBMENU时才能使用

hbmpChecked

菜单项被选中时显示在一侧的位图的句柄。

hbmpUnchecked

菜单项没有被选中时显示在一侧的位图的句柄

dwItemData

应用程序定义的菜单项相关联的值,该项只有在设置了fMask的MIIM_DATA时才能使用

dwTypeData

菜单项的内容,它的具体意义依赖于fTYPE值,并且它只能在fMask设置了MIIM_TYPE标记时才能被使用;

要获取一个MFT_STRING类型的菜单项,首先要得到该字符串的大小,通过设置MENUITEMINFO结构的dwTypeData值为空并调用函数GetMenuItemInfo得到的cch值就是字符串的大小,然后分配一个字符串大小的缓冲区,把指向缓冲区的指针存赋给dwTypeData并再次调用GetMenuItemInfo函数用字符串来填充缓冲区。

如果获取其它类型的菜单项,GetMenuItemInfo函数会赋给dwTypeData一个类型由fType成员指定的值。当使用SetMenuItemInfo函数时,dwTypeData必须包含一个类型由fType成员指定的值,该项只有在设置了fMask成员的MIIM_STRING标记时才能使用。

cch

当检索一个MFT_STRING类型菜单项的信息时,为菜单项文本(TCHAR)的长度

hbmpItem

菜单项上显示位图的句柄,它可能是以下标记中的一个,该项只有在设置了fMask成员的MIIM_BITMAP标记时才能使用。

HBMMENU_CALLBACK:一个由拥有该菜单的窗口绘制的位图

HBMMENU_MBAR_CLOSE:菜单栏的关闭按钮

HBMMENU_MBAR_MINIMIZE:菜单栏的最小化按钮

HBMMENU_MBAR_RESTORE:菜单栏的还原按钮

……

(2)使用位图做菜单项时的注意事项

①Windows会调整菜单栏高度来适应最高的位图,其他位图(包括文字)和菜单上沿顶端对齐。

②如果把一个位图放入顶级菜单,那么SM_CYMENU调用和GetSystemMetrics得到的菜单高度值不同。

③当菜单包含文本时,Windows会自动添加键盘接口。但一旦加入位图,就没有键盘接口了。可以使用WM_MENUCHAR消息,在按下Alt键以及没有映射到任何菜单项的某个字母键时,Windows会向应用程序发送一个WM_MENUCHAR消息。wParam为被按下键的ASCII码。如果和某个菜单项对应,就要向Windows返回一个双字(即return的返回值),并将高位字设为2(即MNC_EXECUTE),将低位字设成对应菜单项的索引。Windows会向窗口发送WM_COMMAND消息。

 【GrafMenu程序】
效果图

/*------------------------------------------------------------
SKETCH.C -- Shadow Bitmap Demonstration
(c) Charles Petzold, 1998
------------------------------------------------------------*/
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   PSTR szCmdLine, int iCmdShow)
{
    static TCHAR szAppName[] = TEXT("Sketch");
    HWND         hwnd;
    MSG          msg;
    WNDCLASSEX     wndclass;
    wndclass.style = CS_HREDRAW | CS_VREDRAW;
    wndclass.cbSize = sizeof(WNDCLASSEX);
    wndclass.lpfnWndProc = WndProc;
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hInstance = hInstance;
    wndclass.hIcon = LoadIcon(hInstance, szAppName);
    wndclass.hIconSm = LoadIcon(hInstance, szAppName);
    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndclass.lpszMenuName = NULL;
    wndclass.lpszClassName = szAppName;
    if (!RegisterClassEx(&wndclass))
    {
        MessageBox(NULL, TEXT("This program requires Windows NT!"),
                   szAppName, MB_ICONERROR);
        return 0;
    }

    hwnd = CreateWindow(szAppName,                  // window class name
                        TEXT("Sketch"), // window caption
                        WS_OVERLAPPEDWINDOW,        // window style
                        CW_USEDEFAULT,              // initial x position
                        CW_USEDEFAULT,              // initial y position
                        CW_USEDEFAULT,              // initial x size
                        CW_USEDEFAULT,              // initial y size
                        NULL,                       // parent window handle
                        NULL,                       // window menu handle
                        hInstance,                  // program instance handle
                        NULL);                     // creation parameters

    //因为在CreateWindow会发送WM_CREATE,在WM_CREATE消息中会创建一个位图
    //如果位图太大,会导致创建失败,WM_CREATE会返回-1,如下处理这种情况.
    if (hwnd == NULL)
    {
        MessageBox(NULL, TEXT("Not enough memory to create bitmap!"),
                   szAppName, MB_ICONERROR);
        return 0;
    }
    ShowWindow(hwnd, iCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}
//获得最大可显示的位图
void GetLargestDisplayMode(int* pcxBitmap, int* pcyBitmap)
{
    DEVMODE devmode;
    int iModeNum = 0;
    *pcxBitmap = *pcyBitmap = 0;
    ZeroMemory(&devmode, sizeof(DEVMODE));
    devmode.dmSize = sizeof(DEVMODE);   //这个要记得设置!
    //EnumDisplaySettins第1个参数为NULL,表示当前正在使用的显示设备,可以不止一个。
    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:
        hdc = GetDC(hwnd);
        GetLargestDisplayMode(&cxBitmap, &cyBitmap);
        hBitmap = CreateCompatibleBitmap(hdc, cxBitmap, cyBitmap);
        hdcMem = CreateCompatibleDC(hdc);
        ReleaseDC(hwnd, hdc);
        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 = LOWORD(lParam);
        yMouse = HIWORD(lParam);
        fLeftButtonDown = TRUE;
        return 0;
    case WM_LBUTTONUP:
        if (fLeftButtonDown)
            //SetCapture(NULL);
            ReleaseCapture();//课本用SetCapture(NULL);

        fLeftButtonDown = FALSE;
        return 0;

        //右键用来擦图(用白色绘图,等于在擦图)
    case WM_RBUTTONDOWN:
        if (!fLeftButtonDown) //因为当左键按下时,说明正在用白色擦图,此时鼠标是被捕捉的
            SetCapture(hwnd);
        xMouse = LOWORD(lParam);
        yMouse = HIWORD(lParam);
        fRightButtonDown = TRUE;
        return 0;
    case WM_RBUTTONUP:
        if (fRightButtonDown)
            //SetCapture(NULL);
            ReleaseCapture();
        fRightButtonDown = FALSE;
        return 0;
    case WM_MOUSEMOVE:
        //己知问题:当鼠标在客户区内按下一个键时并拖动绘图,到客户区外依次释放两个键,
        //重回客户区时,这里鼠标己经松开,但仍会画图——原因是当客户区外释放时,就ReleaseCapture
        //了,导致其中一个鼠标按键的ButtonUp消息收不到,即仍处于fButtonDown状态,从而下列的判断会通过,
        //所以仍会执行后面的代码
        if (!fLeftButtonDown && !fRightButtonDown) //左右键都没按下时,退出
            return 0;
        hdc = GetDC(hwnd);
        //左键按下时,选黑色画笔,右键时白色画笔。同时按下时仍然是黑色的画笔
        SelectObject(hdc, GetStockObject(fLeftButtonDown ? BLACK_PEN : WHITE_PEN));
        SelectObject(hdcMem, GetStockObject(fLeftButtonDown ? BLACK_PEN : WHITE_PEN));
        //同时在屏幕dc与内存dc中绘图
        MoveToEx(hdc, xMouse, yMouse, NULL);
        MoveToEx(hdcMem, xMouse, yMouse, NULL);
        xMouse = LOWORD(lParam);
        yMouse = HIWORD(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_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}

//resource.h

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by GrafMenu.rc
//
#define IDM_FONT_COUR                   101
#define IDM_FONT_ARIAL                  102
#define IDM_FONT_TIMES                  103
#define IDM_HELP                        104
#define IDM_EDIT_UNDO                   40005
#define IDM_EDIT_CUT                    40006
#define IDM_EDIT_COPY                   40007
#define IDM_EDIT_PASTE                  40008
#define IDM_EDIT_CLEAR                  40009
#define IDM_FILE_NEW                    40010
#define IDM_FILE_OPEN                   40011
#define IDM_FILE_SAVE                   40012
#define IDM_FILE_SAVE_AS                40013

// Next default values for new objects
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        108
#define _APS_NEXT_COMMAND_VALUE         40014
#define _APS_NEXT_CONTROL_VALUE         1000
#define _APS_NEXT_SYMED_VALUE           105
#endif
#endif

//GrafMenu.rc

//Microsoft Developer Studio generated resource script.
//
#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "afxres.h"

/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS

/////////////////////////////////////////////////////////////////////////////
// English (U.S.) resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
#ifdef _WIN32
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
#endif //_WIN32

#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//

1 TEXTINCLUDE DISCARDABLE 
BEGIN
    "resource.h"
END

2 TEXTINCLUDE DISCARDABLE 
BEGIN
    "#include ""afxres.h""
"
    ""
END

3 TEXTINCLUDE DISCARDABLE 
BEGIN
    "
"
    ""
END

#endif    // APSTUDIO_INVOKED


/////////////////////////////////////////////////////////////////////////////
//
// Menu
//

MENUFILE MENU DISCARDABLE 
BEGIN
    MENUITEM "&New",                        IDM_FILE_NEW
    MENUITEM "&Open...",                    IDM_FILE_OPEN
    MENUITEM "&Save",                       IDM_FILE_SAVE
    MENUITEM "Save &As...",                 IDM_FILE_SAVE_AS
END

MENUEDIT MENU DISCARDABLE 
BEGIN
    MENUITEM "&Undo",                       IDM_EDIT_UNDO
    MENUITEM SEPARATOR
    MENUITEM "Cu&t",                        IDM_EDIT_CUT
    MENUITEM "&Copy",                       IDM_EDIT_COPY
    MENUITEM "&Paste",                      IDM_EDIT_PASTE
    MENUITEM "De&lete",                     IDM_EDIT_CLEAR
END


/////////////////////////////////////////////////////////////////////////////
//
// Bitmap
//

BITMAPFONT              BITMAP  DISCARDABLE     "Fontlabl.bmp"
BITMAPHELP              BITMAP  DISCARDABLE     "Bighelp.bmp"
BITMAPEDIT              BITMAP  DISCARDABLE     "Editlabl.bmp"
BITMAPFILE              BITMAP  DISCARDABLE     "Filelabl.bmp"
#endif    // English (U.S.) resources
/////////////////////////////////////////////////////////////////////////////



#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//


/////////////////////////////////////////////////////////////////////////////
#endif    // not APSTUDIO_INVOKED
原文地址:https://www.cnblogs.com/5iedu/p/4698468.html