Windows程序设计--(四)文本输出

4.1 绘制和重绘

4.1.2 有效矩阵和无效矩阵

在擦除对话框之后,需要重画的被对话框遮住的矩形区域,这个区域称为「无效区域」或「更新区域」。正是显示区域内无效区域的存在,才会让Windows将一个WM_PAINT消息放在应用程序的消息队列中。只有在显示区域的某一部分失效时,窗口才会接受WM_PAINT消息。

Windows内部为每个窗口保存一个「绘图信息结构」,这个结构包含了包围无效区域的最小矩形的坐标以及其它信息,这个矩形就叫做「无效矩形」,有时也称为「无效区域」。如果在窗口消息处理程序处理WM_PAINT消息之前显示区域中的另一个区域变为无效,则Windows计算出一个包围两个区域的新的无效区域(以及一个新的无效矩形),并将这种变化后的信息放在绘制信息结构中。Windows不会将多个WM_PAINT消息都放在消息队列中。

4.2 GDI 简介

4.2.2 获取设备环境句柄:方法一

HDC hdc;//设备环境句柄
PAINTSTRUCT ps;//绘制结构
RECT rect;//矩形结构
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);//标明窗口绘制开始,设备环境句柄
        GetClientRect(hwnd, &rect);//获取窗口客户区的尺寸
        DrawText(hdc, TEXT("Hello Hk_Mayfly!"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
        EndPaint(hwnd, &ps);//结束窗口绘制
        return 0;

4.2.3 绘制信息结构

PAINTSTRUCT结构包含了应用程序用来绘制它所拥有的窗口客户区所需要的信息。PAINTSTRUCT的结构定义如下:

typedef struct tagPAINTSTRUCT {
  HDC hdc;
  BOOL fErase; 
  RECT rcPaint;
  BOOL fRestore;
  BOOL fIncUpdate;
  BYTE rgbReserved[32]; 
} PAINTSTRUCT, *PPAINTSTRUCT;

  当调用BeginPaint函数时, Windows将自动填充这个结构体中的成员相关属性, 程序仅能使用前三个成员, 其他为Windows内部使用。 参数一HDC hdc即为设备环境句柄, BeginPaint函数的返回值也就是这里的设备环境句柄, 简单来说就是先填充再返回; 参数二fErase决定是否擦出客户区背景, 如果为非零值则擦除背景,否则不擦除背景; 参数三rcPaint 通过指定客户区左上角和右下角的坐标确定一个要绘制的矩形范围, 即使需要更新的无效区域不是一个矩形, Windows也会把需要重绘的部分裁剪为一个矩形。

  如果你仍想重绘整个客户区, 可以在BeginPaint函数之前调用InvalidateRect( hwnd, NULL, TRUE );使整个客户区无效化。

 

4.2.4 获取设备环境句柄:方法二

#include <Windows.h>
#include <iostream>

using namespace std;

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);//消息函数声明

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)//主函数
{
    static TCHAR szAppName[] = TEXT("WNDCLASS NAME");//窗口类名称
    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(DKGRAY_BRUSH);//主窗口背景色
    wndclass.lpszMenuName = NULL;//窗口菜单
    wndclass.lpszClassName = szAppName;//窗口类名

    if (!RegisterClass(&wndclass)) {//注册窗口类,如果注册失败弹出窗口
        MessageBox(NULL, TEXT("窗口创建失败!程序需要Windows NT!(传递窗口消息为UNICODE)"), szAppName, MB_ICONERROR);//消息窗口
        
        return 0;
    }

    hwnd = CreateWindow(szAppName,                //Windows类名
                        TEXT("窗口绘制成功!"),        //窗口标题
                        WS_OVERLAPPEDWINDOW,    //窗口风格
                        CW_USEDEFAULT,            //初始化窗口位置的X坐标
                        CW_USEDEFAULT,            //初始化窗口位置的Y坐标
                        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;//绘制结构
    /*
    typedef struct tagRECT
    {
        LONG    left;
        LONG    top;
        LONG    right;
        LONG    bottom;
    } RECT
    其中left,top赋为0,因此right和bottom表示客户区的宽度和高度(像素)
    */
    RECT rect;//矩形结构

    switch (message) {//处理得到的消息
    case WM_CREATE://窗口创建发来消息
        MessageBox(hwnd, TEXT("创建成功,音乐播放"), TEXT("Windows"), MB_OK | MB_ICONINFORMATION);
        /*
        1.波形文件的名称
        2.只有当声音文件是一个资源时才有用,此处NULL表示不使用
        3.指定一组选项,表示指定了第一个参数为文件名且该段声音是以异步方式播放
        */
        PlaySound(TEXT("hellowin.wav"), NULL, SND_FILENAME | SND_ASYNC);
        return 0;
    case WM_PAINT://处理窗口绘制
        hdc = GetDC(hwnd);
        GetClientRect(hwnd, &rect);//获取窗口客户区的尺寸
        DrawText(hdc, TEXT("Hello Hk_Mayfly!"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
        ReleaseDC(hwnd, hdc);
        ValidateRect(hwnd, NULL);//因为会不断更新整个客户区,不断刷新,要使其他消息能接收,需要使客户区有效
        return 0;
    case WM_LBUTTONDOWN:
        MessageBox(hwnd, TEXT("左键按下!"), TEXT("BIU"), MB_OK | MB_ICONINFORMATION);
        return 0;
    case WM_RBUTTONDOWN:
        MessageBox(hwnd, TEXT("右键按下!"), TEXT("BIU"), MB_OK | MB_ICONINFORMATION);
        return 0;
    case WM_DESTROY://处理窗口关闭时的消息
        MessageBox(hwnd, TEXT("主窗口关闭"), TEXT("Windows"), MB_OK | MB_ICONINFORMATION);//显示一个文本字符串
        PostQuitMessage(0);//将退出消息插入消息队列,程序从消息循环退出,return msg.wParam
        return 0;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);//执行默认消息处理
}

4.2.5 TEXTOUT函数详解

TextOut (hdc, x, y, psText, iLength) ;

以下将详细地讨论这个函数。

第一个参数是设备内容句柄,它既可以是GetDC的传回值,也可以是在处理WM_PAINT消息时BeginPaint的传回值。

psText参数是指向字符串的指针,iLength是字符串中字符的个数。如果psText指向Unicode字符串,则字符串中的字节数就是iLength值的两倍。字符串中不能包含任何ASCII控制字符(如回车、换行、制表或退格),Windows会将这些控制字符显示为实心块。Text0ut不识别作为字符串结束标志的内容为零的字节(对于Unicode,是一个短整数型态的0),而需要由nLength参数指明长度。

TextOut中的x和y定义显示区域内字符串的开始位置,x是水平位置,y是垂直位置。字符串中第一个字符的左上角位于坐标点(x,y)。在内定的设备内容中,原点(x和y均为0的点)是显示区域的左上角。如果在TextOut中将x和y设为0,则将从显示区域左上角开始输出字符串。

4.2.6 系统字体

设备内容还定义了在您呼叫TextOut显示文字时Windows使用的字体。内定字体为「系统字体」,或用Windows表头文件中的标识符,即SYSTEM_FONT。系统字体是Windows用来在标题列、菜单和对话框中显示字符串的内定字体。

4.2.7 字符大小

GetTextMetrics需要的TEXTMETRIC型态的结构:

typedef struct tagTEXTMETRIC {
  LONG tmHeight;              //字符高度
  LONG tmAscent;              //字符上部高度(基线以上)
  LONG tmDescent;             //字符下部高度(基线以下)
  LONG tmInternalLeading,     //由tmHeight定义的字符高度的顶部空间数目
  LONG tmExternalLeading,     //夹在两行之间的空间数目
  LONG tmAveCharWidth,        //平均字符宽度
  LONG tmMaxCharWidth,        //最宽字符的宽度
  LONG tmWeight;              //字体的粗细轻重程度
  LONG tmOverhang,            //加入某些拼接字体上的附加高度
  LONG tmDigitizedAspectX,    //字体设计所针对的设备水平方向
  LONG tmDigitizedAspectY,    //字体设计所针对的设备垂直方向
  BCHAR tmFirstChar;          //为字体定义的第一个字符
  BCHAR tmLastChar;           //为字体定义的最后一个字符
  BCHAR tmDefaultChar;        //字体中所没有字符的替代字符
  BCHAR tmBreakChar;          //用于拆字的字符
  BYTE tmItalic,              //字体为斜体时非零
  BYTE tmUnderlined,          //字体为下划线时非零
  BYTE tmStruckOut,           //字体被删去时非零
  BYTE tmPitchAndFamily,      //字体间距(低4位)和族(高4位)
  BYTE tmCharSet;             //字体的字符集
} TEXTMETRIC;

总共20个字段,我们只关心前7个。默认的映射模式为MM_TEXT

TextOut的两种运用,将上面代码的WM_PAINT部分替换即可。

第一种

TEXTMETRIC tm;
TCHAR szBuffer[100];
int iLength = wsprintf(szBuffer, TEXT("Unicode形式输出!"));
case WM_PAINT://处理窗口绘制
    hdc = BeginPaint(hwnd, &ps);
    GetClientRect(hwnd, &rect);
    TextOut(hdc, 400, 200, szBuffer, iLength);
    EndPaint(hwnd, &ps);
    return 0;

第二种:

在switch前面定义一个结构变量tm

TEXTMETRIC tm;
TCHAR szBuffer[100];
int iLength = wsprintf(szBuffer, TEXT("Unicode形式输出!"));
case WM_PAINT://处理窗口绘制
    hdc = GetDC(hwnd);
    GetTextMetrics(hdc, &tm);
    TextOut(hdc, 400, 200, szBuffer, iLength);
    ReleaseDC(hwnd, hdc);
    ValidateRect(hwnd, NULL);
    return 0;

4.2.8 文本尺寸大小

tmHeight,它是tmAscent和tmDescent的和。这两个值表示了基准在线下字符的最大纵向高度。「间距」(leading)指打印机在两行文字间插入的空间。在TEXTMETRIC结构中,内部的间距包括在tmAscent中(因此也在tmHeight中),并且它经常是重音符号出现的地方。tmInternalLeading字段可被设成0,在这种情况下,加重音的字母会稍稍缩短以便容纳重音符号。

字段tmExternalLeading,它是字体设计者建议加在横向字符之间的空间大小。

TEXTMETRICS结构包含有描述字符宽度的两个字段,即tmAveCharWidth(小写字母加权平均宽度)和tmMaxCharWidth(字体中最宽字符的宽度)。

4.2.9 文本的格式化

4.2.10 综合使用

SYSMETS.h
#include <Windows.h>

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

struct
{
    const TCHAR* szName;
    int iAge;
    const TCHAR* szMajor;
}
sysmetrics[]={
    TEXT("刘然"), 20, TEXT("软件工程"),
    TEXT("黄鹤"), 21, TEXT("计算机科学"),
    TEXT("马平"), 20, TEXT("网络工程"),
    TEXT("评然"), 20, TEXT("软件工程"),
    TEXT("里鹤"), 21, TEXT("计算机科学"),
    TEXT("高平"), 20, TEXT("网络工程"),
    TEXT("偶然"), 20, TEXT("软件工程"),
    TEXT("炮打"), 21, TEXT("计算机科学"),
    TEXT("马平"), 20, TEXT("网络工程"),
    TEXT("刘然"), 20, TEXT("软件工程"),
    TEXT("黄鹤"), 21, TEXT("计算机科学"),
    TEXT("马平"), 20, TEXT("网络工程")
};
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include "SYSMETS.h"

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);//消息函数声明

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)//主函数
{
    static TCHAR szAppName[] = TEXT("WNDCLASS NAME");//窗口类名称
    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!(传递窗口消息为UNICODE)"), szAppName, MB_ICONERROR);//消息窗口

        return 0;
    }

    hwnd = CreateWindow(szAppName,    //Windows类名
        TEXT("窗口绘制成功!"),        //窗口标题
        WS_OVERLAPPEDWINDOW,        //窗口风格
        CW_USEDEFAULT,                //初始化窗口位置的X坐标
        CW_USEDEFAULT,                //初始化窗口位置的Y坐标
        500,                        //初始化窗口长度大小
        300,                        //初始化窗口宽度大小
        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;
    int i = 0;
    TCHAR szBuffer[100];
    HDC hdc;//设备环境句柄
    PAINTSTRUCT ps;//绘制结构
    TEXTMETRIC tm;

    switch (message) {//处理得到的消息
    case WM_CREATE://窗口创建发来消息
        hdc = GetDC(hwnd);
        GetTextMetrics(hdc, &tm);
        cxChar = tm.tmAveCharWidth;        //得到字体平均宽度
        //cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2;//判断字体是等宽字体还是变宽字体,变宽字体是cxChar的1.5倍
        cyChar = tm.tmHeight + tm.tmExternalLeading;// //字体高度, 总高度tmHeight + 两行文字之间的建议间距大小tmExternalLeading
        ReleaseDC(hwnd, hdc);
        return 0;
    case WM_PAINT://处理窗口绘制
        hdc = BeginPaint(hwnd, &ps);
        for (i = 0; i < NUMLINES; ++i) {
            //SetTextAlign(hdc, TA_RIGHT | TA_TOP);//设置TextOut使用的坐标将从右上角开始
            TextOut(hdc, 0, cyChar * i, sysmetrics[i].szName, lstrlen(sysmetrics[i].szName));
            //TextOut(hdc, 400, cyChar * i, szBuffer, wsprintf(szBuffer, TEXT("%2d"), GetSystemMetrics(sysmetrics[i].iAge)));
            TextOut(hdc, 400, cyChar * i, szBuffer, wsprintf(szBuffer, TEXT("%2d"), sysmetrics[i].iAge));
            TextOut(hdc, 200, cyChar * i, sysmetrics[i].szMajor, lstrlen(sysmetrics[i].szMajor));
        }
     EndPaint(hwnd,&ps);
return 0; case WM_LBUTTONDOWN: MessageBox(hwnd, TEXT("左键按下!"), TEXT("BIU"), MB_OK | MB_ICONINFORMATION); return 0; case WM_RBUTTONDOWN: MessageBox(hwnd, TEXT("右键按下!"), TEXT("BIU"), MB_OK | MB_ICONINFORMATION); return 0; case WM_DESTROY://处理窗口关闭时的消息 MessageBox(hwnd, TEXT("主窗口关闭"), TEXT("Windows"), MB_OK | MB_ICONINFORMATION);//显示一个文本字符串 PostQuitMessage(0);//将退出消息插入消息队列,程序从消息循环退出,return msg.wParam return 0; } return DefWindowProc(hwnd, message, wParam, lParam);//执行默认消息处理 }

SYSMETS.h
#include <Windows.h>

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

struct
{
    const TCHAR* szName;
    int iAge;
    const TCHAR* szMajor;
}
sysmetrics[]={
    TEXT("刘然"), 20, TEXT("软件工程"),
    TEXT("黄鹤"), 21, TEXT("计算机科学"),
    TEXT("马平"), 20, TEXT("网络工程"),
    TEXT("评然"), 20, TEXT("软件工程"),
    TEXT("里鹤"), 21, TEXT("计算机科学"),
    TEXT("高平"), 20, TEXT("网络工程"),
    TEXT("偶然"), 20, TEXT("软件工程"),
    TEXT("炮打"), 21, TEXT("计算机科学"),
    TEXT("马平"), 20, TEXT("网络工程"),
    TEXT("刘然"), 20, TEXT("软件工程"),
    TEXT("黄鹤"), 21, TEXT("计算机科学"),
    TEXT("马平"), 20, TEXT("网络工程"),
    TEXT("评然"), 20, TEXT("软件工程"),
    TEXT("里鹤"), 21, TEXT("计算机科学"),
    TEXT("高平"), 20, TEXT("网络工程"),
    TEXT("偶然"), 20, TEXT("软件工程"),
    TEXT("炮打"), 21, TEXT("计算机科学"),
    TEXT("马平"), 20, TEXT("网络工程"),
    TEXT("刘然"), 20, TEXT("软件工程"),
    TEXT("黄鹤"), 21, TEXT("计算机科学"),
    TEXT("马平"), 20, TEXT("网络工程"),
    TEXT("评然"), 20, TEXT("软件工程"),
    TEXT("里鹤"), 21, TEXT("计算机科学"),
    TEXT("高平"), 20, TEXT("网络工程"),
    TEXT("偶然"), 20, TEXT("软件工程"),
    TEXT("炮打"), 21, TEXT("计算机科学"),
    TEXT("马平"), 20, TEXT("网络工程"),
    TEXT("刘然"), 20, TEXT("软件工程"),
    TEXT("黄鹤"), 21, TEXT("计算机科学"),
    TEXT("马平"), 20, TEXT("网络工程"),
    TEXT("评然"), 20, TEXT("软件工程"),
    TEXT("里鹤"), 21, TEXT("计算机科学"),
    TEXT("高平"), 20, TEXT("网络工程"),
    TEXT("偶然"), 20, TEXT("软件工程"),
    TEXT("炮打"), 21, TEXT("计算机科学"),
    TEXT("马平"), 20, TEXT("网络工程")
};
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include "SYSMETS.h"

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);//消息函数声明

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)//主函数
{
    static TCHAR szAppName[] = TEXT("WNDCLASS NAME");//窗口类名称
    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!(传递窗口消息为UNICODE)"), szAppName, MB_ICONERROR);//消息窗口

        return 0;
    }

    hwnd = CreateWindow(szAppName,    //Windows类名
        TEXT("窗口绘制成功!"),        //窗口标题
        WS_OVERLAPPEDWINDOW,        //窗口风格
        CW_USEDEFAULT,                //初始化窗口位置的X坐标
        CW_USEDEFAULT,                //初始化窗口位置的Y坐标
        500,                        //初始化窗口长度大小
        300,                        //初始化窗口宽度大小
        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;
    static int cxClient, cyClient,iVscrollPos;
    int i = 0, y;
    TCHAR szBuffer[100];
    HDC hdc;//设备环境句柄
    PAINTSTRUCT ps;//绘制结构
    TEXTMETRIC tm;

    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;// //字体高度, 总高度tmHeight + 两行文字之间的建议间距大小tmExternalLeading
        ReleaseDC(hwnd, hdc);
        SetScrollRange(hwnd, SB_VERT, 0, NUMLINES - 1, FALSE);//设置垂直滚动条的范围
        SetScrollPos(hwnd, SB_VERT, iVscrollPos, TRUE);//设置垂直滚动条的位置
        return 0;
    case WM_PAINT://处理窗口绘制
        hdc = BeginPaint(hwnd, &ps);
        for (i = 0; i < NUMLINES; ++i) {
            y = cyChar * (i - iVscrollPos);//刚开始为0,小于iVscrollPos都被显示到了内容区域之外
            //SetTextAlign(hdc, TA_RIGHT | TA_TOP);
            TextOut(hdc, 0, y, sysmetrics[i].szName, lstrlen(sysmetrics[i].szName));
            //TextOut(hdc, 400, cyChar * i, szBuffer, wsprintf(szBuffer, TEXT("%2d"), GetSystemMetrics(sysmetrics[i].iAge)));
            TextOut(hdc, 400, y, szBuffer, wsprintf(szBuffer, TEXT("%2d"), sysmetrics[i].iAge));
            TextOut(hdc, 200, y, sysmetrics[i].szMajor, lstrlen(sysmetrics[i].szMajor));
        }
        return 0;
    case WM_LBUTTONDOWN:
        MessageBox(hwnd, TEXT("左键按下!"), TEXT("BIU"), MB_OK | MB_ICONINFORMATION);
        return 0;
    case WM_RBUTTONDOWN:
        MessageBox(hwnd, TEXT("右键按下!"), TEXT("BIU"), MB_OK | MB_ICONINFORMATION);
        return 0;
    case WM_SIZE:
        //cxClient = LOWORD(lParam);//客户区宽度
        cyClient = HIWORD(lParam);//客户区的高度
        return 0;
    case WM_VSCROLL:
        switch (LOWORD(wParam)) {
        case SB_LINEUP:
            iVscrollPos -= 1;//表示向上移动一行
            break;
        case SB_LINEDOWN:
            iVscrollPos += 1;
            break;
        case SB_PAGEUP:
            iVscrollPos -= cyClient / cyChar;//cyClient / cyChar表示客户区一列能显示的字符数,因此这里表示向上一屏
            break;
        case SB_PAGEDOWN:
            iVscrollPos += cyClient / cyChar;
            break;
        case SB_THUMBPOSITION:
            iVscrollPos = HIWORD(wParam);
            break;
        default:
            break;
        }
        /*
        min中判断是否到达最后一组数据,
        max如果在第一行向上移动,iVscrollPos为负,所以使用max表示向上还是显示第一组数据。
        */
        iVscrollPos = max(0, min(iVscrollPos, NUMLINES - 1));
        if (iVscrollPos != GetScrollPos(hwnd, SB_VERT)) {//如果滚动跳当前位置和我更新之后的iVscrollPos位置不同,就需要设置新的滚动条位置,并绘制数据
            SetScrollPos(hwnd, SB_VERT, iVscrollPos, TRUE);//设置显示位置
            InvalidateRect(hwnd, NULL, TRUE);//将要绘制区域无效
        }
        return 0;
    case WM_DESTROY://处理窗口关闭时的消息
        MessageBox(hwnd, TEXT("主窗口关闭"), TEXT("Windows"), MB_OK | MB_ICONINFORMATION);//显示一个文本字符串
        PostQuitMessage(0);//将退出消息插入消息队列,程序从消息循环退出,return msg.wParam
        return 0;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);//执行默认消息处理
}

 但是上面的最后一个信息,显示在最后一页,可以将最后一个信息显示在最后一页的最后一行。

#include <Windows.h>

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

struct
{
    const TCHAR* szName;
    int iAge;
    const TCHAR* szMajor;
}
sysmetrics[]={
    TEXT("刘然"), 20, TEXT("软件工程"),
    TEXT("黄鹤"), 21, TEXT("计算机科学"),
    TEXT("马平"), 20, TEXT("网络工程"),
    TEXT("评然"), 20, TEXT("软件工程"),
    TEXT("里鹤"), 21, TEXT("计算机科学"),
    TEXT("高平"), 20, TEXT("网络工程"),
    TEXT("偶然"), 20, TEXT("软件工程"),
    TEXT("炮打"), 21, TEXT("计算机科学"),
    TEXT("马平"), 20, TEXT("网络工程"),
    TEXT("刘然"), 20, TEXT("软件工程"),
    TEXT("黄鹤"), 21, TEXT("计算机科学"),
    TEXT("马平"), 20, TEXT("网络工程"),
    TEXT("评然"), 20, TEXT("软件工程"),
    TEXT("里鹤"), 21, TEXT("计算机科学"),
    TEXT("高平"), 20, TEXT("网络工程"),
    TEXT("偶然"), 20, TEXT("软件工程"),
    TEXT("炮打"), 21, TEXT("计算机科学"),
    TEXT("马平"), 20, TEXT("网络工程"),
    TEXT("刘然"), 20, TEXT("软件工程"),
    TEXT("黄鹤"), 21, TEXT("计算机科学"),
    TEXT("马平"), 20, TEXT("网络工程"),
    TEXT("评然"), 20, TEXT("软件工程"),
    TEXT("里鹤"), 21, TEXT("计算机科学"),
    TEXT("高平"), 20, TEXT("网络工程"),
    TEXT("偶然"), 20, TEXT("软件工程"),
    TEXT("炮打"), 21, TEXT("计算机科学"),
    TEXT("马平"), 20, TEXT("网络工程"),
    TEXT("刘然"), 20, TEXT("软件工程"),
    TEXT("黄鹤"), 21, TEXT("计算机科学"),
    TEXT("马平"), 20, TEXT("网络工程"),
    TEXT("评然"), 20, TEXT("软件工程"),
    TEXT("里鹤"), 21, TEXT("计算机科学"),
    TEXT("高平"), 20, TEXT("网络工程"),
    TEXT("偶然"), 20, TEXT("软件工程"),
    TEXT("炮打"), 21, TEXT("计算机科学"),
    TEXT("马结束"), 20, TEXT("网络工程")
};
View Code
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include "SYSMETS.h"

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);//消息函数声明

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)//主函数
{
    static TCHAR szAppName[] = TEXT("WNDCLASS NAME");//窗口类名称
    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!(传递窗口消息为UNICODE)"), szAppName, MB_ICONERROR);//消息窗口

        return 0;
    }

    hwnd = CreateWindow(szAppName,    //Windows类名
        TEXT("窗口绘制成功!"),        //窗口标题
        WS_OVERLAPPEDWINDOW,        //窗口风格
        CW_USEDEFAULT,                //初始化窗口位置的X坐标
        CW_USEDEFAULT,                //初始化窗口位置的Y坐标
        500,                        //初始化窗口长度大小
        300,                        //初始化窗口宽度大小
        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;
    static int cxClient, cyClient,iMaxWidth, iVertPos, iHorzPos, iPaintBeg, iPaintEnd;
    int i = 0, x, y;
    SCROLLINFO si;
    TCHAR szBuffer[100];
    HDC hdc;//设备环境句柄
    PAINTSTRUCT ps;//绘制结构
    TEXTMETRIC tm;

    switch (message) {//处理得到的消息
    case WM_CREATE://窗口创建发来消息
        hdc = GetDC(hwnd);
        GetTextMetrics(hdc, &tm);
        cxChar = tm.tmAveCharWidth;        //得到字体平均宽度
        cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2;//判断字符类型,Unicode是字符型的1.5倍大小
        cyChar = tm.tmHeight + tm.tmExternalLeading;//字体高度, 总高度tmHeight + 两行文字之间的建议间距大小tmExternalLeading
        ReleaseDC(hwnd, hdc);
        iMaxWidth = 40 * cxChar + 22 * cxCaps;//最大宽度
        return 0;
    case WM_PAINT://处理窗口绘制
        hdc = BeginPaint(hwnd, &ps);

        si.cbSize = sizeof(si);//必须设置cbSize为结构体大小
        si.fMask = SIF_POS;//指定滚动条位置
        GetScrollInfo(hwnd, SB_VERT, &si);//获取当前纵向滚动条位置
        iVertPos = si.nPos;//设置iVertPos为当前纵向滚动条位置

        GetScrollInfo(hwnd, SB_HORZ, &si);//获取当前横向滚动条位置
        iHorzPos = si.nPos;//设置iHorzPos为当前横向滚动条位置

        iPaintBeg = max(0, iVertPos + ps.rcPaint.top / cyChar);//ps.rcPaint.top这里为0,处理UP操作
        //ps.rcPaint.bottom这里为cyClient,处理DOWN操作,因为...top为0,所以不考虑Unicode字符影响
        //而...bottom表示cyClient大小,实际上的Unicode字符是原来的1.5倍
        iPaintEnd = min(NUMLINES, iVertPos + 3 * ps.rcPaint.bottom / cyChar / 2);
        for (i = iPaintBeg; i < iPaintEnd; ++i) {
            //小于当前横iHorzPos,纵iVertPos滚动条位置的数据都被隐藏
            //0和i相当于输出的起始位置
            x = cxChar * (0 - iHorzPos);
            y = cyChar * (i - iVertPos);
            //SetTextAlign(hdc, TA_LEFT | TA_TOP);
            TextOut(hdc, x, y, sysmetrics[i].szName, lstrlen(sysmetrics[i].szName));
            TextOut(hdc, x+ 22 * cxCaps, y, sysmetrics[i].szMajor, lstrlen(sysmetrics[i].szMajor));
            //SetTextAlign(hdc, TA_RIGHT | TA_TOP);
            TextOut(hdc, x+ 22 * cxCaps + 40 * cxChar, y, szBuffer, wsprintf(szBuffer, TEXT("%2d"), sysmetrics[i].iAge));
        }
        return 0;
    case WM_SIZE:
        cxClient = LOWORD(lParam);//客户区宽度
        cyClient = HIWORD(lParam);//客户区的高度

        si.cbSize = sizeof(si);
        si.fMask = SIF_RANGE | SIF_PAGE;//指定滚动范围和页面大小
        
        si.nMin = 0;//纵向范围最小为0
        si.nMax = NUMLINES - 1;//纵向范围最大值为NUMLINES-1
        si.nPage = cyClient / cyChar;//页面行数
        SetScrollInfo(hwnd, SB_VERT, &si, TRUE);//设置滚动范围和滚动页码到si中

        si.cbSize = sizeof(si);
        si.fMask = SIF_RANGE | SIF_PAGE;
        si.nMin = 0;//横向范围最小为0
        si.nMax = 2 + iMaxWidth / cxChar;//横向范围最大值
        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_TOP:
            si.nPos = si.nMin;
            break;
        case SB_BOTTOM:
            si.nPos = si.nMax;
            break;
        case SB_LINEUP:
            si.nPos -= 1;//表示向上移动一行
            break;
        case SB_LINEDOWN:
            si.nPos += 1;
            break;
        case SB_PAGEUP:
            si.nPos -= si.nPage;//cyClient / cyChar表示客户区一列能显示的字符数,因此这里表示向上一屏
            break;
        case SB_PAGEDOWN:
            si.nPos += si.nPage;
            break;
        case SB_THUMBTRACK:
            si.nPos = si.nTrackPos;
            break;
        default:
            break;
        }
        si.fMask = SIF_POS;
        SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
        GetScrollInfo(hwnd, SB_VERT, &si);
        if (si.nPos != iVertPos) {//如果滚动跳当前位置和我更新之后的iVscrollPos位置不同,就需要设置新的滚动条位置,并绘制数据
            ScrollWindow(hwnd, 0, cyChar * (iVertPos - si.nPos), NULL, NULL);
            UpdateWindow(hwnd);
        }
        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_THUMBPOSITION:
            si.nPos = si.nTrackPos;//截获SB_THUMBPOSITION通知码
            break;
        default:
            break;
        }
        si.fMask = SIF_POS;
        SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);
        GetScrollInfo(hwnd, SB_HORZ, &si);
        if (si.nPos != iHorzPos) {//如果滚动跳当前位置和我更新之后的iVscrollPos位置不同,就需要设置新的滚动条位置,并绘制数据
            ScrollWindow(hwnd, cxChar * (iHorzPos - si.nPos), 0, NULL, NULL);
        }
        return 0;
    case WM_DESTROY://处理窗口关闭时的消息
        PostQuitMessage(0);//将退出消息插入消息队列,程序从消息循环退出,return msg.wParam
        return 0;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);//执行默认消息处理
}

iPaintBeg和iPaintEnd那两行删了,下面使用for(i = 0; i < NUMLINES; ++i)也能够正常输出
原文地址:https://www.cnblogs.com/Mayfly-nymph/p/11300555.html