尝试Win32窗口的简单文本输出和滚动条, 实现可滚动的文本显示窗口.
用记事本打开了一件cpp文件, 以下是一张记事本的截图:
想要实现记事本的功能, 一些功能先不考虑, 先实现文本的显示, 以下是效果图:
窗口之前已经能显示, 而文字的输出和实现滚动条是这次需要考虑的.
将内存中的字符输出到显示器上, 在这个过程中, 系统通过标准化的字体数据(文件)实现了字符到图像的映射.Windows操作系统早期使用的是等宽字体, 这种类型的字体每个字符图像的宽度和高度容易计算, 不过变宽的字体更加灵活, 也更适合人眼分辨.
一般Visual Studio 使用的就是等宽字体, 或者也可以主动调为等宽字体(在菜单栏Tools -> Options -> Environment -> Fonts and Colors中设置):
(
查看字体时等宽字体VS会加粗显示
)
使用等宽字体, 如果想要定位当前编辑的位置就较为容易, 甚至可以简单用当前字符位置的索引乘以单个字符的宽高来获取当前位置:
01234567
1abcdef
2ghijkl
3mnopqr
4stuvwx
5yz ?!;
如果在VS中(设置好等宽字体)查看上述注释, 就能看到所有字符上下左右都是对齐的, 它们是等宽等高的.
高度是行的高度, 实际上显示出的字符宽高未必相等, 但是它们会留有一部分空隙, 实际上字符和字符是对齐的, 算上空隙之后可以将每个字
符图像看作等宽等高的
假如光标(插入符号)在’i’和’j’之间,
在Windows中标识当前插入符号位置的, 即编辑文本时的在当前位置闪烁的标志, 叫做插入符号Caret, 光标用来特指鼠标对应的光标, 下文
均使用插入符号避免歧义.
i在第三行的第四个字符的位置, j在第三行的第五个字符的位置:
可以直接计算插入符号的坐标为(3 * charH, 4 * charW), 两个参数分别代表垂直坐标和水平坐标.
又或者可以通过当前插入符号的位置判断在哪个字符之后或者行首.
不过实际上不可能都是等宽字体, 变宽字体更符合人的审美, 而且很多时候是无法完美实现等宽的, 中文和英文字符混用时常常中文字符和英文字符的宽度可能不一样, 即使是等宽字体也无法解决:
但是即使强制让中文和英文字符等宽, 看起来也不会很舒服.
使用变宽字体, 处理起字符位置就相对等宽字体更麻烦一些, 不过更不好把握的是确定字符串的宽, 因为同样是三个字符, "ABC"和"abc"的宽度可能就不一样了.
尽管通过
GetTextMetrics
能获取字体的平均宽度aveCharW, 实际显示时, 如果是变宽字体, 一种粗糙的解决方案是
将aveCharW扩大一定的比例, 比如1.5倍
fonts-and-text
有更详细的字体说明
.
使用
GetTextMetrics
2
能获取系统字体数据, 它会获取当前设备(
HDC
)使用的字体数据, 具体的数据信息存入了
TEXTMETRIC
:
typedef struct tagTEXTMETRICA {
LONG tmHeight;
LONG tmAscent;
LONG tmDescent;
LONG tmInternalLeading;
LONG tmExternalLeading;
LONG tmAveCharWidth;
LONG tmMaxCharWidth;
LONG tmWeight;
LONG tmOverhang;
LONG tmDigitizedAspectX;
LONG tmDigitizedAspectY;
BYTE tmFirstChar;
BYTE tmLastChar;
BYTE tmDefaultChar;
BYTE tmBreakChar;
BYTE tmItalic;
BYTE tmUnderlined;
BYTE tmStruckOut;
BYTE tmPitchAndFamily;
BYTE tmCharSet;
} TEXTMETRICA, *PTEXTMETRICA, *NPTEXTMETRICA, *LPTEXTMETRICA;
其中 LONG tmAveCharWidth;
代表了字符平均宽度. BYTE tmPitchAndFamily;
标识了字体是否是等宽的, 如果是等宽的低位值为0, 否则为1. LONG tmMaxCharWidth;
代表了字符的最大宽度. LONG tmHeight;
代表字符的宽度, LONG tmExternalLeading;
是字体设计者建议在多行显示时每行间留下的宽度, 可以听取也可以忽略.
所以可以使用该函数获取单个字符的宽高:
LONG chW, chH;
//......
chW = LONG((tm.tmPitchAndFamily & 1 ? 3 : 2) * (tm.tmAveCharWidth / 2.0));
chH = tm.tmHeight;
//......
显示文本可以使用DrawText
, TextOut
, 或者应该还有一些可以用来显示文本的函数, 我目前除了Direct之外, 直接调用Windows API 的只使用过这两个函数(宏), 参考《Windows 程序设计》, 使用了TextOut4这个最常用的文本显示函数:
BOOL TextOutW(
HDC hdc,
int x,
int y,
LPCWSTR lpString,
int c
BOOL TextOutA(
HDC hdc,
int x,
int y,
LPCSTR lpString,
int c
#ifdef UNICODE
#define TextOut TextOutW
#else
#define TextOut TextOutA
x, y是文本的坐标, 默认是文本外接矩形的左上角:

比如上图显示’msg;'文本, (x, y)就是红色矩形的左上角的坐标(示意用, 粗略代表文本的外接矩形).
Windows默认的坐标系默认是左上角为(0, 0)点, 水平对应x轴, 竖直对应y轴, 点的坐标常记为(x, y)
LPCTSTR lpString,
int c
lpString是指的字符串, 并不一定以0字节结尾,
如果是宽字符, 就是对应双0字节
c指的字符串的长度,
如果是使用缓冲字符数组, 然后使用wspintf
函数输入, 也可以直接将函数返回值传入参数c:
TextOut(hdc, x, y,
wsprintf(buf, TEXT("format string"), sysmetrics[i].iIndex)
虽然TextOut默认将(x, y)对应到文本外接矩形的左上角坐标, 但是仍可以通过SetTextAlign6函数来切换对应点, 该函数修改了设备的文本对齐方式.
一种调用方式是每次显示完字符串后设置回默认值
UINT SetTextAlign(
HDC hdc,
UINT align
align是掩码参数, 选择align掩码值时, 水平和垂直方向各要选择一个。
或者函数给出各方向的默认位置
另外,每次设置掩码要么更改水平位置, 要么更改垂直位置, 不能同时更改两个方向的位置。
每次调用, 至少一个方向的位置是不变的
align的所有参数可以参见nf-wingdi-settextalign. 下面给出了一些值:
Value | Desc |
---|
TA_LEFT | 参考点在边界矩形的左部 |
TA_TOP | 参考点在边界矩形的顶部 |
TA_RIGHT | 参考点在边界矩形的右部 |
TA_BOTTOM | 参考点在边界矩形的底部 |
TA_CENTER | 参考点在边界矩形的水平中心 |
TA_TOP | 参考点在边界矩形的顶部 |
窗口的滚动条添加很容易, 只要在创建窗口时设置一下窗口风格, WS_VSCROLL , WS_HSCROLL, 或者 WS_VSCROLL | WS_HSCROLL窗口风格标识符. 分别对应垂直滚动条和水平滚动条.
Window Style | Desc |
---|
WS_VSCROLL | 垂直滚动条, 固定右侧 |
WS_HSCROLL | 水平滚动条, 固定右侧 |
或者通过控件也能实现, 以下均考虑Windows提供并和Window Style相关的滚动条
-
Windows通过设置窗口风格标识符添加的滚动条不在客户区, 也总是缩短和伸展以适应客户区的宽度和高度.
-
默认的滚动条支持鼠标的互动操作并由Windows操作系统处理对应的鼠标消息, 但是没有相应的键盘接口. 如果滚动条需要响应键盘消息, 如Page Up/Page Down等, 需要自己显式实现.
-
用户操作滚动条时, Windows会更新滚动条显示的位置并发送消息, 但是滚动条实际的位置并没有变化, 所以需要主动调用SetScrollPos函数来设置和显示, 否则用户操作结束后还会回到原来的位置.
如果不设置滚动条的Window Style, 主动调用SetScrollRange, SetScrollPos, SetScrollInfo函数之后也会和设置了滚动条的Window Style一样.
滚动条操作时会发出WM_VSCROLL7 (WM_HSCROLL)消息, 以WM_SCROLL为例, 开发者在WindowProc中处理它时需要返回0值, 以下为两个参数的含义:
Parameter | DESC |
---|
WPARAM | LOWORD 确定了滚动条的详细消息类型 (或见下图).当LOWORD 为SB_THUMBPOSITION 或者SB_THUMBTRACK 时, HIWORD确定了滚动条当前响应(显示)的位置. |
LPARAM | 如果消息发自控件类型的滚动条, 则为控件的句柄, 否则为NULL |
可以在接收到滚动条的消息时进行一些必要处理, 比如更新滚动条的位置和刷新客户区:
static int iVScrollPos = 0;
//......
switch (LOWORD(wParam)) {
case SB_LINEUP:
--iVScrollPos;
break;
case SB_LINEDOWN:
++iVScrollPos;
break;
case SB_PAGEUP:
iVScrollPos -= get_page_height() / get_char_height();
break;
case SB_PAGEDOWN:
iVScrollPos += get_page_height() / get_char_height();
break;
case SB_THUMBPOSITION:
iVScrollPos = HIWORD(wParam);
break;
if (iVScrollPos < iMin) iVScrollPos = iMin;
else if (iVScrollPos > iMax) iVScrollPos = iMax;
if (iVScrollPos != GetScrollPos(m_hwnd, SB_VERT)) {
SetScrollPos(m_hwnd, SB_VERT, iVScrollPos, TRUE);
InvalidateRect(m_hwnd, NULL, TRUE);
默认滚动条的范围是[0, 100] (整数值), 不过通过SetScrollRange9可以调节该范围.
使用SetScrollPos10 可以设置滚动条的位置:
int SetScrollPos(
HWND hWnd,
int nBar,
int nPos,
BOOL bRedraw
该函数的nPos参数表示滚动条的新位置.
(该例子取自《Windows 程序设计》, 代码由本人编写)
效果图如下:

接下来将GetSystemMetrics
11函数部分信息显示在窗口中:
* GetSystemMetrics() codes
#define SM_CXSCREEN 0
#define SM_CYSCREEN 1
#define SM_CXVSCROLL 2
#define SM_CYHSCROLL 3
#define SM_CYCAPTION 4
#define SM_CXBORDER 5
#define SM_CYBORDER 6
#define SM_CXDLGFRAME 7
#define SM_CYDLGFRAME 8
#define SM_CYVTHUMB 9
#define SM_CXHTHUMB 10
#define SM_CXICON 11
#define SM_CYICON 12
#define SM_CXCURSOR 13
#define SM_CYCURSOR 14
#define SM_CYMENU 15
#define SM_CXFULLSCREEN 16
#define SM_CYFULLSCREEN 17
#define SM_CYKANJIWINDOW 18
#define SM_MOUSEPRESENT 19
#define SM_CYVSCROLL 20
#define SM_CXHSCROLL 21
#define SM_DEBUG 22
#define SM_SWAPBUTTON 23
#define SM_RESERVED1 24
#define SM_RESERVED2 25
#define SM_RESERVED3 26
#define SM_RESERVED4 27
#define SM_CXMIN 28
#define SM_CYMIN 29
#define SM_CXSIZE 30
#define SM_CYSIZE 31
#define SM_CXFRAME 32
#define SM_CYFRAME 33
#define SM_CXMINTRACK 34
#define SM_CYMINTRACK 35
#define SM_CXDOUBLECLK 36
#define SM_CYDOUBLECLK 37
#define SM_CXICONSPACING 38
#define SM_CYICONSPACING 39
#define SM_MENUDROPALIGNMENT 40
#define SM_PENWINDOWS 41
#define SM_DBCSENABLED 42
#define SM_CMOUSEBUTTONS 43
为了方便, 没有包含原书中描述文本字符串, 并在复制上述宏到新建的头文件中后, 使用VS, 通过正则表达式(Regex)进行替换, 并添加部分代码, 可以直接使用:
1、简单实现基本的显示功能,界面功能,使用了RichEdit控件
2、添加打开文件,显示文件,保存文件,以及常见的复制、粘贴等功能!
3、实现显示行号功能!这里使用了子类化功能!
4、逐步完善所有功能,如判断文本时候有修改,并作相应的变动!
最后一个可以自己尝试添加诸如选择字体,对说选文本选定制定的颜色(可能会要用到RTF文本格式)!
文字与文本操作1.HDC hdc; hdc=BeginPaint(HWND,LPPAINTSTRUCT);2.定义字体句柄变量:HFONT hF3.获得系统字体句柄:hF= GetStockObject ( nFontStyle)//获取系统的字体 创建自定义字体 HFont=CreateFont ( int nHeight, //字体高度,取0则采用系统缺省值,使用逻辑单位 int
#include"resource.h" //很重要,要引入资源头文件
窗口显示文字,目标:文字随着窗口大小变化变化。文字太多时候,可以通过Scroll滚动条来显示。
思路,在Create中利用G
DWORD dw = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FR
向文本文件输出标准输出设备显示器被系统看作文本文件,所以我们以向标准设备输出为例,介绍文本文件输出格式控制插入运算符插入(<<)运算符为所有标准C++数据类型预先设计的,用于传送字节到一个输出流对象。操纵符(manipulator)插入运算符与操纵符一起工作控制输出格式。很多操纵符都定义在ios_base类中(如hex())、头文件(如setprecision())。控制输出宽度在流中...
译:如何使用win32 api中的edit控件<br />――――利用Edit类进行windows gui编程的方法和技巧<br />© Guy Lecky-Thompson <br /><br />
Jun 14, 2007 <br />Article
describing the Win32 edit class and how to use it as a child window
control in a Windows application as a self-maintaining si
查了好多资料,最后得出单行编辑控件要想文本显示在垂直居中只有重绘了,这样虽然得到想相的效果,但是有点就是在WM_SIZE中移动控件后,今天我在查看编辑控件消息是发现了.EM_SETRECT这个消息,所以就想,既然单行不行,就创建多行的样式嘛,干嘛非要创建单行呢。所以要在控件移动后才重新设置文本显示矩形区域位置。这样创建出来还是单行编辑控件一样的外观嘛.控件重绘后又回到以前的显示位置,在发关EM_SETRECT消息。这样就得到文本垂直居中的效果。
正文图形为什么会闪烁的原因是:我们的绘图过程大多放在OnDraw或者OnPaint函数中,OnDraw在进行屏幕显示时是由OnPaint进行调用的。当窗口由于任何原因需要重绘时,总是先用背景色将显示区清除,然后才调用OnPaint,而背景色往往与绘图内容反差很大,这样在短时间内背景色与显示图形的交替出现,使得显示窗口看起来在闪。如果将背景刷设置成NULL,这样无论怎样重绘图形都不会闪了。当然,这样...