65.9K
CodeProject 正在变化。 阅读更多。
Home

选择矩形:“资源管理器风格”实现!

starIconstarIconstarIconstarIconstarIcon

5.00/5 (12投票s)

2010年1月26日

CPOL

2分钟阅读

viewsIcon

43081

downloadIcon

1860

半透明选择矩形

引言

你好。

我找不到一个完整的替代方案来代替旧的 API 函数 DrawFocusRect(...),以便在我的视图中使用它...

所以我用自己的方式做了。现在它在这里,也供你参考。

背景

分层窗口 (layered window) 可以很好地扮演这种选择矩形的角色——因为它可以以 Alpha 混合的方式显示。在这种情况下,我们不需要控制选择的父视图的客户端区域的重绘(如果我们在父视图的设备上下文中绘制动态可变尺寸的内容,则需要这样做)。

我编写了一个 class CCoverWnd : public CWnd 类来实现选择矩形的行为。这个窗口现在必须是半透明的,并且分层工作。

步骤 1. 创建分层窗口

在创建分层窗口时,我们必须设置一个特殊的扩展窗口样式 WS_EX_LAYERED。此外,我们必须记住,分层窗口不能是子窗口。由于选择矩形没有标题栏,我们可以将普通窗口样式指定为 WS_POPUP

bool CCoverWnd::Create(CWnd* pParentView)
{
  bool bResult = false;

  if (pParentView) {
    // The special style of the layered windows (WS_EX_LAYERED) will be used:
    CreateEx(WS_EX_LAYERED,                          // to be created as a layered window
             _T("STATIC"),                           // using of an existing window class
             NULL,                                   // there is no title 
                                                     // for the rectangle
             WS_POPUP | WS_VISIBLE,                  // common window styles
             -1, -1, 0, 0,                           // initial position and dimensions
             pParentView->GetSafeHwnd(),             // parent view of the rectangle
             NULL);                                  // not used menu identifier
    if (GetSafeHwnd()) {
      pParentView->GetClientRect(m_cParentCliRect);  // client area of the parent view
      pParentView->ClientToScreen(m_cParentCliRect); // our coordinates 
                                                     // are screen related
      pParentView->SetFocus();                       // return the focus at parent
      bResult = true;
    }
  }

  return bResult;
}

现在我们可以创建我们的覆盖窗口,并且需要能够以某种位置、某种大小和某种绘制的表面显示它... :)

步骤 2. 更新分层窗口

有一个 API 函数用于更新分层窗口的位置和内容 (WinUser.h)

WINUSERAPI
BOOL
WINAPI
UpdateLayeredWindow(
    __in HWND hWnd,                  // handle of the layered window
    __in_opt HDC hdcDst,             // destination DC (in our case screen DC)
    __in_opt POINT *pptDst,          // destination position at the screen
    __in_opt SIZE *psize,            // dimensions of window at the screen
    __in_opt HDC hdcSrc,             // source (memory) DC of prepainting
    __in_opt POINT *pptSrc,          // source position for the surface bits transfer
    __in COLORREF crKey,             // color to be fully transparent (not our case)
    __in_opt BLENDFUNCTION *pblend,  // blending parameters
    __in DWORD dwFlags);             // kind of the transfer 
                                     // (in our case ULW_ALPHA - for alpha blending)

参数 pblend 是一个指向 struct BLENDFUNCTION 类型的指针,如下所示 (WinGDI.h)

typedef struct _BLENDFUNCTION
{
    BYTE   BlendOp;                  // must be AC_SRC_OVER currently
    BYTE   BlendFlags;               // must be zero
    BYTE   SourceConstantAlpha;      // general surface transparency 
                                     // [0(opaque) -255(transparent)]
    BYTE   AlphaFormat;              // 0 - the transparency of the surface bits 
                                     // has no role
                                     // AC_SRC_ALPHA - the transparency of the 
                                     // surface bits has a role
                                     // this parameter is independent from 
                                     // SourceConstantAlpha
} BLENDFUNCTION, *PBLENDFUNCTION;

更新的实现

现在我们可以为 CCoverWnd 提供一个自己的函数来显示它 :).

// Placing the window at the screen position crPos
void CCoverWnd::ShowAt(const CRect& crPos)
{
  if (GetSafeHwnd()) {
    CRect cMoveRect(crPos);
    cMoveRect.NormalizeRect();

    CRect cIntersectRect(cMoveRect);
    cIntersectRect.IntersectRect(m_cDrawRect, cMoveRect); // allowed area for placing

    int iWidth(cMoveRect.Width());   // painting dimension per X
    int iHeight(cMoveRect.Height()); // painting dimension per Y

    HDC hdcScreen = ::GetDC(NULL);           // destination screen DC
    HDC hDC = ::CreateCompatibleDC(hdcScreen); // source memory DC

    // We have to create a bitmap by CreateDIBSection(..)
    // to operate with its bits directly. Thanks to SledgeHammer01
    BITMAPINFO sBI              = {0};
    sBI.bmiHeader.biSize        = sizeof(BITMAPINFOHEADER);
    sBI.bmiHeader.biWidth       = iWidth; 
    sBI.bmiHeader.biHeight      = iHeight; 
    sBI.bmiHeader.biPlanes      = 1; 
    sBI.bmiHeader.biBitCount    = 32; 
    sBI.bmiHeader.biCompression = BI_RGB;

    HBITMAP hBmp = ::CreateDIBSection(hDC, &sBI, DIB_RGB_COLORS, NULL, NULL, 0);
    HBITMAP hBmpOld = (HBITMAP) ::SelectObject(hDC, hBmp);
    
    bool bFillAlphaOK = FillAlpha(hBmp); // try to fill the surface bits 
                                         // with alpha channel
    if (!bFillAlphaOK) {
      FillRGB(hDC, iWidth, iHeight); // else - without the alpha channel
    }

    // Preparing the blend parameters
    BLENDFUNCTION blend       = {0};
    blend.BlendOp             = AC_SRC_OVER;
    blend.SourceConstantAlpha = bFillAlphaOK ? 160 : 64;
    blend.AlphaFormat         = bFillAlphaOK ? AC_SRC_ALPHA : 0;

    // Destination position at the screen
    POINT ptPos   = {cIntersectRect.left,
                     cIntersectRect.top};

    // Dimensions of the bits transfer
    SIZE sizeWnd  = {cIntersectRect.Width(),
                     cIntersectRect.Height()};

    // Source position in source (memory DC)
    POINT ptSrc   = {cIntersectRect.left - cMoveRect.left,
                     cIntersectRect.top  - cMoveRect.top};

    // Call the wizard :)
    ::UpdateLayeredWindow(m_hWnd, hdcScreen, &ptPos, &sizeWnd,
                          hDC, &ptSrc, 0, &blend, ULW_ALPHA);

    // Clearance
    ::SelectObject(hDC, hBmpOld);
    ::DeleteObject(hBmp);
    ::DeleteDC(hDC);
    ::ReleaseDC(NULL, hdcScreen);
  }
}

填充过程封装在 CCoverWnd 中,可以在源代码文件中看到(上面的第三个下载集)。

代码使用

在应该在其表面提供选择的 CWnd 继承对象中实例化并创建 CCoverWnd 类的一个对象就足够了。

class CCoverTestDlg : public CDialog
{
  bool      m_bCaptured;
  
  CPoint    m_cpStart,
            m_cpEnd;

  CCoverWnd m_cCoverWnd; // An own test instance, could be static :)
...

现在可以显示覆盖,例如

void CCoverTestDlg::ShowCover()
{
  if (!m_cCoverWnd.GetSafeHwnd()) {
    m_cCoverWnd.Create(this); // The info of the client area will be used
                              // by the child-cover, see CCoverWnd::Create(..)
  }

  if (m_cCoverWnd.GetSafeHwnd()) {
    CRect cShowRect(m_cpStart, m_cpEnd);
    ClientToScreen(cShowRect); // The cover is screen related

    m_cCoverWnd.ShowAt(cShowRect); // Good luck...
  }
}

... 也可以“隐藏”它

void CCoverTestDlg::DestroyCover()
{
  if (m_cCoverWnd.GetSafeHwnd()) {
    m_cCoverWnd.DestroyWindow(); // Thanks...
  }
}

关注点

感谢 SledgeHammer01 - 感谢他建议直接写入颜色数据,而无需使用函数 CBitmap::SetBitmapBits(..)

该函数现在处于以下状态

bool CCoverWnd::FillAlpha(HBITMAP hBmp)
{
  bool bResult = false;

  if (hBmp) {
    BITMAP bmp;
    GetObject(hBmp, sizeof(BITMAP), &bmp);

    DWORD dwCount = bmp.bmWidthBytes * bmp.bmHeight;
    if (dwCount >= sizeof(DWORD)) {
      DWORD* pcBitsWords = (DWORD*) bmp.bmBits;
      if (pcBitsWords) {
        DWORD dwIndex(dwCount / sizeof(DWORD));
        DWORD dwUp = bmp.bmWidth;
        DWORD dwDn = dwIndex -dwUp;
        DWORD dwR  = bmp.bmWidth -1;
        while (dwIndex--)  {
          DWORD dwSides = dwIndex % bmp.bmWidth;
          if (dwIndex < dwUp ||
              dwIndex > dwDn ||
              0   == dwSides ||
              dwR == dwSides) {
            pcBitsWords[dwIndex] = sm_clrPenA;   // 0xFF0080FF (Edge, AA =0xFF)
          } else {
            pcBitsWords[dwIndex] = sm_clrBrushA; // 0x400020FF (Plain, AA =0x40)
          }
        }
        bResult = true;
      }
    }
  }

  return bResult;
}

我很高兴收到进一步的建议来改进位写入。

谢谢!

历史

  • 2010年1月25日 12:46:50 UTC+0100 -- 创建
  • 2010年1月25日 17:48:01 UTC+0100 -- 修改了“背景”部分
  • 2010年1月25日 18:01:36 UTC+0100 -- 修改了“要点”部分
  • 2010年1月28日 12:13:54 UTC+0100 -- 改进了代码
© . All rights reserved.