GDI 直线和折线(6)

设置开始点

MoveToEx 函数用于移动画笔到指定的位置:

BOOL MoveToEx(
    HDC hdc,          // 设备环境句柄
    int X,            // 要移动到的 x 坐标
    int Y,            // 要移动到的 y 坐标
    LPPOINT lpPoint   // 之前的位置(POINT 结构地址)
);

绘制直线

LineTo 函数用于绘制直线到指定位置(这会改变当前画笔的位置到结束位置):

BOOL LineTo(
    HDC hdc,    // 设备环境句柄
    int nXEnd,  // 结束位置的 x 坐标
    int nYEnd   // 结束位置的 y 坐标
);

绘制折线

Polyline 函数用于绘制一系列点构成的折线(这不会改变当前画笔的位置):

BOOL Polyline(
    HDC hdc,            // 设备环境句柄
    CONST POINT *lppt,  // POINT 结构数组(一系列点)
    int cPoints         // POINT 结构的数量(点的数量)
);

PolylineTo 函数用于绘制由画笔位置开始到一系列点所构成的折线(这会改变当前画笔的位置到最后一个点的位置):

BOOL PolylineTo( 
    HDC hdc,            // 设备环境句柄
    CONST POINT *lppt,  // POINT 结构数组(一系列点)
    DWORD cCount        // POINT 结构的数量(点的数量)
);

PolyPolyline 函数用于同时绘制多条折线(这不会改变当前画笔的位置):

BOOL PolyPolyline(
    HDC hdc,                      // 设备环境句柄
    CONST POINT *lppt,            // POINT 结构数组(一系列点)
    CONST DWORD *lpdwPolyPoints,  // 数组中的每个元素表示对应折线的点的数量
    DWORD cCount                  // 折线数量(上个参数的元素个数)
);

绘制 Sin 函数图像的程序示例

#include 
#include 
#include 

#define NUMS 10000
#define TWOPI 6.283185307

LRESULT CALLBACK WindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {

    HDC hdc;
    PAINTSTRUCT ps;
    RECT rcClient;
    POINT apt[NUMS];
    TCHAR szBuffer[100];
    size_t nLength;
    int cxClient, cyClient, i;

    switch (message) {

    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);

        GetClientRect(hwnd, &rcClient);
        cxClient = rcClient.right - rcClient.left;
        cyClient = rcClient.bottom - rcClient.top;

        MoveToEx(hdc, 0, cyClient / 2, NULL);
        LineTo(hdc, cxClient, cyClient / 2);

        for (i = 0; i < NUMS; i++) {
            apt[i].x = i;
            apt[i].y = (int)((1 - sin(i * TWOPI / cxClient)) * cyClient / 2);
        }

        SetTextAlign(hdc, TA_TOP | TA_RIGHT);

        StringCchPrintf(szBuffer, sizeof(szBuffer) / sizeof(TCHAR), TEXT("y = sin x"));
        StringCchLength(szBuffer, sizeof(szBuffer) / sizeof(TCHAR), &nLength);
        TextOut(hdc, cxClient - 10, 10, szBuffer, nLength);

        Polyline(hdc, apt, i - 1);

        EndPaint(hwnd, &ps);
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, message, wParam, lParam);
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {

    LPCTSTR lpszClassName = TEXT("Demo");
    LPCTSTR lpszWindowName = TEXT("Demo Program");

    WNDCLASS wndclass;
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wndclass.hInstance = hInstance;
    wndclass.lpfnWndProc = WindowProc;
    wndclass.lpszClassName = lpszClassName;
    wndclass.lpszMenuName = NULL;
    wndclass.style = CS_HREDRAW | CS_VREDRAW;

    if (!RegisterClass(&wndclass)) {
        MessageBox(NULL, TEXT("This program requires Windows NT!"), lpszWindowName, MB_ICONERROR);
        return 0;
    }

    HWND hwnd = CreateWindow(
        lpszClassName,
        lpszWindowName,
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        NULL,
        NULL,
        hInstance,
        NULL
    );

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

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

GDI 线段绘制示例程序

  1 #include 
  2 #include 
  3 #include 
  4 
  5 typedef struct tagLINE {
  6     POINT ptStart;
  7     POINT ptEnd;
  8 }LINE;
  9 
 10 LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
 11 {
 12     HDC hdc;
 13     PAINTSTRUCT ps;
 14     TCHAR szBuffer[100];
 15     size_t nLength;
 16     static int cxClient, cyClient;
 17     static BOOL fDrawing;
 18     static LINE arrLine[1000];
 19     static int length;
 20     static LINE line;
 21     int i;
 22 
 23     switch (message)
 24     {
 25     case WM_CREATE:
 26         length = 0;
 27         fDrawing = FALSE;
 28         return 0;
 29 
 30     case WM_LBUTTONUP:
 31         
 32         if (fDrawing) {
 33             line.ptEnd.x = LOWORD(lParam);
 34             line.ptEnd.y = HIWORD(lParam);
 35             arrLine[length] = line;
 36             length++;
 37         }
 38         else {
 39             line.ptStart.x = LOWORD(lParam);
 40             line.ptStart.y = HIWORD(lParam);
 41         }
 42 
 43         fDrawing = !fDrawing;
 44 
 45         InvalidateRect(hwnd, NULL, TRUE);
 46         
 47         return 0;
 48 
 49     case WM_SIZE:
 50         hdc = GetDC(hwnd);
 51 
 52         cxClient = LOWORD(lParam);
 53         cyClient = HIWORD(lParam);
 54 
 55         ReleaseDC(hwnd, hdc);
 56         return 0;
 57 
 58     case WM_PAINT:
 59         hdc = BeginPaint(hwnd, &ps);
 60 
 61         for (i = 0; i < length; i++) {
 62             MoveToEx(hdc, arrLine[i].ptStart.x, arrLine[i].ptStart.y, NULL);
 63             LineTo(hdc, arrLine[i].ptEnd.x, arrLine[i].ptEnd.y);
 64         }
 65 
 66         StringCchPrintf(szBuffer, sizeof(szBuffer) / sizeof(TCHAR), TEXT("Size: %d x %d"), cxClient, cyClient);
 67         StringCchLength(szBuffer, sizeof(szBuffer) / sizeof(TCHAR), &nLength);
 68 
 69         SetTextAlign(hdc, TA_TOP | TA_RIGHT);
 70         TextOut(hdc, cxClient, 0, szBuffer, nLength);
 71 
 72         EndPaint(hwnd, &ps);
 73         return 0;
 74 
 75     case WM_DESTROY:
 76         PostQuitMessage(0);
 77         return 0;
 78     }
 79 
 80     return DefWindowProc(hwnd, message, wParam, lParam);
 81 }
 82 
 83 
 84 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdLine)
 85 {
 86     LPCTSTR lpszClassName = TEXT("Demo");
 87     LPCTSTR lpszWindowName = TEXT("Demo Window");
 88 
 89     WNDCLASS wndclass;
 90     wndclass.cbClsExtra = 0;
 91     wndclass.cbWndExtra = 0;
 92     wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
 93     wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
 94     wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
 95     wndclass.hInstance = hInstance;
 96     wndclass.lpfnWndProc = WndProc;
 97     wndclass.lpszClassName = lpszClassName;
 98     wndclass.lpszMenuName = NULL;
 99     wndclass.style = CS_HREDRAW | CS_VREDRAW;
100 
101     if (!RegisterClass(&wndclass)) {
102         MessageBox(NULL, TEXT("This program requires Windows NT!"), TEXT("Error"), MB_ICONERROR);
103         return 0;
104     }
105 
106     HWND hwnd = CreateWindow(
107         lpszClassName,
108         lpszWindowName,
109         WS_OVERLAPPEDWINDOW,
110         CW_USEDEFAULT,
111         CW_USEDEFAULT,
112         CW_USEDEFAULT,
113         CW_USEDEFAULT,
114         NULL,
115         NULL,
116         hInstance,
117         NULL
118     );
119 
120     ShowWindow(hwnd, nCmdLine);
121     UpdateWindow(hwnd);
122 
123     MSG msg;
124     while (GetMessage(&msg, NULL, 0, 0))
125     {
126         TranslateMessage(&msg);
127         DispatchMessage(&msg);
128     }
129 
130     return msg.wParam;
131 }

 

GDI 像素(5)

RGB 颜色

使用 RGB 宏可以创建一个由三个整数值(R、G、B)的 COLORREF 值。

COLORREF RGB(
  BYTE byRed,    // 红色值(R)
  BYTE byGreen,  // 绿色值(G)
  BYTE byBlue    // 蓝色值(B)
);

设置像素

SetPixel 函数用于将坐标为 x 和 y 的像素点设定为某个特定的颜色:

COLORREF SetPixel( 
    HDC hdc,           // 当前的设备环境句柄
    int X,             // 像素点的 x 坐标
    int Y,             // 像素点的 y 坐标  
    COLORREF crColor   // 要设置的 RGB 颜色(COLOREF 类型)
);
// 返回成功设置的 RGB 颜色(COLORREF 类型)

SetPixelV 函数用于将坐标为 x 和 y 的像素点设定为某个特定的颜色,它比 SetPixel 更快,因为 SetPixelV 只返回一个 BOOL 值,指示设置成功与否:

BOOL SetPixelV(
  HDC hdc,           // 当前的设备环境句柄
  int X,             // 像素点的 x 坐标
  int Y,             // 像素点的 y 坐标
  COLORREF crColor   // 要设置的 RGB 颜色(COLORREF)
);

获取像素

GetPixel 函数用于获取坐标为 x 和 y 所在像素点的颜色值:

COLORREF GetPixel(
  HDC hdc,    // 当前的设备环境句柄
  int nXPos,  // 像素点的 x 坐标
  int nYPos   // 像素点的 y 坐标
);
// 返回像素的颜色值

滚动条(4)

窗口滚动条的显示

用于创建窗口的 CreateWindow 函数的第三个参数可以设置 WS_HSCROLL(水平滚动条) 和 WS_VSCROLL(垂直滚动条) 这两个风格标识符,以标识窗口附带水平滚动条和(或)垂直滚动条

滚动条消息

当用户单击或拖动窗口滚动条时,Windows 向窗口过程发送 WM_HSCROLL(水平滚动) 消息或 WM_VSCROLL(垂直滚动) 消息,鼠标按下与鼠标松开都会产生这些消息(即一个动作至少会产生两条消息)。消息伴随着消息参数,lParam 的值为滚动条的句柄(当是窗口的滚动条时,此值为 NULL 可以忽略),wParam 的低位字代表鼠标在滚动条上的动作:

向上(左)滚动一行:SB_LINEUP

向下(右)滚动一行:SB_LINEDOWN

向上(左)滚动一页:SB_PAGEUP

向下(右)滚动一页:SB_PAGEDOWN

拖动滚动条不放:SB_THUMBTRACK(一直发送),wParam 的高位字是用户拖动滑块的当前位置

拖动滚动条时释放鼠标:SB_THUMBPOSITION,wParam 的高位字是松开鼠标键时,滑块的最终位置

松开鼠标键时:SB_ENDSCROLL

滚动条的范围和位置

调用 SetScrollRange 函数可以设置滚动条的范围:

BOOL SetScrollRange(          
    HWND hWnd,    // 窗口的句柄
    int nBar,     // 滚动条类型(SB_VERT 或 SB_HORZ)
    int nMinPos,  // 最小位置
    int nMaxPos,  // 最大位置
    BOOL bRedraw  // 是否需要 Windows 根据新的范围来重绘滚动条(若编程时调用其它函数来调整滚动条的显示,则最好设为FALSE,避免过多重绘)
);

调用 SetScrollPos 函数可以调整滚动条的位置:

int SetScrollPos(          
    HWND hWnd,    // 窗口的句柄
    int nBar,     // 滚动条类型(SB_VERT 或 SB_HORZ)
    int nPos,     // 要设置的滚动条的位置(必须在最小位置和最大位置之间)
    BOOL bRedraw  // 是否需要 Windows 根据新的范围来重绘滚动条(若编程时调用其它函数来调整滚动条的显示,则最好设为FALSE,避免过多重绘)
);

调用 GetScrollRange 函数可以获取滚动条的范围:

BOOL GetScrollRange(
    HWND hWnd,
    int nBar,
    LPINT lpMinPos,    // 最小位置
    LPINT lpMaxPos    // 最大位置
);

调用 GetScrollPos函数可以获取滚动条的位置:

int GetScrollPos(
    HWND hWnd,
    int nBar
);

滚动条编程要点

Windows 负责的部分:

  • 处理所有鼠标消息
  • 单击滚动条时,提供反向显示的闪烁
  • 拖动滑块时,显示滑块在滚动条内移动
  • 向窗口过程发送滚动条消息

程序负责的部分:

  • 初始化滚动条的范围和位置
  • 处理窗口过程的滚动条消息
  • 更新滑块的位置
  • 根据滚动条变化更新客户区的内容

滚动条的改良

  • SCROLLINFO 结构用于存放滚动条信息,其定义如下:
typedef struct tagSCROLLINFO { 
    UINT cbSize;     // 设为 sizeof (SCROLLINFO)
    UINT fMask;      // 要设置和获取的值 
    int  nMin;       // 范围的最小值
    int  nMax;       // 范围的最大值
    UINT nPage;      // 页面大小
    int  nPos;       // 当前位置
    int  nTrackPos;  // 当前追踪位置
}   SCROLLINFO, *LPSCROLLINFO; 
typedef SCROLLINFO CONST *LPCSCROLLINFO;

cbSize 字段表示该结构的大小,通常被填充为 sizeof(SCROLLINFO) ,这样方便新版本的 Windows 扩充字段。

fMask 字段把 SIF 为前缀的标志用位或组合起来,表示设置和获取的值。

SIF_RANGE:该标志表示滚动条范围,此时 nMin、nMax 有效

SIF_POS:该标志表示滚动条位置,此时 nPos 有效

SIF_PAGE:该标志表示页面大小,此时 nPage 有效

SIF_TRACKPOS:该标志表示当前滑块的位置(只用于 GetScrollInfo 中,只在 SB_THUMBTRACK 和 SB_THUMBPOSITION 有效),nTrackPos 有效

SIF_DISABLENOSCROLL:该标志表示当滚动条不显示时,显示禁用的滚动条样式

SIF_ALL:该标志是 SIF_RANGE、SIF_POS、SIF_PAGE、SIF_TRACKPOS 的组合

  • SetScrollInfo 函数用于设置滚动条信息:
int SetScrollInfo(          
    HWND hwnd,          // 当前窗口句柄
    int fnBar,          // 滚动条类型(SB_VERT 或者 SB_HORZ)
    LPCSCROLLINFO lpsi, // SCROLLINFO 结构的地址
    BOOL fRedraw        // 是否重绘的标志
);
  • GetScrollInfo 函数用于获取滚动条信息:
BOOL GetScrollInfo(          
    HWND hwnd,          // 当前窗口句柄
    int fnBar,          // 滚动条类型(SB_VERT 或者 SB_HORZ)
    LPCSCROLLINFO lpsi, // SCROLLINFO 结构的地址
);
  • ScrollWindow 函数用于滚动窗口客户区内容:
BOOL ScrollWindow(
    HWND hWnd,             // 当前窗口句柄
    int XAmount,           // 水平滚动量,为负则向左滚动
    int YAmount,           // 垂直滚动量,为负则向上滚动
    const RECT *lpRect,    // 指定滚动客户区矩形的范围
    const RECT *lpClipRect // 指定滚动的裁剪区域
);

最终版 SYSMET 程序示例

  • SYSMET.h 源代码:
/*-----------------------------------------------
   SYSMETS.H -- System metrics display structure
  -----------------------------------------------*/

#define NUMLINES ((int) (sizeof sysmetrics / sizeof sysmetrics [0]))

struct
{
     int     iIndex ;
     TCHAR * szLabel ;
     TCHAR * szDesc ;
} 
sysmetrics [] =
{
     SM_CXSCREEN,             TEXT ("SM_CXSCREEN"),              
                              TEXT ("Screen width in pixels"),
     SM_CYSCREEN,             TEXT ("SM_CYSCREEN"),              
                              TEXT ("Screen height in pixels"),
     SM_CXVSCROLL,            TEXT ("SM_CXVSCROLL"),             
                              TEXT ("Vertical scroll width"),
     SM_CYHSCROLL,            TEXT ("SM_CYHSCROLL"),             
                              TEXT ("Horizontal scroll height"),
     SM_CYCAPTION,            TEXT ("SM_CYCAPTION"),             
                              TEXT ("Caption bar height"),
     SM_CXBORDER,             TEXT ("SM_CXBORDER"),              
                              TEXT ("Window border width"),
     SM_CYBORDER,             TEXT ("SM_CYBORDER"),              
                              TEXT ("Window border height"),
     SM_CXFIXEDFRAME,         TEXT ("SM_CXFIXEDFRAME"),          
                              TEXT ("Dialog window frame width"),
     SM_CYFIXEDFRAME,         TEXT ("SM_CYFIXEDFRAME"),          
                              TEXT ("Dialog window frame height"),
     SM_CYVTHUMB,             TEXT ("SM_CYVTHUMB"),              
                              TEXT ("Vertical scroll thumb height"),
     SM_CXHTHUMB,             TEXT ("SM_CXHTHUMB"),              
                              TEXT ("Horizontal scroll thumb width"),
     SM_CXICON,               TEXT ("SM_CXICON"),                
                              TEXT ("Icon width"),
     SM_CYICON,               TEXT ("SM_CYICON"),                
                              TEXT ("Icon height"),
     SM_CXCURSOR,             TEXT ("SM_CXCURSOR"),              
                              TEXT ("Cursor width"),
     SM_CYCURSOR,             TEXT ("SM_CYCURSOR"),              
                              TEXT ("Cursor height"),
     SM_CYMENU,               TEXT ("SM_CYMENU"),                
                              TEXT ("Menu bar height"),
     SM_CXFULLSCREEN,         TEXT ("SM_CXFULLSCREEN"),          
                              TEXT ("Full screen client area width"),
     SM_CYFULLSCREEN,         TEXT ("SM_CYFULLSCREEN"),          
                              TEXT ("Full screen client area height"),
     SM_CYKANJIWINDOW,        TEXT ("SM_CYKANJIWINDOW"),         
                              TEXT ("Kanji window height"),
     SM_MOUSEPRESENT,         TEXT ("SM_MOUSEPRESENT"),          
                              TEXT ("Mouse present flag"),
     SM_CYVSCROLL,            TEXT ("SM_CYVSCROLL"),             
                              TEXT ("Vertical scroll arrow height"),
     SM_CXHSCROLL,            TEXT ("SM_CXHSCROLL"),             
                              TEXT ("Horizontal scroll arrow width"),
     SM_DEBUG,                TEXT ("SM_DEBUG"),                 
                              TEXT ("Debug version flag"),
     SM_SWAPBUTTON,           TEXT ("SM_SWAPBUTTON"),            
                              TEXT ("Mouse buttons swapped flag"),
     SM_CXMIN,                TEXT ("SM_CXMIN"),                 
                              TEXT ("Minimum window width"),
     SM_CYMIN,                TEXT ("SM_CYMIN"),                 
                              TEXT ("Minimum window height"),
     SM_CXSIZE,               TEXT ("SM_CXSIZE"),                
                              TEXT ("Min/Max/Close button width"),
     SM_CYSIZE,               TEXT ("SM_CYSIZE"),                
                              TEXT ("Min/Max/Close button height"),
     SM_CXSIZEFRAME,          TEXT ("SM_CXSIZEFRAME"),           
                              TEXT ("Window sizing frame width"),
     SM_CYSIZEFRAME,          TEXT ("SM_CYSIZEFRAME"),           
                              TEXT ("Window sizing frame height"),
     SM_CXMINTRACK,           TEXT ("SM_CXMINTRACK"),            
                              TEXT ("Minimum window tracking width"),
     SM_CYMINTRACK,           TEXT ("SM_CYMINTRACK"),            
                              TEXT ("Minimum window tracking height"),
     SM_CXDOUBLECLK,          TEXT ("SM_CXDOUBLECLK"),           
                              TEXT ("Double click x tolerance"),
     SM_CYDOUBLECLK,          TEXT ("SM_CYDOUBLECLK"),           
                              TEXT ("Double click y tolerance"),
     SM_CXICONSPACING,        TEXT ("SM_CXICONSPACING"),         
                              TEXT ("Horizontal icon spacing"),
     SM_CYICONSPACING,        TEXT ("SM_CYICONSPACING"),         
                              TEXT ("Vertical icon spacing"),
     SM_MENUDROPALIGNMENT,    TEXT ("SM_MENUDROPALIGNMENT"),     
                              TEXT ("Left or right menu drop"),
     SM_PENWINDOWS,           TEXT ("SM_PENWINDOWS"),            
                              TEXT ("Pen extensions installed"),
     SM_DBCSENABLED,          TEXT ("SM_DBCSENABLED"),           
                              TEXT ("Double-Byte Char Set enabled"),
     SM_CMOUSEBUTTONS,        TEXT ("SM_CMOUSEBUTTONS"),         
                              TEXT ("Number of mouse buttons"),
     SM_SECURE,               TEXT ("SM_SECURE"),                
                              TEXT ("Security present flag"),
     SM_CXEDGE,               TEXT ("SM_CXEDGE"),                
                              TEXT ("3-D border width"),
     SM_CYEDGE,               TEXT ("SM_CYEDGE"),                
                              TEXT ("3-D border height"),
     SM_CXMINSPACING,         TEXT ("SM_CXMINSPACING"),          
                              TEXT ("Minimized window spacing width"),
     SM_CYMINSPACING,         TEXT ("SM_CYMINSPACING"),          
                              TEXT ("Minimized window spacing height"),
     SM_CXSMICON,             TEXT ("SM_CXSMICON"),              
                              TEXT ("Small icon width"),
     SM_CYSMICON,             TEXT ("SM_CYSMICON"),              
                              TEXT ("Small icon height"),
     SM_CYSMCAPTION,          TEXT ("SM_CYSMCAPTION"),           
                              TEXT ("Small caption height"),
     SM_CXSMSIZE,             TEXT ("SM_CXSMSIZE"),              
                              TEXT ("Small caption button width"),
     SM_CYSMSIZE,             TEXT ("SM_CYSMSIZE"),              
                              TEXT ("Small caption button height"),
     SM_CXMENUSIZE,           TEXT ("SM_CXMENUSIZE"),            
                              TEXT ("Menu bar button width"),
     SM_CYMENUSIZE,           TEXT ("SM_CYMENUSIZE"),            
                              TEXT ("Menu bar button height"),
     SM_ARRANGE,              TEXT ("SM_ARRANGE"),               
                              TEXT ("How minimized windows arranged"),
     SM_CXMINIMIZED,          TEXT ("SM_CXMINIMIZED"),           
                              TEXT ("Minimized window width"),
     SM_CYMINIMIZED,          TEXT ("SM_CYMINIMIZED"),           
                              TEXT ("Minimized window height"),
     SM_CXMAXTRACK,           TEXT ("SM_CXMAXTRACK"),            
                              TEXT ("Maximum dragable width"),
     SM_CYMAXTRACK,           TEXT ("SM_CYMAXTRACK"),            
                              TEXT ("Maximum dragable height"),
     SM_CXMAXIMIZED,          TEXT ("SM_CXMAXIMIZED"),           
                              TEXT ("Width of maximized window"),
     SM_CYMAXIMIZED,          TEXT ("SM_CYMAXIMIZED"),           
                              TEXT ("Height of maximized window"),
     SM_NETWORK,              TEXT ("SM_NETWORK"),               
                              TEXT ("Network present flag"),
     SM_CLEANBOOT,            TEXT ("SM_CLEANBOOT"),             
                              TEXT ("How system was booted"),
     SM_CXDRAG,               TEXT ("SM_CXDRAG"),                
                              TEXT ("Avoid drag x tolerance"),
     SM_CYDRAG,               TEXT ("SM_CYDRAG"),                
                              TEXT ("Avoid drag y tolerance"),
     SM_SHOWSOUNDS,           TEXT ("SM_SHOWSOUNDS"),            
                              TEXT ("Present sounds visually"),
     SM_CXMENUCHECK,          TEXT ("SM_CXMENUCHECK"),           
                              TEXT ("Menu check-mark width"),
     SM_CYMENUCHECK,          TEXT ("SM_CYMENUCHECK"),           
                              TEXT ("Menu check-mark height"),
     SM_SLOWMACHINE,          TEXT ("SM_SLOWMACHINE"),           
                              TEXT ("Slow processor flag"),
     SM_MIDEASTENABLED,       TEXT ("SM_MIDEASTENABLED"),        
                              TEXT ("Hebrew and Arabic enabled flag"),
     SM_MOUSEWHEELPRESENT,    TEXT ("SM_MOUSEWHEELPRESENT"),     
                              TEXT ("Mouse wheel present flag"),
     SM_XVIRTUALSCREEN,       TEXT ("SM_XVIRTUALSCREEN"),        
                              TEXT ("Virtual screen x origin"),
     SM_YVIRTUALSCREEN,       TEXT ("SM_YVIRTUALSCREEN"),        
                              TEXT ("Virtual screen y origin"),
     SM_CXVIRTUALSCREEN,      TEXT ("SM_CXVIRTUALSCREEN"),       
                              TEXT ("Virtual screen width"),
     SM_CYVIRTUALSCREEN,      TEXT ("SM_CYVIRTUALSCREEN"),       
                              TEXT ("Virtual screen height"),
     SM_CMONITORS,            TEXT ("SM_CMONITORS"),             
                              TEXT ("Number of monitors"),
     SM_SAMEDISPLAYFORMAT,    TEXT ("SM_SAMEDISPLAYFORMAT"),     
                              TEXT ("Same color format flag")
} ;
  • SYSMET.c 源代码:
#include 
#include 
#include "SysMets.h"

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {

    HDC hdc;
    PAINTSTRUCT ps;
    TEXTMETRIC tm;
    static int cxChar, cxCaps, cyChar, cxClient, cyClient, iMaxWidth;
    int iVertPos, iHorzPos, iBeginLine, iEndLine, i, x, y;
    size_t nLength;
    SCROLLINFO si;
    TCHAR szBuffer[100];

    switch (message) {
    case WM_CREATE:
        hdc = GetDC(hwnd);

        GetTextMetrics(hdc, &tm);
        cxChar = tm.tmAveCharWidth;
        cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2;
        cyChar = tm.tmHeight + tm.tmExternalLeading;

        iMaxWidth = cxCaps * 22 + cxChar * 40;

        ReleaseDC(hwnd, hdc);
        return 0;

    case WM_SIZE:
        cxClient = LOWORD(lParam);
        cyClient = HIWORD(lParam);

        si.cbSize = sizeof(si);
        si.fMask = SIF_RANGE | SIF_PAGE | SIF_DISABLENOSCROLL;
        si.nMin = 0;
        si.nMax = NUMLINES - 1;
        si.nPage = cyClient / cyChar;
        SetScrollInfo(hwnd, SB_VERT, &si, TRUE);

        si.cbSize = sizeof(si);
        si.fMask = SIF_RANGE | SIF_PAGE | SIF_DISABLENOSCROLL;
        si.nMin = 0;
        si.nMax = iMaxWidth / cxChar + 5;
        si.nPage = cxClient / cxChar;
        SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);

        return 0;

    case WM_VSCROLL:

        si.cbSize = sizeof(si);
        si.fMask = SIF_ALL;
        GetScrollInfo(hwnd, SB_VERT, &si);
        iVertPos = si.nPos;

        switch (LOWORD(wParam)) {
        case SB_LINEUP:
            si.nPos -= 1;
            break;

        case SB_LINEDOWN:
            si.nPos += 1;
            break;

        case SB_PAGEUP:
            si.nPos -= si.nPage;
            break;

        case SB_PAGEDOWN:
            si.nPos += si.nPage;
            break;

        case SB_THUMBTRACK:
            si.nPos = si.nTrackPos;
            break;
        }

        si.cbSize = sizeof(si);
        si.fMask = SIF_POS;
        SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
        GetScrollInfo(hwnd, SB_VERT, &si);

        if (si.nPos != iVertPos) {
            ScrollWindow(hwnd, 0, cyChar * (iVertPos - si.nPos), NULL, NULL);
        }

        return 0;

    case WM_HSCROLL:

        si.cbSize = sizeof(si);
        si.fMask = SIF_ALL;
        GetScrollInfo(hwnd, SB_HORZ, &si);
        iHorzPos = si.nPos;

        switch (LOWORD(wParam)) {
        case SB_LINELEFT:
            si.nPos -= 1;
            break;

        case SB_LINERIGHT:
            si.nPos += 1;
            break;

        case SB_PAGELEFT:
            si.nPos -= si.nPage;
            break;

        case SB_PAGERIGHT:
            si.nPos += si.nPage;
            break;

        case SB_THUMBTRACK:
            si.nPos = si.nTrackPos;
            break;
        }

        si.cbSize = sizeof(si);
        si.fMask = SIF_POS;
        SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);
        GetScrollInfo(hwnd, SB_HORZ, &si);

        if (si.nPos != iHorzPos) {
            ScrollWindow(hwnd, cxChar * (iHorzPos - si.nPos), 0, NULL, NULL);
        }

        return 0;

    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);

        si.cbSize = sizeof(si);
        si.fMask = SIF_ALL;
        GetScrollInfo(hwnd, SB_VERT, &si);
        iVertPos = si.nPos;
        GetScrollInfo(hwnd, SB_HORZ, &si);
        iHorzPos = si.nPos;

        // Control the line range
        iBeginLine = max(0, iVertPos + (ps.rcPaint.top / cyChar));
        iEndLine = min(NUMLINES - 1, iVertPos + (ps.rcPaint.bottom / cyChar));

        for (i = iBeginLine; i <= iEndLine; i++) {
            x = cxChar * (1 - iHorzPos);
            y = cyChar * (i - iVertPos);

            StringCchPrintf(szBuffer, sizeof(szBuffer) / sizeof(TCHAR), sysmetrics[i].szLabel);
            StringCchLength(szBuffer, sizeof(szBuffer) / sizeof(TCHAR), &nLength);
            TextOut(hdc, x, y, szBuffer, nLength);

            StringCchPrintf(szBuffer, sizeof(szBuffer) / sizeof(TCHAR), sysmetrics[i].szDesc);
            StringCchLength(szBuffer, sizeof(szBuffer) / sizeof(TCHAR), &nLength);
            TextOut(hdc, x + cxCaps * 22, y, szBuffer, nLength);

            StringCchPrintf(szBuffer, sizeof(szBuffer) / sizeof(TCHAR), TEXT("%5d"), sysmetrics[i].iIndex);
            StringCchLength(szBuffer, sizeof(szBuffer) / sizeof(TCHAR), &nLength);
            TextOut(hdc, x + cxCaps * 22 + cxChar * 40, y, szBuffer, nLength);
        }

        EndPaint(hwnd, &ps);
        return 0;

    case WM_CLOSE:
        if (MessageBox(hwnd, TEXT("Do you really want to quit?"), TEXT("Please confirm:"), MB_ICONQUESTION | MB_OKCANCEL) == IDOK) {
            DestroyWindow(hwnd);
        }
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, message, wParam, lParam);
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    LPCTSTR lpszClassName = TEXT("ScrollDemo");
    LPCTSTR lpszWindowName = TEXT("Scroll Demo");

    WNDCLASS wndclass;
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wndclass.hInstance = hInstance;
    wndclass.lpfnWndProc = WndProc;
    wndclass.lpszClassName = lpszClassName;
    wndclass.lpszMenuName = NULL;
    wndclass.style = CS_HREDRAW | CS_VREDRAW;

    if (!RegisterClass(&wndclass)) {
        MessageBox(NULL, TEXT("This program requires Windows NT!"), lpszWindowName, MB_ICONERROR);
        return 0;
    }

    HWND hwnd = CreateWindow(
        lpszClassName,
        lpszWindowName,
        WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        NULL,
        NULL,
        hInstance,
        NULL
    );

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

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

滚动条小结

总的来说,SYSMET 程序可以细分为如下结构:

对 WM_CREATE 的消息的响应,获取系统字符的各个参数。

对 WM_SIZE 消息的响应,改变水平滚动条和垂直滚动条的范围(nMin、nMax)和页面大小(nPage)

对 WM_VSCROLL 消息的响应,根据 LOWORD(wParam) 改变垂直滚动条滑块的位置(nPos),并根据位置变化的偏移量,滚动客户区(ScrollWindow)

对 WM_HSCROLL 消息的响应,根据 LOWORD(wParam) 改变水平滚动条滑块的位置(nPos),并根据位置变化的偏移量,滚动客户区(ScrollWindow)

对 WM_PAINT 消息的响应,根据滚动条滑块位置和 PAINTSTRUCT 的 rcPaint 无效矩形区域信息计算出,文本绘制的开始行(iBeginLine)和结束行(iEndLine);并根据滚动条滑块位置,计算出文本输出的起始水平位置(x)和垂直位置(y),并输出文本(TextOut)

文本和客户区的尺寸(3)

TextOut 函数

TextOut 函数是在客户区指定位置显示文本的 GDI 函数,它的原型如下:

BOOL TextOut(
  HDC hdc,           // 设备环境句柄
  int nXStart,       // 开始位置的 x 坐标
  int nYStart,       // 开始位置的 y 坐标
  LPCTSTR lpString,  // 要显示的字符串
  int cbString       // 要显示字符个数(如果是 Unicode 字符则为原本的两倍)
);

文本的尺寸

1、TEXTMETRIC 结构用于存放字符尺寸的各种值,最常用的有 7 个值:

typedef struct tagTEXTMETRIC { 
  LONG tmHeight;          // 字符高度(tmAscent 与 tmDescent)之和
  LONG tmAscent;          // 基线之上的最大高度
  LONG tmDescent;         // 基线之下的最大高度
  LONG tmInternalLeading; // 内部间距(包含于 tmAscent 之中,通常为了显示重音符号)
  LONG tmExternalLeading; // 外部间距,行间距
  LONG tmAveCharWidth;    // 小写字符的加权平均宽度
  LONG tmMaxCharWidth;    // 最宽的字符的宽度
  // 其他字段
} TEXTMETRIC, *PTEXTMETRIC;

2、GetTextMetrics 函数用于获取字体的尺寸,它的第一个参数是当前的设备环境句柄,第二个参数是要填充的 TEXTMETRIC 结构的地址:

BOOL GetTextMetrics(
  HDC hdc,            // 当前设备环境句柄
  LPTEXTMETRIC lptm   // 要填充的 TEXTMETRIC 结构的地址
);

Windows 运行时,系统字体不会变化。所以程序只需要调用一次 GetSystemMetrics 函数(最好在处理 WM_CREATE 消息的时候)

获取系统字体的宽度和高度的示例代码:

// ......
// cxChar 为平均字符宽度,cyChar 为字符总高度,cxCaps 为大写字符的平均宽度
static int cxChar, cyChar, cxCaps;
TEXTMETRIC tm;
// ......
case WM_CREATE:
    hdc = GetDC(hwnd);

    GetTextMetrics(hdc, &tm);
    cxChar = tm.tmAveCharWidth;
    // tmPitchAndFamily 字段的低位决定字体是否为等宽字体,1为变宽,0为等宽。变宽时 cxCaps 是 cxChar 的1.5倍,等宽时 cxCaps 等于 cxChar

    cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2;
    cyChar = tm.tmHeight + tm.tmExternalLeading;

    ReleaseDC(hwnd, hdc);
    return 0;
// ......

文本的对齐

调用 SetTextAlign 函数可以指定显示文本使用的坐标从文本区域的何处开始计算,示例如下:

// 指定坐标从文本区域的右上角开始计算
SetTextAlign(hdc, TA_RIGHT | TA_TOP);

客户区的尺寸

当窗口大小发生变化时,Windows 会向窗口过程发送一条 WM_SIZE 消息,相应的 lParam 变量的低位字是客户区的宽度,高位字是客户区的高度。示例代码如下:

// ......
case WM_SIZE:
    cxClient = LOWORD(lParam); // 低位字为客户区宽度
    cyClient = HIWORD(lParam); // 高位字为客户区高度
    
    // WM_SIZE 消息响应代码
    
    return 0;
// ......

GDI 设备环境句柄(2)

WM_PAINT 消息的触发

Windows 程序在以下情况会触发WM_PAINT消息:

  • 窗口被移动导致被遮盖部分暴露出来
  • 用户调整窗口的大小(当窗口类的 style 字段被设置为 CS_HREDRAW 和 CS_VREDRAW)
  • 调用 ScrollWindow 或 ScrollDC 函数滚动客户区
  • 调用 InvalidateRect 或 InvalidateRgn 函数生成 WM_PAINT

获取设备环境句柄

需要在屏幕上绘图的时候,需要先获取到设备环境句柄,这里有两种方式:

1、调用 BeginPaint 函数(用于响应 WM_PAINT 消息)

第一个参数是当前的窗口句柄,第二个参数是 PAINTSTRUCT 结构的地址,它将返回一个设备环境句柄。BeginPaint 函数的原型如下:

HDC BeginPaint(
  HWND hwnd,            // 当前的窗口句柄
  LPPAINTSTRUCT lpPaint // PAINTSTRUCT 结构的地址
)

Windows 为每个窗口维护一个“绘制信息结构”,即 PAINTSTRUCT,这里给出了它的定义:

typedef struct tagPAINTSTRUCT { 
  HDC  hdc;             // 设备环境句柄,即 BeginPaint 函数的返回值
  BOOL fErase;          // 背景刷状态,如果为 TRUE,表示无效背景区域需要进行擦除,为 FALSE,表示已经擦除了无效区域的背景
  RECT rcPaint;         // 无效矩形边界,是一个 RECT 结构,包含 left、top、right、bottom 四个参数
  BOOL fRestore; 
  BOOL fIncUpdate; 
  BYTE rgbReserved[32]; 
} PAINTSTRUCT;

调用 BeginPaint 函数时,PAINTSTRUCT 结构中的字段将被自动填充。

当调用 BeginPaint 函数获得设备环境句柄,并处理 WM_PAINT 消息后,必须使用 EndPaint 函数释放获取到的设备环境句柄,该函数原型如下:

BOOL EndPaint(
  HWND hWnd,                  // 当前的窗口句柄
  CONST PAINTSTRUCT *lpPaint  // PAINTSTRUCT 结构的地址
);

源码片段示例:

// ......
case WM_PAINT:
    hdc = BeginPaint(hwnd, &ps);
    // 其他GDI代码
    EndPaint(hwnd, &ps);
    return 0;
// ......

2、调用 GetDC 函数(用于响应非 WM_PAINT 消息)

GetDC 函数只有一个参数,即当前的窗口句柄,它将设备环境句柄作为返回值返回。在使用完设备环境句柄后,必须调用 ReleaseDC 函数将其释放,它有两个参数,第一个是当前的窗口句柄,第二个是要释放的设备环境句柄。

源码片段示例:

// ......
    hdc = GetDC(hwnd);
    // 其他GDI代码
    ReleaseDC(hwnd, hdc);
    return 0;
// ......

GetDC / ReleaseDC 组合通常用于处理键盘消息或(与)鼠标消息。

需要注意的地方(小结)

  • WM_PAINT 消息的产生,表示窗口客户区需要重绘。
  • BeginPaint 函数返回的是无效矩形客户区(PAINTSTRUCT 结构中的 rcPaint 字段)的设备环境句柄,而 GetDC 函数返回的是整个客户区的设备环境句柄。
  • BeginPaint 函数调用后,无效矩形区域将变得有效化;而 GetDC 函数本身不会使客户区无效区域有效化,需要自行调用 ValidateRect 函数使客户区无效区域有效化。

我的第一个 Windows 窗口程序(1)

一般来说,构建一个 Windows 程序可以分为如下几个步骤:

  • 定义窗口类(WNDCLASS)
  • 注册窗口类(RegisterClass)
  • 创建窗口(CreateWindow)
  • 更新显示窗口(UpdateWindow、ShowWindow)
  • 建立消息循环(GetMessage)
  • 处理消息(DispatchMessage)

消息处理由窗口过程(WndProc)来完成,消息分为队列消息和非队列消息两种:

  • 队列消息:Windows 放入消息队列的消息,在消息循环中被检索(GetMessage),然后分发投递到窗口过程中(DispatchMessage)。
  • 非队列消息:Windows 对窗口过程的直接调用(将消息直接发送到窗口过程)

Windows 示例程序代码:

#include <windows.h>

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {

   HDC hdc;
   PAINTSTRUCT ps;

    switch (message) {
    case WM_CREATE:
        /* 窗口创建时的消息处理 */
        return 0;

    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);
        /* 窗口重绘的消息处理 */
        EndPaint(hwnd, &ps);
        return 0;

    case WM_DESTROY:
        /* 窗口销毁时的消息处理 */
        PostQuitMessage(0);
        return 0;
    }
    
    /* 默认的窗口过程消息处理 */
    return DefWindowProc(hwnd, message, wParam, lParam);
}


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {

    LPCTSTR lpszClassName = TEXT("demo");
    
    /* 定义窗口类 */
    WNDCLASS wndclass;
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wndclass.hInstance = hInstance;
    wndclass.lpszClassName = lpszClassName;
    wndclass.lpszMenuName = NULL;
    wndclass.lpfnWndProc = WndProc;
    wndclass.style = CS_HREDRAW | CS_VREDRAW;
    
    /* 注册窗口类 */
    if (!RegisterClass(&wndclass)) {
        MessageBox(NULL, TEXT("This Program requires Windows NT!"), lpszClassName, MB_ICONERROR);
        return 0;
    }
    
    /* 创建窗口 */
    HWND hwnd = CreateWindow(
        lpszClassName, 
        TEXT("Demo Window"), 
        WS_OVERLAPPEDWINDOW, 
        CW_USEDEFAULT, 
        CW_USEDEFAULT, 
        CW_USEDEFAULT, 
        CW_USEDEFAULT, 
        NULL, 
        NULL, 
        hInstance, 
        NULL
    );
    
    /* 更新显示窗口 */
    UpdateWindow(hwnd);
    ShowWindow(hwnd, nCmdShow);

    /* 消息循环 */
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

      从上面的程序示例可以看出,WinMain函数完成了程序的基本配置,并且建立了消息循环,WndProc窗口过程则完成每一次消息循环的处理。这样,一个简单的Windows程序框架基本上就构建出来了,其中的细节值得回味。