当你向程序中输入文本时,通常会有下划线、竖线或方框指示你输入的下一个字符将出现在屏幕上的位置。你也许认为这是“光标”,但在编写Windows程序时,你必须避免这种习惯。在Windows中,它被称为“插入符号”(caret)。“光标”(cursor)特指表示鼠标位置的位图图像,即鼠标指针。
本节必须掌握的知识点:
关于插入符号的函数
第34练:文本编辑器插入符号
5.5.1 关于插入符号的函数
■Windows中有五个基本的插入符号函数:
●CreateCaret:创建和窗口关联的插入符号。
●SetCaretPos:设置窗口内的插入符号的位置。
●ShowCaret:显示插入符号。
●HideCaret:险藏插入符号。
●DestroyCaret:销毁插入符号。
■操作插入符的函数
此外,还有用于获得当前插入符号位置的函数(GetCaretPos)与获得和设置插入符号闪烁时间的函数(GetCaretBlinkTime 和 SetCaretBlinkTime)。
●GetCaretPos 函数的原型:
BOOL GetCaretPos(
LPPOINT lpPoint // 接收光标位置的指针
);
返回值:
如果函数调用成功,返回值为非零值(TRUE)。
如果函数调用失败,返回值为零(FALSE)。
使用 GetCaretPos 函数可以获取当前光标的位置,即光标在屏幕上的坐标。通过传递一个指向 POINT 结构的指针,函数将光标的坐标信息存储在该结构中。
示例代码:
#include <Windows.h>
int main() {
POINT caretPos;
// 获取光标位置
if (GetCaretPos(&caretPos)) {
// 输出光标的坐标
printf("Caret position: x = %d, y = %d\n", caretPos.x, caretPos.y);
} else {
// 获取光标位置失败
printf("Failed to get caret position.\n");
}
return 0;
}
●GetCaretBlinkTime函数原型:
UINT GetCaretBlinkTime();
返回值:
返回一个无符号整数,表示光标闪烁的时间间隔(以毫秒为单位)。
使用 GetCaretBlinkTime 函数可以获取当前系统中光标闪烁的时间间隔。光标闪烁是指光标在显示和隐藏之间的交替效果。
示例代码:
#include <Windows.h>
int main() {
UINT blinkTime = GetCaretBlinkTime();
// 输出光标闪烁时间间隔
printf("Caret blink time: %u ms\n", blinkTime);
return 0;
}
●SetCaretBlinkTime函数原型:
BOOL SetCaretBlinkTime(
UINT uMSeconds // 光标闪烁的时间间隔(以毫秒为单位)
);
返回值:
如果函数调用成功,返回值为非零值(TRUE)。
如果函数调用失败,返回值为零(FALSE)。
使用 SetCaretBlinkTime 函数可以设置光标闪烁的时间间隔。你可以将所需的时间间隔(以毫秒为单位)作为参数传递给该函数。
示例代码:
#include <Windows.h>
int main() {
UINT blinkTime = 500; // 设置光标闪烁时间间隔为 500 毫秒
// 设置光标闪烁时间间隔
if (SetCaretBlinkTime(blinkTime)) {
printf("Caret blink time set successfully.\n");
} else {
printf("Failed to set caret blink time.\n");
}
return 0;
}
■处理插入符
在Windows中,插入符号通常是一个字符大小的水平线或方框,或是与字符高度相一 致的竖线。使用变宽字体的时候推荐使用竖线插入符号,比如Windows默认系统字体。因 为变宽字体字符不是固定大小的,不能把水平线或者方框设置为字符的大小。
如果你的程序中需要插入符号,那么不应该简单地在窗口过程的WM_CREATE消息中创建它并在WM_DESTROY消息中销毁它。不建议你这样做的原因是一个消息队列仅能够支持一个插入符号。因此,如果你的程序有多于一个窗口,则多个窗口必须有效地共享同一个插入符号。
这并不像听起来那样有限制性。你想一下,仅当窗口具有输入焦点时,窗口中插入符号的显示才有意义。的确,闪烁的插入符号的存在是一种视觉提示:它让用户意识到他可以向程序中输入文本。因为任何时候仅有一个窗口具有输入焦点,所有多个窗口同时使插入符号闪烁是没有意义的。
程序能通过处理WM_SETFOCUS消息和WM_KILLFOCUS消息来决定它是否具有输入焦点。正如名称所暗示的,当窗口过程接收输入焦点时,它接收到一个WM_SETFOCUS 消息:当它失去输入焦点时,收到一个WM_KILLFOCUS消息。这些消息成对出现:窗口过程在接收到一条WM_KILLFOCUS消息前,总是会接收到一条WM_SETFOCUS消息。 并且在窗口的生命期窗口过程总是接收到相同数目的WM_SETFOCUS消息和 WM_KILLFOCUS 消息。
使用插入符号的主要规则很简单:在窗口过程处理WM_SETFOCUS消息时调用 CreateCaret 函数,处理 WM_KILLFOCUS 消息时调用 DestroyCaret 函数。
还有一些其他规则:创建的插入符号是隐藏的。在调用CreateCaret之后,窗口过程必 须调用ShowCaret使之可见。另外,如果窗口过程处理的是一个非WM_PAINT消息,但要在窗口内绘制某些东西时,它必须调用HideCaret隐藏插入符号。当它结束在窗口内的绘制之后,再调用ShowCaret来显示插入符号。HideCaret的效果是叠加的:如果你调用了HideCaret很多次,但没调用过ShowCaret,那么你必须再调用同样次数的ShowCaret才能使插入符号可见。
5.5.2 第34练:文本编辑器插入符号
/*------------------------------------------------------------------
034 WIN32 API 每日一练
第34个例子TYPER.C:文本编辑器---插入符号
GetFocus函数
CreateCaret函数
SetCaretPos函数
ShowCaret函数
HideCaret函数
DestroyCaret函数
GetCaretPos函数
WM_SETFOCUS消息
WM_KILLFOCUS消息
注:TYPER程序 不允许使用 双字节宽度的字符
无搜索,替换,保存文件,拼写检查,滚动,帮助等扩展功能。
(c) www.bcdaren.com, 2020
----------------------------------------------------------------*/
#include <windows.h>
#define BUFFER(x,y) *(pBuffer + y * cxBuffer + x)
TCHAR * pBuffer = NULL;
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT("Typer");
…(略)
return msg.wParam;
}
LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)
{
static DWORD dwCharSet = DEFAULT_CHARSET ;//#define DEFAULT_CHARSET 1
static int cxChar, cyChar, cxClient, cyClient,
cxBuffer, cyBuffer,xCaret, yCaret ;
//static TCHAR * pBuffer = NULL ;
HDC hdc ;
int x, y, i ;
PAINTSTRUCT ps ;
TEXTMETRIC tm ;
switch (message)
{
case WM_INPUTLANGCHANGE:
dwCharSet = wParam ;
//继续执行下去
case WM_CREATE:
hdc = GetDC (hwnd) ;
//创建逻辑字体
SelectObject ( hdc, CreateFont (0, 0, 0, 0, 0, 0, 0, 0,
dwCharSet, 0, 0, 0, FIXED_PITCH, NULL)) ;
//获取字体信息
GetTextMetrics (hdc, &tm) ;
cxChar = tm.tmAveCharWidth ;
cyChar = tm.tmHeight ;
DeleteObject (SelectObject (hdc, GetStockObject (SYSTEM_FONT))) ;
ReleaseDC (hwnd, hdc) ;
//继续执行下去
case WM_SIZE:
// 获取窗口大小,以像素为单位
if (message == WM_SIZE)
{
cxClient = LOWORD (lParam) ;
cyClient = HIWORD (lParam) ;
}
// 计算窗口大小(字符)
cxBuffer = max (1, cxClient / cxChar) ; //cxBuffer窗口字符宽度
cyBuffer = max (1, cyClient / cyChar) ; //cyBuffer窗口字符高度
// 为缓冲区分配内存并清除它
if (pBuffer != NULL)
free (pBuffer) ;
//创建保存窗口所有字符的缓冲区
pBuffer = (TCHAR *) malloc (cxBuffer * cyBuffer * sizeof (TCHAR)) ;
//初始化pBuffer为一个‘ ’空字符
for (y = 0 ; y < cyBuffer ; y++)
for (x = 0 ; x < cxBuffer ; x++)
BUFFER(x,y) = ' ' ;
// 插入符号定位左上角
xCaret = 0 ;
yCaret = 0 ;
if (hwnd == GetFocus ()) //插入符号为当前窗口---焦点窗口
//设置插入符号位置
SetCaretPos (xCaret * cxChar, yCaret * cyChar) ;
InvalidateRect (hwnd, NULL, TRUE) ; //重绘窗口
return 0 ;
//获得键盘焦点后发送到窗口
case WM_SETFOCUS:
// 创建和显示插入符号,指定大小,NULL表示实心
CreateCaret (hwnd, NULL, cxChar, cyChar) ;
SetCaretPos (xCaret * cxChar, yCaret * cyChar) ; //设置符号位置
ShowCaret (hwnd) ; //显示符号
return 0 ;
//在失去键盘焦点之前立即发送到窗口
case WM_KILLFOCUS:
// 隐藏和删除插入符号
HideCaret (hwnd) ; //隐藏符号
DestroyCaret () ; //删除符号
return 0 ;
//击键消息---光标移动的处理
case WM_KEYDOWN:
switch (wParam)
{
case VK_HOME:
xCaret = 0 ;
break ;
case VK_END:
xCaret = cxBuffer - 1 ;
break ;
case VK_PRIOR: //Page Up
yCaret = 0 ;
break ;
case VK_NEXT: //PageDown
yCaret = cyBuffer - 1 ;
break ;
case VK_LEFT:
xCaret = max (xCaret - 1, 0) ;
break ;
case VK_RIGHT:
xCaret = min (xCaret + 1, cxBuffer - 1) ;
break ;
case VK_UP:
yCaret = max (yCaret - 1, 0) ;
break ;
case VK_DOWN:
yCaret = min (yCaret + 1, cyBuffer - 1) ;
break ;
case VK_DELETE: //注意VK_BACK键当字符消息去处理,这里只处理VK_DELETE
//将该行从当前位置后面字符依次前移一格
for (x = xCaret ; x < cxBuffer - 1 ; x++)
BUFFER(x, yCaret) = BUFFER(x + 1, yCaret);
BUFFER(cxBuffer - 1, yCaret) = ' ';//该行最后一个设为空字符。
//绘图前必须先隐藏插入符
HideCaret(hwnd);
hdc = GetDC(hwnd);
SelectObject(hdc, CreateFont(0, 0, 0, 0, 0, 0, 0, 0,
dwCharSet, 0, 0, 0, FIXED_PITCH, NULL));
TextOut(hdc, xCaret * cxChar, yCaret * cyChar,
&BUFFER(xCaret, yCaret),
cxBuffer - xCaret); //重新输出该行xCaret后面的文字
DeleteObject(SelectObject(hdc, GetStockObject(SYSTEM_FONT)));
ReleaseDC(hwnd, hdc);
ShowCaret(hwnd);
break;
}
SetCaretPos(xCaret * cxChar, yCaret * cyChar);
return 0;
//字符消息
case WM_CHAR:
for (i = 0 ; i < (int) LOWORD (lParam) ; i++)
{
//转义字符处理
switch (wParam)
{
case '\b': // 退格
if (xCaret > 0)
{
xCaret--;
//1为repeat字段的值
SendMessage(hwnd, WM_KEYDOWN, VK_DELETE, 1);
}
break;
case '\t': //Tab键,8个字符长度
do
{
SendMessage (hwnd, WM_CHAR, ' ', 1) ;
} while (xCaret % 8 != 0) ;
break ;
case '\n': //换行符,改变y到下一行,x坐标没变。
if (++yCaret == cyBuffer)
yCaret = 0 ;
break ;
case '\r': // 回车
xCaret = 0;
if (++yCaret == cyBuffer)
yCaret = 0;
break;
//ESC键,清空屏幕 \xhh表示1到2位十六进制所代表的任意字符
case '\x1B':
for (y = 0; y < cyBuffer; y++)
for (x = 0; x < cxBuffer; x++)
BUFFER(x, y) = ' ';
xCaret = 0;
yCaret = 0;
InvalidateRect(hwnd, NULL, FALSE);
break;
default: // 字符编码
BUFFER(xCaret, yCaret) = (TCHAR)wParam;
HideCaret(hwnd);
hdc = GetDC(hwnd);
SelectObject(hdc, CreateFont(0, 0, 0, 0, 0, 0, 0, 0,
dwCharSet, 0, 0, 0, FIXED_PITCH, NULL));
TextOut(hdc, xCaret * cxChar, yCaret * cyChar,
&BUFFER(xCaret, yCaret), 1);
DeleteObject(
SelectObject(hdc, GetStockObject(SYSTEM_FONT)));
ReleaseDC(hwnd, hdc);
ShowCaret(hwnd);
if (++xCaret == cxBuffer)
{
xCaret = 0;
if (++yCaret == cyBuffer)
yCaret = 0;
}
break;
}
}
SetCaretPos (xCaret * cxChar, yCaret * cyChar) ;
return 0 ;
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
SelectObject (hdc, CreateFont (0, 0, 0, 0, 0, 0, 0, 0,
dwCharSet, 0, 0, 0, FIXED_PITCH, NULL)) ;
for (y = 0 ; y < cyBuffer ; y++)
TextOut (hdc, 0, y * cyChar, & BUFFER(0,y), cxBuffer) ;
DeleteObject (SelectObject (hdc, GetStockObject (SYSTEM_FONT))) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY:
if (pBuffer != NULL) free(pBuffer);
PostQuitMessage(0);
return 0;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
/*************************************************************************
CreateCaret:创建和窗口关联的插入符号
SetCaretPos:设置窗口内的插入符号的位置
ShowCaret :显示插入符号
HideCaret :隐藏插入符号
DestroyCaret:销毁插入符号
GetCaretPos :当前插入符号的位置
GetFocus函数:检索具有键盘焦点的窗口的句柄
**************************************************************************
WM_SETFOCUS消息:获得键盘焦点后发送到窗口
#define WM_SETFOCUS 0x0007
参数:wParam
失去键盘焦点的窗口句柄。此参数可以为NULL。
lParam:不使用此参数。
**************************************************************************
WM_KILLFOCUS消息:在失去键盘焦点之前立即发送到窗口
#define WM_SETFOCUS 0x0008
参数:wParam
失去键盘焦点的窗口句柄。此参数可以为NULL。
lParam:不使用此参数。
*/
运行结果:
图5-7 文本编辑器插入符
总结
实例TYPER.C创建了一个简单的文本编辑器。
●窗口过程首先通过M_INPUTLANGCHANGE消息获取当前字符集,然后在WM_CREATE消息中选入新创建的逻辑字体,并获取新逻辑字体的字符宽和高。
●在WM_SIZE消息中,当窗口客户区大小发生变化时,使用空格字符清空窗口客户区,然后将插入符置于窗口客户区的左上角,然后重绘窗口。【注意】前提时当前窗口为焦点窗口。
●当窗口获得焦点时,窗口过程处理M_SETFOCUS消息,创建、设置并显示插入符。
●当窗口失去焦点时,窗口过程处理WM_KILLFOCUS消息,隐藏并删除插入符。
●在按键消息WM_KEYDOWN中,处理HOME、END、PageUp、PageDown和上下左右箭头键,计算插入符的新位置。处理Delete键稍微复杂一些,先将当前位置后面的字符依次前移一个位置,并在该行字符的结尾处添加一个空格。【注意】移动字符时,先隐藏插入符,再选入新创建的逻辑字体绘制字符,绘制完成后重新显示插入符。所有字符重新绘制完成后,重新将插入符置于该行字符的结尾处。
●窗口过程在WM_CHAR消息代码块绘制字符。我们把字符分为两类:
转义字符处理:
'\b'将字符位置减一,然后转为Delete按键消息处理。
'\t':转换为空格字符消息处理。
'\n':将字符yCaret加一(行数加一)。
'\r':将字符xCaret清零(列数清零)。
'\x1B':ESC键清空屏幕(窗口客户区输出空格),并将插入符坐标清零。
正常可见字符的处理:与Delete键的处理过程类似,先隐藏插入符,然后绘制字符后再重新显示插入符,再将插入符置于下一个位置。
●WM_PAINT消息选入新的逻辑字体绘制所有字符。
【注意】
实例创建的简单文本编辑器并不完善,并不支持复制、粘贴、剪切、选择等功能,也没有对应的菜单选项。我们将在后续的章节中逐步完善此文本编辑器。
此外,当我们输入中文字符时,需要收到将插入符后移一个位置,等待输入下一个字符。