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





5.00/5 (12 votes)
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
:
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):
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):
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 :).
// 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.
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:
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:
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:
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