MFC 学习笔记

cpp 复制代码
#include<windows.h>

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
	MessageBox(NULL, TEXT("Hello, world!"), TEXT("SayHi"), MB_ABORTRETRYIGNORE);
	return 0;
}

messageBox的技术文档

MessageBox,《Windows程序设计(SDK编程)》,Windows程序设计,鱼C论坛 - Powered by Discuz!

还可以加上图标 用或就行 |

cpp 复制代码
#include<windows.h>

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
	MessageBox(NULL, TEXT("Hello, world!"), TEXT("SayHi"), MB_ABORTRETRYIGNORE|MB_ICONINFORMATION| MB_DEFBUTTON1);
	return 0;
}

messagebox 的返回值

Unicode 字符集

vs 是自动使用Unicode 字符集

C语言的使用

TEXT("") 的定义 就是 L""

cpp 复制代码
#include<windows.h>

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
	
	
	const TCHAR *szContent = TEXT("你好,世界");
	static const TCHAR szTitle[] = TEXT("打招呼");
	MessageBox(NULL, szContent, szTitle, MB_ABORTRETRYIGNORE|MB_ICONINFORMATION| MB_DEFBUTTON1);
	return 0;
}

微软 2013版本的模板

cpp 复制代码
/* -------------------------------------------------------------------
                    MyWindows.c -- 基本窗口模型
                                《Windows 程序设计(SDK)》视频教程
--------------------------------------------------------------------*/

#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("MyWindows");
    HWND hwnd;
    MSG msg;
    WNDCLASS wndclass;

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

    if (!RegisterClass(&wndclass))
    {
        MessageBox(NULL, TEXT("这个程序需要在 Windows NT 才能执行!"), szAppName, MB_ICONERROR);
        return 0;
    }

    hwnd = CreateWindow(szAppName,
        TEXT("鱼C工作室"),
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        NULL,
        NULL,
        hInstance,
        NULL);

    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;
    PAINTSTRUCT ps;
    RECT rect;

    switch (message)
    {
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);
        GetClientRect(hwnd, &rect);
        DrawText(hdc, TEXT("大家好,这是我的第一个窗口程序!"), -1, &rect,
            DT_SINGLELINE | DT_CENTER | DT_VCENTER);
        EndPaint(hwnd, &ps);
        return 0;

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

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

WNDCLASS 的文档

【新提醒】WNDCLASS 结构,《Windows程序设计(SDK编程)》,Windows程序设计,鱼C论坛 - Powered by Discuz!

经常使用 wc 或者 wcex 来命名 WNDCLASS

CreatWindew

【新提醒】CreateWindow,《Windows程序设计(SDK编程)》,Windows程序设计,鱼C论坛 - Powered by Discuz!

Windows 窗口的诞生过程。

定义窗口类结构(WNDCLASS) ->

注册窗口类(RegisterClass) ->

创建窗口(CreateWindow) ->

显示窗口(ShowWindow) ->

更新窗口(UpdateWindow) ->

消息循环(GetMessage -> TranslateMessage ->DispatchMessage

消息队列

1. 关于消息机制,还有三点需要补充:

  • 消息队列是FIFO的形式
  • WM_PAINT,WM_TIMER 和 WM_QUIT 这三个消息属于特例,操作系统会把它们时刻放在消息队列的最后
  • 消息其实会细分为队列化消息和非队列化消息

2. 掌握基本的调试程序方法:

如果调试的时候遇到诸如无法加载PDB文件、符号表之类的问题,请在【调试】->【选项和设置】的调试->符号项,勾选"Microsoft 符号服务器",然后点击调试就会自动从微软的服务器开始自动加载需要的符号表了。

windows 的三大核心部件

设备环境句柄:

在 WndProc中使用:

cpp 复制代码
hdc = BeginPaint(hwnd, &ps);
        // 使用GDI函数
EndPaint(hwnd, &ps);

在任何地方使用:

cpp 复制代码
hdc = GetDC(hwnd);
        // 使用GDI函数
ReleaseDC(hwnd, hdc);

四个常用的字符串处理函数:

wsprintf

cpp 复制代码
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
for (i = 0; i < 10; i++) {
    wsprintf(szText, TEXT("第 %d 行 : %s"), i + 1, TEXT("I love windows!"));
    TextOut(hdc, 10, 10 + i * 20, szText, lstrlen(szText));
}

缓冲区溢出

缓冲区溢出是黑客 最常用的攻击方式,所以微软官方 给字符串更安全的方式 来防止这种攻击。

需要包含这个头文件

cpp 复制代码
#include<strsafe.h>
cpp 复制代码
    HDC hdc;
    PAINTSTRUCT ps;
    RECT rect;
    TCHAR szText[128] = TEXT("I love windows!");
    int i;
    size_t iTarget;

    switch (message)
    {
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);
        GetClientRect(hwnd, &rect);
        for (i = 0; i < 10; i++) {
            //wsprintf(szText, TEXT("第 %d 行 : %s"), i + 1, TEXT("I love windows!"));
            StringCchPrintf(szText, 128, TEXT("第 %d 行 : %s"), i + 1, TEXT("I love windows!"));
            StringCchLength(szText, 128, &iTarget);
            TextOut(hdc, 10, 10 + i * 20, szText, iTarget);
        }
        
        EndPaint(hwnd, &ps);
        return 0;
        break;

不要猜测 文本的尺寸

会前7个就行。

cpp 复制代码
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    RECT rect;
    TCHAR szText[128];
    int i;
    size_t iTarget;
    TEXTMETRIC tm;
    static int cxChar, cyChar;

    switch (message)
    {
    case WM_CREATE:
        hdc = GetDC(hwnd);
        GetTextMetrics(hdc, &tm);
        cxChar = tm.tmAveCharWidth;
        cyChar = tm.tmHeight + tm.tmExternalLeading;
        ReleaseDC(hwnd, hdc);
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);
        GetClientRect(hwnd, &rect);
        for (i = 0; i < 10; i++) {
            //wsprintf(szText, TEXT("第 %d 行 : %s"), i + 1, TEXT("I love windows!"));
            StringCchPrintf(szText, 128, TEXT("第 %d 行 : %s"), i + 1, TEXT("I love windows!"));
            StringCchLength(szText, 128, &iTarget);
            TextOut(hdc, 10, 10 + i * cyChar, szText, iTarget);
        }

获取窗口的各种尺寸

cpp 复制代码
cxClient = GetSystemMetrics(SM_CXSCREEN);
cyClient = GetSystemMetrics(SM_CYSCREEN);

获取当前屏幕分辨率的宽高

cpp 复制代码
 HDC hdc;
 PAINTSTRUCT ps;
 RECT rect;
 TCHAR szText[128];
 int i;
 size_t iTarget;
 TEXTMETRIC tm;
 static int cxChar, cyChar;
 static int cxClient, cyClient;

 switch (message)
 {
 case WM_CREATE:
     hdc = GetDC(hwnd);
     GetTextMetrics(hdc, &tm);
     cxChar = tm.tmAveCharWidth;
     cyChar = tm.tmHeight + tm.tmExternalLeading;
     ReleaseDC(hwnd, hdc);
 case WM_PAINT:
     hdc = BeginPaint(hwnd, &ps);
     GetClientRect(hwnd, &rect);
     cxClient = GetSystemMetrics(SM_CXSCREEN);
     cyClient = GetSystemMetrics(SM_CYSCREEN);
     //for (i = 0; i < 10; i++) {
     //    //wsprintf(szText, TEXT("第 %d 行 : %s"), i + 1, TEXT("I love windows!"));
     //    StringCchPrintf(szText, 128, TEXT("第 %d 行 : %s"), i + 1, TEXT("I love windows!"));
     //    StringCchLength(szText, 128, &iTarget);
     //    TextOut(hdc, 10, 10 + i * cyChar, szText, iTarget);
     //}

     StringCchPrintf(szText, 128, TEXT("当前屏幕分辨率: %d x %d px"), cxClient, cyClient);
     StringCchLength(szText, 128, &iTarget);
     TextOut(hdc, 10, 10 + 10 * cyChar, szText, iTarget);
     EndPaint(hwnd, &ps);
     return 0;
     break;

获取窗口尺寸

每次窗口大小发生改变,系统会自动发送WM_SIZE 消息。

把新的宽度和高度 存放在 lParam 参数的 低16位 和 高16位

LOWORD()

HIWORD()

cpp 复制代码
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    RECT rect;
    TCHAR szBuffer[128];

    switch (message)
    {
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);
        GetClientRect(hwnd, &rect);
        DrawText(hdc, TEXT("大家好,这是我的第一个窗口程序!"), -1, &rect,
            DT_SINGLELINE | DT_CENTER | DT_VCENTER);
        EndPaint(hwnd, &ps);
        break;
    case WM_SIZE:
        hdc = GetDC(hwnd);
        wsprintf(szBuffer, TEXT("窗口大小变为:%d x %d"), LOWORD(lParam), HIWORD(lParam));
        
        TextOut(hdc, 10, 10, szBuffer, lstrlen(szBuffer));
        ReleaseDC(hwnd, hdc);
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

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

滚动条

| WS_VSCROLL | WS_HSCROLL

CreateWindow 的时候,第三个参数加上 WS_VSCROLL 或 WS_HSCROLL 即可添加对应的垂直滚动条或水平滚动条。

当用户单击滚动条或拖动滑块时,Windows 就会向窗口过程发送 WM_VSCROLL 消息或 WM_HSCROLL 消息。

垂直滚动条的API文档

【新提醒】WM_HSCROLL 消息,《Windows程序设计(SDK编程)》,Windows程序设计,鱼C论坛 - Powered by Discuz!

cpp 复制代码
/*----------------------------------------------------
   SYSMETS1.C -- System Metrics Display Program No. 1
                 (c) Charles Petzold, 1998
  ----------------------------------------------------*/

#define WINVER 0x0500
#include <windows.h>
#include "sysmets.h"
#include<strsafe.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    PSTR szCmdLine, int iCmdShow)
{
    static TCHAR szAppName[] = TEXT("SysMets1");
    HWND         hwnd;
    MSG          msg;
    WNDCLASS     wndclass;

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

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

    hwnd = CreateWindow(szAppName, TEXT("Get System Metrics No. 1"),
        WS_OVERLAPPEDWINDOW | WS_VSCROLL ,
        CW_USEDEFAULT, CW_USEDEFAULT,
        CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL, hInstance, NULL);

    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)
{
    static int  cxChar, cxCaps, cyChar;
    HDC         hdc;
    int         i;
    PAINTSTRUCT ps;
    TCHAR       szBuffer[10];
    TEXTMETRIC  tm;       // 文本度量结构体:存储字体的尺寸信息(如字符宽度/高度)
    static int cxClient;

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

        GetTextMetrics(hdc, &tm);// 获取当前字体的文本度量信息(填充TEXTMETRIC结构体)
        cxChar = tm.tmAveCharWidth;
        //大写 字母1.5倍宽 小写字母1倍宽
        cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2;
        cyChar = tm.tmHeight + tm.tmExternalLeading;

        ReleaseDC(hwnd, hdc);
        return 0;
    case WM_SIZE:
        cxClient = LOWORD(lParam);
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);

        for (i = 0; i < NUMLINES; i++)
        {
            TextOut(hdc, 0, cyChar * i,
                sysmetrics[i].szLabel,
                lstrlen(sysmetrics[i].szLabel));

            TextOut(hdc, 22 * cxCaps, cyChar * i,
                sysmetrics[i].szDesc,
                lstrlen(sysmetrics[i].szDesc));

            SetTextAlign(hdc, TA_RIGHT | TA_TOP);//右边缘顶部对齐

            TextOut(hdc, 22 * cxCaps + 40 * cxChar, cyChar * i, szBuffer,
				wsprintf(szBuffer, TEXT("%5d"),//格式化字符串:将系统指标值转换为字符串并存储在szBuffer中
					GetSystemMetrics(sysmetrics[i].iIndex)));//获取系统指标值并显示在窗口中

			SetTextAlign(hdc, TA_LEFT | TA_TOP);//恢复默认的左边缘顶部对齐
        }
        EndPaint(hwnd, &ps);
        return 0;
    case WM_VSCROLL:
        hdc = GetDC(hwnd);
        SetTextAlign(hdc, TA_RIGHT | TA_TOP);
        
        switch (LOWORD(wParam)) {
            case SB_LINEUP:
                TextOut(hdc, cxClient - 20, 0, TEXT("向上滚动一行。。。"), lstrlen(TEXT("向上滚动一行。。。")));
                break;
            case SB_LINEDOWN:
                // 向下滚动一行 → 第2行(y=cyChar)
                TextOut(hdc, cxClient , 0,
                    TEXT("向下滚动一行。。。"),
                    lstrlen(TEXT("向下滚动一行。。。")));
                break;

            case SB_PAGEUP:
                // 向上滚动一页 → 第3行(y=2*cyChar)
                TextOut(hdc, cxClient, 0,
                    TEXT("向上滚动一页。。。"),
                    lstrlen(TEXT("向上滚动一页。。。")));
                break;

            case SB_PAGEDOWN:
                // 向下滚动一页 → 第4行(y=3*cyChar)
                TextOut(hdc, cxClient, 0,
                    TEXT("向下滚动一页。。。"),
                    lstrlen(TEXT("向下滚动一页。。。")));
                break;

            case SB_THUMBTRACK:
                // 拖动滑块 → 第5行(y=4*cyChar),还能显示当前位置
                wsprintf(szBuffer, TEXT("别抓着我不放。。。"));
                TextOut(hdc, cxClient,0,
                    szBuffer, lstrlen(szBuffer));
                break;
        }
        SetTextAlign(hdc, TA_LEFT | TA_TOP);
        ReleaseDC(hwnd, hdc);
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}

在消息循环队列中 创建的时候 试着滚动条

SetScrollRange 函数设置所指定滚动条范围的最小值和最大值。

【新提醒】SetScrollRange,《Windows程序设计(SDK编程)》,Windows程序设计,鱼C论坛 - Powered by Discuz!

cpp 复制代码
  SetScrollRange(hwnd, SB_VERT, 0, NUMLINES - 1, FALSE);

一般来说 设置位false 就可以。

进度条的更新分为几步:

1 新定义的变量

iVscrollPos 当前进度条的位置,

iOldPos 用于后续对比 是否需要重绘

2 更新的逻辑是

cpp 复制代码
TextOut(hdc, 0, cyChar * i - iVscrollPos * cyChar,
    sysmetrics[i].szLabel,
    lstrlen(sysmetrics[i].szLabel));

cyChar * i - iVscrollPos * cyChar 这里每行打印的时候 i代表当前的行数 要减去进度条 下滑的位置。

注意边界检测 如果这里 的当前要画的行数 超出client了 那这一行就直接不画了 进入下一个循环

cpp 复制代码
// 只绘制可见区域的文字(优化:避免绘制窗口外的内容)
if (yPos + cyChar < 0 || yPos > cyClient)
    continue;

3 使用max min函数 当遇到向上滚动一行的时候 进度条位置就要-1,但是最小是0。其他同理

cpp 复制代码
case SB_LINEUP:
                // 向上滚动一行:位置-1,最小为0
                iVscrollPos = max(0, iVscrollPos - 1);
                wsprintf(szBuffer, TEXT("向上滚动一行。。。"));
                break;

当遇到下翻一页的时候 用cyClient / cyChar 来计算下翻的行数。

cpp 复制代码
case SB_PAGEDOWN:
                // 向下滚动一页:位置+每页行数,最大为NUMLINES-1
                iVscrollPos = min(NUMLINES - 1, iVscrollPos + (cyClient / cyChar));
                wsprintf(szBuffer, TEXT("向下滚动一页。。。"));
                break;

最后每次 进度条的位置有变化 必须跟新进度条的显示位置 而且 还要重绘触发 WM_PAINT

InvalidateRect

InvalidateRect 本身不直接重绘窗口 ,但它会告诉系统:"这个窗口的某块区域'脏了 / 无效了',需要重新绘制",系统会在合适的时机向窗口发送 WM_PAINT 消息。

注意这个函数不是实时更新,当windows 比较忙的时候 会延迟调用MW_PAINT

如果要马上更新 需要调用UpdateWindow 函数。

cpp 复制代码
// 只有滚动位置变化时,才更新滚动条+触发重绘
        if (iVscrollPos != iOldPos)
        {
            // 关键修复:去掉负号,直接用iVscrollPos设置滚动条位置
            SetScrollPos(hwnd, SB_VERT, iVscrollPos, TRUE);
            // 触发窗口重绘,画面才会跟随滚动位置更新
            InvalidateRect(hwnd, NULL, TRUE);
        }
        break;

完整代码:

cpp 复制代码
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static int  cxChar, cxCaps, cyChar;
    HDC         hdc;
    int         i;
    PAINTSTRUCT ps;
    TCHAR       szBuffer[10];
    TEXTMETRIC  tm;       // 文本度量结构体:存储字体的尺寸信息(如字符宽度/高度)
    static int cxClient, cyClient;
    static int iVscrollPos;
    int iOldPos;
    switch (message)
    {
    case WM_CREATE:
        hdc = GetDC(hwnd);

        GetTextMetrics(hdc, &tm);// 获取当前字体的文本度量信息(填充TEXTMETRIC结构体)
        cxChar = tm.tmAveCharWidth;
        //大写 字母1.5倍宽 小写字母1倍宽
        cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2;
        cyChar = tm.tmHeight + tm.tmExternalLeading;
        iVscrollPos = 0;

        SetScrollRange(hwnd, SB_VERT, 0, NUMLINES - 1, FALSE);
        SetScrollPos(hwnd, SB_VERT, iVscrollPos, TRUE);


        ReleaseDC(hwnd, hdc);
        return 0;
    case WM_SIZE:
        cxClient = LOWORD(lParam);
        cyClient = HIWORD(lParam);
        break;
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);

        for (i = 0; i < NUMLINES; i++)
        {
            // 计算当前行的y坐标:总行高*i - 滚动位置*行高
            int yPos = cyChar * i - iVscrollPos * cyChar;
            // 只绘制可见区域的文字(优化:避免绘制窗口外的内容)
            if (yPos + cyChar < 0 || yPos > cyClient)
                continue;


            TextOut(hdc, 0, cyChar * i - iVscrollPos * cyChar,
                sysmetrics[i].szLabel,
                lstrlen(sysmetrics[i].szLabel));

            TextOut(hdc, 22 * cxCaps, cyChar * i - iVscrollPos * cyChar,
                sysmetrics[i].szDesc,
                lstrlen(sysmetrics[i].szDesc));

            SetTextAlign(hdc, TA_RIGHT | TA_TOP);//右边缘顶部对齐

            TextOut(hdc, 22 * cxCaps + 40 * cxChar, cyChar * i - iVscrollPos * cyChar, szBuffer,
				wsprintf(szBuffer, TEXT("%5d"),//格式化字符串:将系统指标值转换为字符串并存储在szBuffer中
					GetSystemMetrics(sysmetrics[i].iIndex)));//获取系统指标值并显示在窗口中

			SetTextAlign(hdc, TA_LEFT | TA_TOP);//恢复默认的左边缘顶部对齐
        }
        EndPaint(hwnd, &ps);
        return 0;
    case WM_VSCROLL:
        hdc = GetDC(hwnd);
        SetTextAlign(hdc, TA_RIGHT | TA_TOP);
        iOldPos = iVscrollPos;

        switch (LOWORD(wParam)) {
            case SB_LINEUP:
                iVscrollPos = max(0, iVscrollPos - 1);
                TextOut(hdc, cxClient - 20, 0, TEXT("向上滚动一行。。。"), lstrlen(TEXT("向上滚动一行。。。")));
                SetScrollPos(hwnd, SB_VERT, iVscrollPos, TRUE);
                break;
            case SB_LINEDOWN:
                // 向下滚动一行 → 第2行(y=cyChar)
                iVscrollPos = min(NUMLINES - 1, iVscrollPos + 1);
                TextOut(hdc, cxClient , 0,
                    TEXT("向下滚动一行。。。"),
                    lstrlen(TEXT("向下滚动一行。。。")));
                SetScrollPos(hwnd, SB_VERT, iVscrollPos, TRUE);
                break;

            case SB_PAGEUP:
                // 
                iVscrollPos = max(0, iVscrollPos - (cyClient / cyChar));
                TextOut(hdc, cxClient, 0,
                    TEXT("向上滚动一页。。。"),
                    lstrlen(TEXT("向上滚动一页。。。")));
                SetScrollPos(hwnd, SB_VERT, iVscrollPos, TRUE);
                break;

            case SB_PAGEDOWN:
                iVscrollPos = min(NUMLINES - 1, iVscrollPos + (cyClient / cyChar));
                
                TextOut(hdc, cxClient, 0,
                    TEXT("向下滚动一页。。。"),
                    lstrlen(TEXT("向下滚动一页。。。")));
                SetScrollPos(hwnd, SB_VERT, iVscrollPos, TRUE);
                break;

            case SB_THUMBPOSITION:
                // 
                iVscrollPos = HIWORD(wParam);
                wsprintf(szBuffer, TEXT("别抓着我不放。。。"));
                TextOut(hdc, cxClient,0,
                    szBuffer, lstrlen(szBuffer));
                break;
        }
        if (iOldPos != iVscrollPos) {
            SetScrollPos(hwnd, SB_VERT, iVscrollPos, TRUE);
            InvalidateRect(hwnd, NULL, TRUE);
        }
        SetTextAlign(hdc, TA_LEFT | TA_TOP);
        ReleaseDC(hwnd, hdc);
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}

我想让最后一行 是文本的最后一行 避免文本出现大量空白。实现和普通网页一样 逻辑,做了下调整。

其实就是在 获得到客户区大小的时候 再次修改进度条范围 就完成了。

完整代码:

cpp 复制代码
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static int  cxChar, cxCaps, cyChar;
    HDC         hdc;
    int         i;
    PAINTSTRUCT ps;
    TCHAR       szBuffer[10];
    TEXTMETRIC  tm;       // 文本度量结构体:存储字体的尺寸信息(如字符宽度/高度)
    static int cxClient, cyClient;
    static int iVscrollPos;
    int iOldPos;
    switch (message)
    {
    case WM_CREATE:
        hdc = GetDC(hwnd);

        GetTextMetrics(hdc, &tm);// 获取当前字体的文本度量信息(填充TEXTMETRIC结构体)
        cxChar = tm.tmAveCharWidth;
        //大写 字母1.5倍宽 小写字母1倍宽
        cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2;
        cyChar = tm.tmHeight + tm.tmExternalLeading;
        iVscrollPos = 0;
        
        SetScrollRange(hwnd, SB_VERT, 0, NUMLINES - (cyClient / cyChar), TRUE);
        SetScrollPos(hwnd, SB_VERT, iVscrollPos, TRUE);


        ReleaseDC(hwnd, hdc);
        return 0;
    case WM_SIZE:
        cxClient = LOWORD(lParam);
        cyClient = HIWORD(lParam);
        SetScrollRange(hwnd, SB_VERT, 0, NUMLINES - (cyClient / cyChar), TRUE);
        SetScrollPos(hwnd, SB_VERT, iVscrollPos, TRUE);
        break;
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);

        for (i = 0; i < NUMLINES; i++)
        {
            // 计算当前行的y坐标:总行高*i - 滚动位置*行高
            int yPos = cyChar * i - iVscrollPos * cyChar;
            // 只绘制可见区域的文字(优化:避免绘制窗口外的内容)
            if (yPos + cyChar < 0 || yPos > cyClient)
                continue;


            TextOut(hdc, 0, cyChar * i - iVscrollPos * cyChar,
                sysmetrics[i].szLabel,
                lstrlen(sysmetrics[i].szLabel));

            TextOut(hdc, 22 * cxCaps, cyChar * i - iVscrollPos * cyChar,
                sysmetrics[i].szDesc,
                lstrlen(sysmetrics[i].szDesc));

            SetTextAlign(hdc, TA_RIGHT | TA_TOP);//右边缘顶部对齐

            TextOut(hdc, 22 * cxCaps + 40 * cxChar, cyChar * i - iVscrollPos * cyChar, szBuffer,
				wsprintf(szBuffer, TEXT("%5d"),//格式化字符串:将系统指标值转换为字符串并存储在szBuffer中
					GetSystemMetrics(sysmetrics[i].iIndex)));//获取系统指标值并显示在窗口中

			SetTextAlign(hdc, TA_LEFT | TA_TOP);//恢复默认的左边缘顶部对齐
        }
        EndPaint(hwnd, &ps);
        return 0;
    case WM_VSCROLL:
        hdc = GetDC(hwnd);
        SetTextAlign(hdc, TA_RIGHT | TA_TOP);
        iOldPos = iVscrollPos;

        switch (LOWORD(wParam)) {
            case SB_LINEUP:
                iVscrollPos = max(0, iVscrollPos - 1);
                TextOut(hdc, cxClient - 20, 0, TEXT("向上滚动一行。。。"), lstrlen(TEXT("向上滚动一行。。。")));
                SetScrollPos(hwnd, SB_VERT, iVscrollPos, TRUE);
                break;
            case SB_LINEDOWN:
                // 向下滚动一行 → 第2行(y=cyChar)
                iVscrollPos = min(NUMLINES - (cyClient / cyChar), iVscrollPos + 1);
                TextOut(hdc, cxClient , 0,
                    TEXT("向下滚动一行。。。"),
                    lstrlen(TEXT("向下滚动一行。。。")));
                SetScrollPos(hwnd, SB_VERT, iVscrollPos, TRUE);
                break;

            case SB_PAGEUP:
                // 
                iVscrollPos = max(0, iVscrollPos - (cyClient / cyChar));
                TextOut(hdc, cxClient, 0,
                    TEXT("向上滚动一页。。。"),
                    lstrlen(TEXT("向上滚动一页。。。")));
                SetScrollPos(hwnd, SB_VERT, iVscrollPos, TRUE);
                break;

            case SB_PAGEDOWN:
                iVscrollPos = min(NUMLINES - (cyClient / cyChar), iVscrollPos + (cyClient / cyChar));
                
                TextOut(hdc, cxClient, 0,
                    TEXT("向下滚动一页。。。"),
                    lstrlen(TEXT("向下滚动一页。。。")));
                SetScrollPos(hwnd, SB_VERT, iVscrollPos, TRUE);
                break;

            case SB_THUMBPOSITION:
                // 
                iVscrollPos = HIWORD(wParam);
                wsprintf(szBuffer, TEXT("别抓着我不放。。。"));
                TextOut(hdc, cxClient,0,
                    szBuffer, lstrlen(szBuffer));
                break;
        }
        if (iOldPos != iVscrollPos) {
            SetScrollPos(hwnd, SB_VERT, iVscrollPos, TRUE);
            InvalidateRect(hwnd, NULL, TRUE);
        }
        SetTextAlign(hdc, TA_LEFT | TA_TOP);
        ReleaseDC(hwnd, hdc);
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}

GDI

COLORREF是什么?

它事实上就是一个 DWORD 类型,DWORD 就是一个四个字节基础类型。四个字节分别表示如下:

用一个字节表示一个颜色的颜色值,一个字节的取值范围正是 0 ~ 255。其实用三个字节来表示三原色就够了,但编程来说,数据存放和处理都是2的倍数,例如 32 位计算机每次处理数据的单位就是 4 个字节。

画点

cpp 复制代码
 case WM_PAINT:
     hdc = BeginPaint(hwnd, &ps);
     for(i = 0;i < 200;i++)
         SetPixelV(hdc, 100 + i, 100, RGB(255, 0, 0));

     EndPaint(hwnd, &ps);
     return 0;

绘制直线(线段)所需要的两个函数

MoveToEx ------ 用于指定直线的起点

LineTo ------ 用于指定直线的终点

cpp 复制代码
MoveToEx(hdc, 100, 100, NULL);
LineTo(hdc, 200, 200);

获取当前位置的坐标

使用 GetCurrentPositionEx 函数。

cpp 复制代码
for (i = 0; i < 50; i++) {
    MoveToEx(hdc, i * 50, 0, NULL);
    LineTo(hdc, i * 50, 2500);
    MoveToEx(hdc, 0, i * 50, NULL);
    LineTo(hdc, 2500, i * 50);
}
cpp 复制代码
case WM_PAINT:
    hdc = BeginPaint(hwnd, &ps);
    GetClientRect(hwnd, &rect);

    for (i = 0; i*50 < rect.right; i++) {
        MoveToEx(hdc, i * 50, 0, NULL);
        LineTo(hdc, i * 50, rect.bottom);
    }
    for (j = 0; j * 50 < rect.bottom; j++) {
        MoveToEx(hdc, 0, j*50, NULL);
        LineTo(hdc, rect.right, j * 50);
    }


    EndPaint(hwnd, &ps);
    return 0;

这个代码 只画客户去的大小 是改进后的。

polyLine

cpp 复制代码
POINT apt[] = { 30, 10, 20, 50, 50, 20, 10, 20, 40, 50, 30, 10 };

switch (message)
{
case WM_PAINT:
    hdc = BeginPaint(hwnd, &ps);
    GetClientRect(hwnd, &rect);

    Polyline(hdc, apt, 6);
    

    EndPaint(hwnd, &ps);
    return 0;

在初始化 PIOINT 的时候 ,编译器会按 "逐个结构体、逐个成员" 的顺序,把数字依次塞进每个 POINTxy

polyline 从第一个点为起点

polylineTo 是从原点为起点

polypolyline

画一个正弦函数:

cpp 复制代码
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    RECT rect;
    static int cxClient, cyClient;
    POINT   apt[NUM];
    int i;

    switch (message)
    {
    case WM_SIZE:
        cxClient = LOWORD(lParam);
        cyClient = HIWORD(lParam);
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);
        GetClientRect(hwnd, &rect);

        MoveToEx(hdc, 0, cyClient / 2, NULL);
        LineTo(hdc, cxClient, cyClient / 2);
        
        for (i = 0; i < NUM; i++) {
            apt[i].x = cxClient / NUM * i;
            apt[i].y = cyClient / 2 - (int)(sin(i * 2 * PI / NUM) * cyClient / 2);
        }
        Polyline(hdc, apt, NUM);

        EndPaint(hwnd, &ps);
        return 0;

矩形:

Rectangle

左上右下 也可以记忆为 左上角+右下角的坐标

cpp 复制代码
Rectangle(hdc, 0+10, 0+10, cxClient-10, cyClient-10);

Rectangle函数不光是绘制矩形 还给里面默认填充白色

椭圆

Ellipse

矩形的内切椭圆

圆角矩形 RundRect

先给出 矩形 左上右下 在给出圆角椭圆的 宽高

弧 Arc

先给出 限定矩形

然后给出 两个点,注意是椭圆中心 到点的连线焦点 开始 如上图。

Chord

Pie

画笔

GetStockObject

GetStockObject 负责获取系统预设的绘图工具SelectObject 负责把工具装进绘图环境(DC),决定后续绘图用什么样式。

|---------------------|--------------------------------------------------------------------------------------------------------------------|
| BLACK_BRUSH | 黑色画刷 |
| DKGRAY_BRUSH | 暗灰色画刷 |
| DC_BRUSH | 1. 纯色画刷,默认颜色是白色的 2. 调用 SetDCBrushColor 函数可以修改该值的颜色 |
| GRAY_BRUSH | 灰色画刷 |
| HOLLOW_BRUSH | 空画刷(相当于 NULL_BRUSH) |
| LTGRAY_BRUSH | 浅灰色画刷 |
| NULL_BRUSH | 空画刷(相当于 HOLLOW_BRUSH) |
| WHITE_BRUSH | 白色画刷 |
| BLACK_PEN | 黑色画笔 |
| DC_PEN | 1. 纯色画笔,默认颜色是白色的 2. 调用 SetDCPenColor 函数可以修改该值的颜色 |
| NULL_PEN | 空画笔(空画笔不绘制任何东西) |
| WHITE_PEN | 白色画笔 |
| ANSI_FIXED_FONT | Windows 中的固定间距(等宽)系统字体 |
| ANSI_VAR_FONT | Windows 中的可变间距(比例间距)系统字体 |
| DEVICE_DEFAULT_FONT | 设备相关字体 |
| DEFAULT_GUI_FONT | 1. 用户界面对象(如菜单、对话框)的默认字体 2. 不推荐使用 DEFAULT_GUI_FONT 或 SYSTEM_FONT 获得对话框或系统的字体 3. 该字体默认是 Tahoma |
| OEM_FIXED_FONT | 原始设备制造商(OEM)相关固定间距(等宽)字体 |
| SYSTEM_FONT | 1. 系统字体 2. 默认情况下,Windows 使用系统字体绘制菜单,对话框和文本 3. 不推荐使用 DEFAULT_GUI_FONT 或 SYSTEM_FONT 获得对话框或系统的字体 4. 该字体默认是 Tahoma |
| SYSTEM_FIXED_FONT | 1. 固定间距(等宽)系统字体 2. 该对象仅为兼容 16 位 Windows 版本提供 |
| DEFAULT_PALETTE | 默认调色板(该调色板由系统调色板中的静态色彩组成) |

SelectObject

cpp 复制代码
 case WM_PAINT:
     hdc = BeginPaint(hwnd, &ps);
     GetClientRect(hwnd, &rect);
     hPen = (HPEN)GetStockObject(BLACK_PEN);
     hOldPen = (HPEN)SelectObject(hdc, hPen);
     MoveToEx(hdc, rect.left, rect.bottom/2, NULL);
     LineTo(hdc, rect.right, rect.bottom/2);
     SelectObject(hdc, hOldPen);
     EndPaint(hwnd, &ps);
     return 0;

这是完整 正规的流程画图。

如果windows的画笔 不满足你的要求 你可以自己创建

CreatePen

默认情况下 虚线之间的空隙是由白色来填充

SetBkColor

注意 粗细必须要是1 才能生效 虚线!!!

SetBkmode

设置背景透明。

OPAQUE

TRANSPARENT

原本绿色的背景色 变透明了

fnPenstyle

|----------------|----------------------------------------------------------|
| | 含义 |
| PS_SOLID | 实线 |
| PS_DASH | 虚线(nWidth 参数的值必须 <= 1) |
| PS_DOT | 点线(nWidth 参数的值必须 <= 1) |
| PS_DASHDOT | 点划线(nWidth 参数的值必须 <= 1) |
| PS_DASHDOTDOT | 点点划线(nWidth 参数的值必须 <= 1) |
| PS_NULL | 该画笔看不见 |
| PS_INSIDEFRAME | 该画笔用于在 GDI 函数绘图中实现,绘制椭圆、矩形、圆角矩形、饼图以及弦等生成的封闭对象框时,画笔宽度向内扩展 |

抖动色

PS_INSIDEFRAME 是唯一一个可以使用抖动色的样式

在只有 16 色、256 色这种少颜色的年代,用「黑白像素交叉排列」,骗眼睛看成「灰色 / 过渡色」,这就叫 Dithering(抖动混色)。 Windows 实现它的方法就是:用小图案做画刷,填充图形

cpp 复制代码
 hPen = CreatePen(PS_SOLID, 50, RGB(255, 0, 0));

就是 这一行代码 改一下 之前是从windows拿 用的 GetStockObject

DeleteObject

只有 createPen 函数创建出来的才能删除。

GetObject

cpp 复制代码
// 假设我们有一个画笔句柄 hPen
HPEN hPen;

// 1. 定义一个 LOGPEN 结构体来接收画笔信息
LOGPEN logPen;

// 2. 调用 GetObject 获取信息
GetObject(hPen, sizeof(LOGPEN), &logPen);

// 3. 现在可以访问 logPen 的成员来获取画笔属性
// logPen.lopnStyle: 画笔样式 (PS_SOLID, PS_DASH 等)
// logPen.lopnWidth: 画笔宽度 (POINT 结构,x 为宽度,y 为 0)
// logPen.lopnColor: 画笔颜色

绘图模式(混合模式)

当 Windows 使用一个画笔绘制直线时,它实际上是在将画笔的像素颜色和目标显示表面的像素颜色按位进行布尔运算。

对像素颜色执行一个按位布尔运算,我们称之为"光栅操作"(Raster Operation),简称"ROP",因为绘制一条直线涉及画笔的颜色和目标的颜色两种,所以这样的布尔运算就被称为"二元光栅操作",或者"ROP2",所以大家看到相关的函数 SetROP2 或者 GetROP2 并不是第二代的意思噢。

举个例子,我们想让客户区上的一条直线永远都显示出来,也就是说不会被背景的颜色覆盖了,那么我们可以这么做:

cpp 复制代码
 SetROP2(hdc, R2_NOT);

这样的话,无论设置什么颜色的客户区背景,画笔的颜色永远跟背景是相反的。

|----------------|------------------------------------------------|
| R2_BLACK | 显示的颜色总是黑色 |
| R2_COPYPEN | 显示当前画笔的颜色 |
| R2_MASKNOTPEN | 显示的颜色是画笔颜色的取反值和屏幕颜色的交集,即:R2_NOTCOPYPEN & 屏幕颜色 |
| R2_MASKPEN | 显示的颜色是画笔颜色和屏幕颜色的交集,即:R2_COPYPEN & 屏幕颜色 |
| R2_MASKPENNOT | 显示的颜色是画笔颜色和屏幕颜色的取反值的交集,即:R2_COPYPEN & R2_NOT |
| R2_MERGENOTPEN | 显示的颜色是画笔颜色的取反值和屏幕颜色的并集,即:R2_NOTCOPYPEN | 屏幕颜色 |
| R2_MERGEPEN | 显示的颜色是画笔颜色和屏幕颜色的并集,即:R2_COPYPEN | 屏幕颜色 |
| R2_MERGEPENNOT | 显示的颜色是画笔颜色和屏幕颜色的取反值的并集,即:R2_COPYPEN | R2_NOT |
| R2_NOP | 不操作,颜色保持不变 |
| R2_NOT | 显示的颜色是屏幕颜色的取反值 |
| R2_NOTCOPYPEN | 显示的颜色是当前画笔颜色的取反值 |
| R2_NOTMASKPEN | 显示的颜色是画笔颜色和屏幕颜色的交集的取反值,即:~R2_MASKPEN |
| R2_NOTMERGEPEN | 显示的颜色是画笔颜色和屏幕颜色的并集的取反值,即:~R2_MERGEPEN |
| R2_NOTXORPEN | 显示的颜色是画笔颜色和屏幕颜色的异或结果的取反值,即:~R2_XORPEN |
| R2_WHITE | 显示的颜色总是白色 |
| R2_XORPEN | 显示的颜色是画笔颜色和屏幕颜色的异或结果,即:R2_COPYPEN ^ 屏幕颜色 |

画刷

其实就是给封闭图形 加了颜色

cpp 复制代码
 HBRUSH hBrush,hOldBrush;

 switch (message)
 {
 case WM_PAINT:
     hdc = BeginPaint(hwnd, &ps);
     hBrush = (HBRUSH)GetStockObject(LTGRAY_BRUSH);
     hOldBrush = (HBRUSH)SelectObject(hdc, hBrush);
     Ellipse(hdc, cxClient / 4, cyClient / 4, cxClient / 4 * 3, cyClient / 4 * 3);

NULL_BRUSH

可以让图形中间变得透明。

CreateSolidBrush

cpp 复制代码
hBrush = CreateSolidBrush(RGB(0, 255, 0));

创建实心画刷

CreateHatchBrush

CreateHatchBrush 函数用于创建一个具有指定阴影样式和颜色的逻辑刷子。

|---------|-------------------------------------------------------------------------------------------------------|
| 参数 | 含义 |
| fnStyle | 指定画刷的阴影样式(具体请看下方) |
| clrref | 1. 指定用于画刷阴影的前景色 2. RGB 颜色使用 RGB 宏生成 COLORREF 结构 |

fnStyle

|---------------|----------------|
| | 含义 |
| HS_BDIAGONAL | 45 度向上,从左至右的阴影 |
| HS_CROSS | 水平和垂直交叉阴影 |
| HS_DIAGCROSS | 45 度交叉阴影 |
| HS_FDIAGONAL | 45 度向下,自左至右阴影 |
| HS_HORIZONTAL | 水平阴影 |
| HS_VERTICAL | 垂直阴影 |

一样的 可以配合SetBkColor 一起使用

绘制多边形

Polygon

其实和前面 Polyline 一样

只是自动帮你连到开头

但是 只有画多边形 才能用画刷填充颜色。

SetPolyFillMode

SetPolyFillMode 函数设置多边形的填充模式,用于填充多边形。

|-----------|------------------------------------|
| | 含义 |
| ALTERNATE | 交替模式(GDI 填充每条扫描线从多边形的奇数边到偶数边之间的区域) |
| WINDING | 螺旋模式(GDI 填充所有能够一笔完成的多边形) |

在完整的图形从左往右看,奇数边开始的封闭图形填充,偶数开始边不填充。

只有4 不能一笔完成

GDI 映射模式

cpp 复制代码
 int iMapMode;

 switch (message)
 {
 case WM_SIZE:
     cxClient = LOWORD(lParam);
     cyClient = HIWORD(lParam);
 case WM_PAINT:
     hdc = BeginPaint(hwnd, &ps);
     
     TextOut(hdc, 200, 300, TEXT("MM_TEXT"), strlen("MM_TEXT"));
     iMapMode = GetMapMode(hdc);
     SetMapMode(hdc, MM_LOMETRIC);
     TextOut(hdc, 200, -300, TEXT("MM_LOMETRIC"), 11);
     SetMapMode(hdc, iMapMode);

窗口和视口

窗口是逻辑坐标

视口是设备坐标(像素)

SetViewportOrgEx(hdc, cxClient / 2, cyClient / 2, NULL);

视口坐标定位在屏幕中间

cpp 复制代码
case WM_PAINT:
    hdc = BeginPaint(hwnd, &ps);
    
    MoveToEx(hdc, 0, cyClient / 2, NULL);
    LineTo(hdc, cxClient, cyClient / 2);
    MoveToEx(hdc, cxClient / 2, 0, NULL);
    LineTo(hdc, cxClient / 2, cyClient);
    SetViewportOrgEx(hdc, cxClient / 2, cyClient / 2, NULL);

    iMapMode = GetMapMode(hdc);
    SetMapMode(hdc, MM_LOMETRIC);
    TextOut(hdc, 100, 100, TEXT("MM_LOMETRIC"), 11);
    SetMapMode(hdc, iMapMode);

MM_ISOTROPICMM_ANISOTROPIC

设备X = (逻辑X - 窗口原点X) * (视口范围X / 窗口范围X) + 视口原点X

设备Y = (逻辑Y - 窗口原点Y) * (视口范围Y / 窗口范围Y) + 视口原点Y

只要不重新设置 逻辑坐标的原点 只设置视口原点 就能做到平移的效果。

cpp 复制代码
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);

        SetMapMode(hdc, MM_ISOTROPIC);

        SetWindowExtEx(hdc, 100, 100, NULL);
        
        //SetWindowOrgEx(hdc, 100, 100, NULL);
        
        SetViewportExtEx(hdc, cxClient, cyClient, NULL);
        SetViewportOrgEx(hdc, cxClient/2, cyClient/2, NULL);
        Ellipse(hdc, -40, 40, 40, -40);

        
        EndPaint(hwnd, &ps);

        return 0;

RECT的各种函数

SetRect

OffsetRet

移动矩形

InflateRect

放大缩小矩形

左右同时变大变小

上下左右都增大100

SetRectEmpty

矩形的所有坐标变为0 空矩形

CopyRect

IntersectRect

UnionRect ------ 计算矩形并集

PtInRect ------ 判断点是否在矩形内

点在矩形内的条件:

left ≤ pt.x < righttop ≤ pt.y < bottom(遵循 GDI 的 "左闭右开" 规则)

随机矩形的绘制

PeekMassage

cpp 复制代码
......
        while (TRUE)
        {
        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))  // 偷窥
        {
            if (msg.message == WM_QUIT)
                break;
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        else
        {
            // 让程序空闲时执行的代码
        }
        }
......

区域和裁剪

鼠标键盘消息

SendMessage

Virtual-Key Codes ------ 虚拟键代码

【新提醒】Virtual-Key Codes,《Windows程序设计(SDK编程)》,Windows程序设计,鱼C论坛 - Powered by Discuz!

WM_KEYDOWN

如果按下 方向键下 就发送 消息。

cpp 复制代码
case WM_KEYDOWN:
    switch (wParam) {
    case VK_UP:
        SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0);
        break;
    case VK_DOWN:
        SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0);
        break;
    }
    break;

字符消息

这里TranslateMessage 就是将 击键消息转换为字符消息

WM_CHARWM_DEADCHAR:由 TranslateMessage 翻译 WM_KEYDOWN(普通键按下)产生。

WM_SYSCHARWM_SYSDEADCHAR:由 TranslateMessage 翻译 WM_SYSKEYDOWN(系统键,如 Alt + 键按下)产生。

书上的代码

cpp 复制代码
/*--------------------------------------------------------
   KEYVIEW1.C -- Displays Keyboard and Character Messages
                 (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("KeyView1");
    HWND         hwnd;
    MSG          msg;
    WNDCLASS     wndclass;

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

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

    hwnd = CreateWindow(szAppName, TEXT("Keyboard Message Viewer #1"),
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL, hInstance, NULL);

    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)
{
    static int   cxClientMax, cyClientMax, cxClient, cyClient, cxChar, cyChar;
    static int   cLinesMax, cLines;
    static PMSG  pmsg;
    static RECT  rectScroll;
    static TCHAR szTop[] = TEXT("Message        Key       Char     ")
        TEXT("Repeat Scan Ext ALT Prev Tran");
    static TCHAR szUnd[] = TEXT("_______        ___       ____     ")
        TEXT("______ ____ ___ ___ ____ ____");

    static const  TCHAR* szFormat[2] = {

              TEXT("%-13s %3d %-15s%c%6u %4d %3s %3s %4s %4s"),
              TEXT("%-13s            0x%04X%1s%c %6u %4d %3s %3s %4s %4s") };

    static const TCHAR* szYes = TEXT("Yes");
    static const TCHAR* szNo = TEXT("No");
    static const TCHAR* szDown = TEXT("Down");
    static const TCHAR* szUp = TEXT("Up");
           
    static const  TCHAR* szMessage[] = {
                        TEXT("WM_KEYDOWN"),    TEXT("WM_KEYUP"),
                        TEXT("WM_CHAR"),       TEXT("WM_DEADCHAR"),
                        TEXT("WM_SYSKEYDOWN"), TEXT("WM_SYSKEYUP"),
                        TEXT("WM_SYSCHAR"),    TEXT("WM_SYSDEADCHAR") };
    HDC          hdc;
    int          i, iType;
    PAINTSTRUCT  ps;
    TCHAR        szBuffer[128], szKeyName[32];
    TEXTMETRIC   tm;

    switch (message)
    {
    case WM_CREATE:
    case WM_DISPLAYCHANGE:

        // Get maximum size of client area

        cxClientMax = GetSystemMetrics(SM_CXMAXIMIZED);
        cyClientMax = GetSystemMetrics(SM_CYMAXIMIZED);

        // Get character size for fixed-pitch font

        hdc = GetDC(hwnd);

        SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));
        GetTextMetrics(hdc, &tm);
        cxChar = tm.tmAveCharWidth;
        cyChar = tm.tmHeight;

        ReleaseDC(hwnd, hdc);

        // Allocate memory for display lines

        if (pmsg)
            free(pmsg);

        cLinesMax = cyClientMax / cyChar;
        pmsg = (PMSG)malloc(cLinesMax * sizeof(MSG));
        cLines = 0;
        // fall through
    case WM_SIZE:
        if (message == WM_SIZE)
        {
            cxClient = LOWORD(lParam);
            cyClient = HIWORD(lParam);
        }
        // Calculate scrolling rectangle

        rectScroll.left = 0;
        rectScroll.right = cxClient;
        rectScroll.top = cyChar;
        rectScroll.bottom = cyChar * (cyClient / cyChar);

        InvalidateRect(hwnd, NULL, TRUE);
        return 0;

    case WM_KEYDOWN:
    case WM_KEYUP:
    case WM_CHAR:
    case WM_DEADCHAR:
    case WM_SYSKEYDOWN:
    case WM_SYSKEYUP:
    case WM_SYSCHAR:
    case WM_SYSDEADCHAR:

        // Rearrange storage array

        for (i = cLinesMax - 1; i > 0; i--)
        {
            pmsg[i] = pmsg[i - 1];
        }
        // Store new message

        pmsg[0].hwnd = hwnd;
        pmsg[0].message = message;
        pmsg[0].wParam = wParam;
        pmsg[0].lParam = lParam;

        cLines = min(cLines + 1, cLinesMax);

        // Scroll up the display

        ScrollWindow(hwnd, 0, -cyChar, &rectScroll, &rectScroll);

        break;        // ie, call DefWindowProc so Sys messages work

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

        SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));
        SetBkMode(hdc, TRANSPARENT);//下划线 是画在一起的 设置成透明 才有下划线效果 不会遮盖文字
        TextOut(hdc, 0, 0, szTop, lstrlen(szTop));
        TextOut(hdc, 0, 0, szUnd, lstrlen(szUnd));

        for (i = 0; i < min(cLines, cyClient / cyChar - 1); i++)
        {
            iType = pmsg[i].message == WM_CHAR ||
                pmsg[i].message == WM_SYSCHAR ||
                pmsg[i].message == WM_DEADCHAR ||
                pmsg[i].message == WM_SYSDEADCHAR;

            GetKeyNameText(pmsg[i].lParam, szKeyName,
                sizeof(szKeyName) / sizeof(TCHAR));

            TextOut(hdc, 0, (cyClient / cyChar - 1 - i) * cyChar, szBuffer,
                wsprintf(szBuffer, szFormat[iType],
                    szMessage[pmsg[i].message - WM_KEYFIRST],
                    pmsg[i].wParam,
                    (PTSTR)(iType ? TEXT(" ") : szKeyName),
                    (TCHAR)(iType ? pmsg[i].wParam : ' '),
                    LOWORD(pmsg[i].lParam),
                    HIWORD(pmsg[i].lParam) & 0xFF,
                    0x01000000 & pmsg[i].lParam ? szYes : szNo,
                    0x20000000 & pmsg[i].lParam ? szYes : szNo,
                    0x40000000 & pmsg[i].lParam ? szDown : szUp,
                    0x80000000 & pmsg[i].lParam ? szUp : szDown));
        }
        EndPaint(hwnd, &ps);
        return 0;

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

鼠标

相关推荐
智者知已应修善业1 小时前
【查找指定字符串首位置与数量不区分大小写完整匹配】2025-5-3
c语言·c++·经验分享·笔记·算法
云潮汐表1 小时前
七里海潮汐表查询2026-02-26
笔记
你怎么知道我是队长1 小时前
前端学习---HTML---标签属性
前端·学习·html
yoyo君~1 小时前
从内存管理到并发架构:C++ 核心内功修炼指南
开发语言·c++·学习·无人机
汐瀼2 小时前
【AI个人学习】npm本地安装claude code白嫖minimax模型
前端·学习·npm
weixin_458872612 小时前
东华复试OJ每日3题打卡·复盘106~108
学习
芝士雪豹只抽瑞克五2 小时前
Tomcat Web应用服务器笔记
前端·笔记·tomcat
pp起床2 小时前
Gen_AI 第七课 LLM的学习过程
人工智能·学习
坚持的小马2 小时前
JVM相关笔记-jps
jvm·笔记