6.5 插入符号(不是光标)

        摘录于《Windows程序(第5版,珍藏版).CHarles.Petzold 著》P213

        当你向程序中输入文本时,通常会有下划线、竖线或方框指示你输入的下一个字符将出现屏幕上的位置。你也许认为这是“光标”,但在编写 Windows 程序时,你必须避免这种习惯。在 Windows 中,它被称为“插入符号”(caret)。光标”(cursor)特指表示鼠标位置的位图图像,即鼠标指针

6.5.1  一些关于插入符号的函数

        Windows 中有五个基本的插入符号函数:

  • CreateCaret:创建和窗口关联的插入符号。
  • SetCaretPos:设置窗口内的插入符号的位置。
  • ShowCaret:显示插入符号。
  • HideCaret:隐藏插入符号。
  • DestroyCaret:销毁插入符号。

此外,还有用于获得当前插入符号位置的函数(GetCaretPos)与获得和设置插入符号闪烁时间的函数(GetCaretBlinkTime 和 SetCaretBlinkTime)。

        在 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 才能使插入符号可见。

6.5.2  TYPER 程序

        TYPER 程序使用了我们在这章中学到的大部分内容。你可以认为 TYPER 是一个非常基础的文本编辑器。你能在窗口中输入文本,用光标移动键(或者该叫插入符号移动键?)移动光标(这里是指插入符号),按下 Esc 键擦除窗口的内容。当你调整窗口大小或改变键盘输入语言时,窗口内容也被擦除。TYPER 程序中没有滚动功能,没有搜索和替换,没有保存文件的方法,没有拼写检查程序,而且没有人形的曲别针(在 微软 Office XP 中,它表示帮助的意思),但它的确是一个文本编辑器的雏形。

/*--------------------------------------------------------
   TYPER.C -- Typing Program
                 (c) Charles Petzold, 1998
  --------------------------------------------------------*/
#include <windows.h>

#define BUFFER(x, y) *(pBuffer + y * cxBuffer + x)

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

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     static TCHAR szAppName[] = TEXT ("Typer") ;
     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 ("Typing Program"),
                          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 DWORD dwCharSet = DEFAULT_CHARSET;
     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;
                                // fall through

     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) ;
                                   // fall through
     case WM_SIZE:
                                // obtain window size in pixels

          if (message == WM_SIZE)
          {
               cxClient = LOWORD (lParam) ;
               cyClient = HIWORD (lParam) ;
          }
                        // calculate window size in characters

          cxBuffer = max(1, cxClient / cxChar);
          cyBuffer = max(1, cyClient / cyChar);

                            // allocate memory for buffer and clear it

          if (pBuffer != NULL)
                free(pBuffer);

          pBuffer = (TCHAR *) malloc(cxBuffer * cyBuffer * sizeof(TCHAR));

          for (y = 0; y < cyBuffer; ++ y)
            for (x = 0; x < cxBuffer; ++ x)
                    BUFFER(x, y) = ' ';

                            // set caret to upper left corner

          xCaret = 0;
          yCaret = 0;

          if (hwnd == GetFocus())
                SetCaretPos(xCaret * cxChar, yCaret * cyChar);

          InvalidateRect(hwnd, NULL, TRUE);
          return 0 ;
     case WM_SETFOCUS:
                                // create and show the caret

          CreateCaret(hwnd, NULL, cxChar, cyChar);
          SetCaretPos(xCaret * cxChar, yCaret * cyChar);
          ShowCaret(hwnd);
          return 0;

     case WM_KILLFOCUS:
                                // hide and destroy the caret

          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:
            yCaret = 0;
            break;

          case VK_NEXT:
            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:
            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);

            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':        //backspace
                if (xCaret > 0)
                {
                    xCaret --;
                    SendMessage(hwnd, WM_KEYDOWN, VK_DELETE, 1);
                }
                break;

              case '\t':       // tab
                do {
                    SendMessage(hwnd, WM_CHAR, ' ', 1);
                } while (xCaret % 8 != 0);
                break;

              case '\n':     // line feed
                if (++yCaret == cyBuffer)
                    yCaret = 0;
                break;

              case '\r':          // carriage return
                xCaret = 0;

                if (++yCaret == cyBuffer)
                    yCaret = 0;
                break;

              case '\x1B':    // escape
                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:                  // character codes
                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:
          PostQuitMessage (0) ;
          return 0 ;
     }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
}

        为了简单起见,TYPER 使用了等宽字体。和你猜想的一样,设计一个使用变宽字体的文本编辑器是非常困难的。本程序在几个地方获得设备环境:在处理 WM_CREATE 消息、WM_KEYDOWN 消息、WM_CHAR 消息和 WM_PAINT 消息时。每一次对 GetStockObject 函数和 SelectObject 函数的调用都选择当前字符集中的等宽字体。

        处理 WM_SIZE 消息时,TYPER 程序计算字符宽度和窗口高度,并在变量 cxBuffer 和 cyBuffer 中保存这些值。然后它使用 malloc 函数分配一个缓冲区,用以存储在窗口中输入的所有字符。注意,缓冲区的大小(以字节计)依赖于 cxBuffer, cyBuffer 和 sizeof(TCHAR)。sizeof(TCHAR)的值是 1 或者 2,这取决于此程序是使用 8 位字符还是使用 Unicode 编译。

        xCaret 和 yCaret 变量存储插入字符的字符位置。处理 WM_SETFOCUS 消息时,TYPER 调用 CreateCaret 函数创建符号字符高度和宽度的插入字符。然后它调用 SetCaretPos 函数设置插入字符的位置,调用 ShowCaret 函数使插入字符可见。处理 WM_KILLFOCUS 消息时,TYPER 调用 HideCaret 函数和 DestroyCaret 函数。

        WM_KEYDOWN 消息的处理主要是对光标移动键的处理。Home 和 End 把插入符号送至一行的开始和结尾,Page Up 和 Page Down 把插入符号送至窗口的顶部和底部。方向键的工作方式不变。对 Delete 键,TYPER 必须移动缓冲区中从下一个插入符号位置到本行末尾的所有内容,然后在行尾显示一个空格。

        WM_CHAR 消息处理 Backspace 键、Tab 键、换行(Ctrl+Enter)键,Enter 键,Esc 键和字符键。注意当处理 WM_CHAR 消息(假定用户输入的每个字符都是重要的)时,我使用了 lParam 参数中的重复计数。但是在 WM_KEYDOWN 消息中,我没有利用重复计数(以防止无意思的过度滚动)。通过使用 SendMessage 函数可简化处理 Backspace 键和 Tab 键的逻辑。Backspace 键的处理逻辑是由处理 Delete 键的逻辑模仿而成的,Tab 则如同输入了一连串的空格。

        像我前面提到的那样,只要窗口过程处理的是非 WM_PAINT 消息,那么当要在窗口内绘制图形时,程序就应该隐藏插入符号。当为 Delete 键处理 WM_KEYDOWN 消息和为字符键处理 WM_CHAR 消息时,TYPER 就这样做了。TYPER 改变缓冲区的内容,然后在窗口中绘制一个或多个新字符。

        尽管 TYPER 程序使用同 KEYVIEW2 中一样的逻辑来实现在用户切换键盘布局时切换字符集的功能,但它仍然不能在 Windows 的远东版本下正常工作。TYPER 不允许使用双字节宽度的字符。这就提出了新的问题,我们将在第 17 章详细讨论字体和文本输出。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值