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) {
CreateEx(WS_EX_LAYERED, _T("STATIC"), NULL, WS_POPUP | WS_VISIBLE, -1, -1, 0, 0, pParentView->GetSafeHwnd(), NULL); if (GetSafeHwnd()) {
pParentView->GetClientRect(m_cParentCliRect); pParentView->ClientToScreen(m_cParentCliRect); pParentView->SetFocus(); 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, __in_opt HDC hdcDst, __in_opt POINT *pptDst, __in_opt SIZE *psize, __in_opt HDC hdcSrc, __in_opt POINT *pptSrc, __in COLORREF crKey, __in_opt BLENDFUNCTION *pblend, __in DWORD dwFlags);
The parameter pblend
is a pointer of type struct BLENDFUNCTION
that is described below (WinGDI.h):
typedef struct _BLENDFUNCTION
{
BYTE BlendOp; BYTE BlendFlags; BYTE SourceConstantAlpha; BYTE AlphaFormat; } BLENDFUNCTION, *PBLENDFUNCTION;
Implementation of the Updating
Now we can provide our own function for CCoverWnd
to show it :).
void CCoverWnd::ShowAt(const CRect& crPos)
{
if (GetSafeHwnd()) {
CRect cMoveRect(crPos);
cMoveRect.NormalizeRect();
CRect cIntersectRect(cMoveRect);
cIntersectRect.IntersectRect(m_cDrawRect, cMoveRect);
int iWidth(cMoveRect.Width()); int iHeight(cMoveRect.Height());
HDC hdcScreen = ::GetDC(NULL); HDC hDC = ::CreateCompatibleDC(hdcScreen);
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); if (!bFillAlphaOK) {
FillRGB(hDC, iWidth, iHeight); }
BLENDFUNCTION blend = {0};
blend.BlendOp = AC_SRC_OVER;
blend.SourceConstantAlpha = bFillAlphaOK ? 160 : 64;
blend.AlphaFormat = bFillAlphaOK ? AC_SRC_ALPHA : 0;
POINT ptPos = {cIntersectRect.left,
cIntersectRect.top};
SIZE sizeWnd = {cIntersectRect.Width(),
cIntersectRect.Height()};
POINT ptSrc = {cIntersectRect.left - cMoveRect.left,
cIntersectRect.top - cMoveRect.top};
::UpdateLayeredWindow(m_hWnd, hdcScreen, &ptPos, &sizeWnd,
hDC, &ptSrc, 0, &blend, ULW_ALPHA);
::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; ...
Now it would be possible to show the cover, for example:
void CCoverTestDlg::ShowCover()
{
if (!m_cCoverWnd.GetSafeHwnd()) {
m_cCoverWnd.Create(this); }
if (m_cCoverWnd.GetSafeHwnd()) {
CRect cShowRect(m_cpStart, m_cpEnd);
ClientToScreen(cShowRect);
m_cCoverWnd.ShowAt(cShowRect); }
}
... as well to "hide" it:
void CCoverTestDlg::DestroyCover()
{
if (m_cCoverWnd.GetSafeHwnd()) {
m_cCoverWnd.DestroyWindow(); }
}
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; } else {
pcBitsWords[dwIndex] = sm_clrBrushA; }
}
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