WinAPI:如何创建水平翻转的箭头光标?

时间:2019-06-27 11:18:36

标签: c++ winapi mfc

在一些应用程序中,我注意到一个水平翻转的箭头光标-例如在写字板中,位于段落左侧的水平翻转的箭头光标表示可以选择光标所指向的行。

enter image description here

我的问题是,如何在WinAPI(或MFC)中以编程方式创建此光标?对于这种类型的游标,LoadCursor function似乎没有方便的预定义常量。

预先感谢您的回复。

1 个答案:

答案 0 :(得分:2)

您说得对,这是 Windows 应用程序中相当常见的鼠标光标样式。令人费解的是,它并未作为标准游标之一包含在内,这使得应用程序开发人员的工作更加困难。

评论中建议您可以在 Paint 中自己创建此游标(或下载其他人创建的游标),然后将其作为资源包含在您的应用程序二进制文件中,但这有一些非常显着的缺点。它不仅会使您的二进制文件膨胀,而且还意味着您的光标样式本质上是硬编码。默认箭头鼠标光标在 Windows 版本中甚至发生了多次更改,因此您需要在不同版本的“翻转”光标之​​间进行动态维护和选择,以便与默认光标兼容,更不用说自定义的用户了他们的光标,甚至选择了不同的内置主题(如“反转”)。因此,我强烈建议不要使用这种方法。

正确的解决方案是以编程方式简单地翻转用户的当前箭头光标。这可确保翻转的光标与用户的首选光标样式相匹配。定义如下函数:

/// Creates a cursor that is based on the specified cursor resource (a la LoadCursor),
/// but has been flipped horizontally.
/// 
/// @param hInstance     A handle to an instance of the module whose executable file
///                      contains the cursor template.
/// @param pCursorName   The name of the template cursor resource to be loaded, or
///                      an integer resource created using the MAKEINTRESOURCE macro
///                      identifying the cursor to be loaded.
/// @return
///    Returns a handle to the newly-created cursor.
/// @remark
///    Note that the returned cursor must be destroyed when you are finished with it
///    by calling @c DestroyIcon.
/// @remark
///    This function swallows errors, ensuring that some cursor is always returned,
///    even if it has not been flipped. The client could, if desired, modify the
///    implementation to throw exceptions or return NULL in response to errors.
HCURSOR CreateCursorFlipped(HINSTANCE hInstance, LPCTSTR pCursorName)
{
   // Load the specified cursor to use as a template.
   HCURSOR hCursor = LoadCursor(hInstance, pCursorName);

   // Get the underlying bitmaps for the cursor template.
   ICONINFO ii;
   if (GetIconInfo(hCursor, &ii))
   {
      // Retrieve information about the bitmap.
      BITMAP bm;
      if (GetObject(ii.hbmMask, sizeof(bm), &bm) == sizeof(bm))
      {
         // Create a screen-compatible device context, and draw the cursor bitmaps into
         // it, flipped across the X axis, in order to create the new cursor bitmap.
         HDC hDC = CreateCompatibleDC(NULL);
         if (hDC)
         {
            HBITMAP hBmpOriginal = (HBITMAP)SelectObject(hDC, ii.hbmMask);
            StretchBlt(hDC,
                       bm.bmWidth - 1,
                       0,
                       -bm.bmWidth,
                       bm.bmHeight,
                       hDC,
                       0,
                       0,
                       bm.bmWidth,
                       bm.bmHeight,
                       SRCCOPY);
            if (ii.hbmColor)
            {
               SelectObject(hDC, ii.hbmColor);
               StretchBlt(hDC,
                          bm.bmWidth - 1,
                          0,
                          -bm.bmWidth,
                          bm.bmHeight,
                          hDC,
                          0,
                          0,
                          bm.bmWidth,
                          bm.bmHeight,
                          SRCCOPY);
            }
            SelectObject(hDC, hBmpOriginal);

            DeleteDC(hDC);
         }

         // Flip the new cursor's hotspot horizontally.
         ii.xHotspot = (bm.bmWidth - 1 - ii.xHotspot);

         // Create a new cursor.
         HCURSOR hCursorNew = CreateIconIndirect(&ii);
         if (hCursorNew)
         {
            hCursor = hCursorNew;
         }
      }

      // Delete the unneeded bitmaps.
      DeleteObject(ii.hbmMask);
      if (ii.hbmColor)
      {
         DeleteObject(ii.hbmColor);
      }
   }

   return hCursor;
}

(如果您使用的是 MFC,那么您可以使用包装类和 RAII 进一步简化此代码。)

得到题中描述的水平翻转箭头光标,用法很简单:

CreateCursorFlipped(nullptr, IDC_ARROW);

但请记住,因为 CreateCursorFlipped 实际上创建了一个游标(而不是仅仅加载一个系统游标),所以您必须销毁游标通过调用 DestroyIcon 完成使用它。