Click here to Skip to main content
15,867,308 members
Articles / Desktop Programming / MFC

Selection Rectangle: An Implementation in "Explorer style" :)

Rate me:
Please Sign up or sign in to vote.
5.00/5 (13 votes)
28 Jan 2010CPOL2 min read 40.6K   1.8K   48   7
A semi-transparent selection rectangle

Introduction

Hello.

I could not find a complete replacement for the old API function DrawFocusRect(...) to use it in my views...

So I did it my way. Now it's here, for your response too.

Background

A layered window could play the role of such selection rectangle in an excellent manner - because of its possibility to be shown with the alpha blending. In this case, we don't need to control the repainting of the client area of the parent view of selection (it would be necessary when we would draw something dynamically changeable in its dimensions in the device context of the parent view).

I have written a class CCoverWnd : public CWnd to implement the behaviour of the selection rectangle. This window must be semi-transparent now layered in its job.

Step 1. Creating of a Layered Window

We have to set a special extension window style WS_EX_LAYERED at creation of a layered window. In addition, we must remember that a layered window may not be a child. Since the selection rectangle has no title bar, we can specify the normal window style as WS_POPUP:

C++
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;
}

Now we can create our covering window and need a possibility to show it at some position, in some size and with some painted surface... :)

Step 2. Updating of a Layered Window

There is an API function to update the placement and the content of a layered window (WinUser.h):

C++
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)

The parameter pblend is a pointer of type struct BLENDFUNCTION that is described below (WinGDI.h):

C++
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;

Implementation of the Updating

Now we can provide our own function for CCoverWnd to show it :).

C++
// 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);
  }
}

The fill-out procedures are encapsulated in CCoverWnd and could be observed in the source files (the third download set above).

Using of Code

It would be enough to instance and create an object of the CCoverWnd class in a CWnd-inherited object, that should provide the selection on its surface.

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

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

Now it would be possible to show the cover, for example:

C++
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...
  }
}

... as well to "hide" it:

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

Points of Interest

Thanks to SledgeHammer01 - for the advice to write the color data directly, without the function CBitmap::SetBitmapBits(..).

The touched function has the following state now:

C++
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;
}

I would be glad to read the next advice to improve the bits writing.

Thank you!

History

  • Mon Jan 25 12:46:50 UTC+0100 2010 -- Created
  • Mon Jan 25 17:48:01 UTC+0100 2010 -- Modified section "Background"
  • Mon Jan 25 18:01:36 UTC+0100 2010 -- Modified section "Points of Interest"
  • Thu Jan 28 12:13:54 UTC+0100 2010 -- Improved code

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer COPA-DATA GmbH
Austria Austria
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Question.NET Pin
Ger8315-Aug-12 0:33
Ger8315-Aug-12 0:33 
AnswerRe: .NET Pin
Eugen Podsypalnikov15-Aug-12 2:58
Eugen Podsypalnikov15-Aug-12 2:58 
- As I know, a .NET application can access any Windows API, for example SendMessage(..),
these function are marked by the "::" prefix in the code above
and making the kernel of the "Selection Rectangle"

- The second thinkable chance is the extended painting (inside of the WM_PAINT handling):
--- firstly the form will be drawn normally
--- then the captured by the mouse rectangle, by a semi-transparent[^] brush
--- of course, any changing/reset of the captured rectangle should invalidate its old area

But, I have not tried it myself... Smile | :)
They sought it with thimbles, they sought it with care;
They pursued it with forks and hope;
They threatened its life with a railway-share;
They charmed it with smiles and soap. Smile | :)

GeneralSuggestion Pin
Junlin Xu6-Jan-11 7:23
Junlin Xu6-Jan-11 7:23 
GeneralFillAlpha() optimizations... Pin
SledgeHammer0126-Jan-10 11:22
SledgeHammer0126-Jan-10 11:22 
GeneralRe: FillAlpha() optimizations... Pin
Eugen Podsypalnikov26-Jan-10 19:38
Eugen Podsypalnikov26-Jan-10 19:38 
GeneralRe: FillAlpha() optimizations... Pin
SledgeHammer0126-Jan-10 20:09
SledgeHammer0126-Jan-10 20:09 
GeneralRe: FillAlpha() optimizations... Pin
Eugen Podsypalnikov26-Jan-10 23:22
Eugen Podsypalnikov26-Jan-10 23:22 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.