Windows程序設計核心總結(GDI位圖對象-2018.5.8)

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<windows.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("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<windows.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("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<windows.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("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<windows.h>
#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<windows.h>
#include<windowsx.h>
#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);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章