Direct2D: Opaque child windows over a transparent parent window - c++

I'd like to create a Direct2D application that has a transparent background on which a few opaque complex controls are placed.
The problem can be broken into several sub-problems:
Architecture: Should the controls be implemented as child windows? I think that this is the correct approach, rather that creating Direct2D polygons that implement a child-window functionality.
I tried to implement this by initializing the parent window:
SetWindowLong(m_hwnd, GWL_EXSTYLE, GetWindowLong(m_hwnd, GWL_EXSTYLE) | WS_EX_LAYERED);
SetLayeredWindowAttributes(m_hwnd, 0, (255 * 50) / 100, LWA_ALPHA);
And create the child window as WS_CHILD. This resulted in a child on which all D2D drowings including background are transparent. I could not find a way to make the child opaque.
When I create the child window as WS_POPUP or WS_OVERLAPPED the opacity problem is solved but the child window is located on the desktop unrelated to the parent.
Layered Window?
I chose to work with Layered Windows but since I target at VistaSP2 and higher there might be better solutions.
I tried the solution offered here but I too failed to implement it.

Do you mean to create a 32-bit-per-pixel window?
(Sorry for being unable to comment, not enough rep over here)
In that case, you are FORCED to use UpdateLayeredWindow (and a CreateDIBSection call while you initialize), no matter what, every time you finished drawing the scene, after you finished drawing the scene, like:
// Draw to your D2D1 RenderTarget here
RECT rcWin = {0};
GetWindowRect(hWnd,&rcWin);
POINT ptw = {rcWin.left,rcWin.top};
SIZE pts = {rcWin.right-rcWin.left,rcWin.bottom-rcWin.top};
POINT ptsrc = {0};
HDC ScreenDC = GetDC(0);
UpdateLayeredWindow( hWnd, ScreenDC, &ptw, &pts, MemDC, &ptsrc, 0, &bf, ULW_ALPHA);
ReleaseDC(0,ScreenDC);
About the initialization:
RECT r = {0};
GetWindowRect(hWnd,&r);
HDC scrDC = GetDC(0);
MemDC = CreateCompatibleDC(scrDC);
ReleaseDC(0,scrDC);
if(!MemDC)
{ FailInit(); }
BITMAPINFO bmi = {0};
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biWidth = r.right-r.left;
bmi.bmiHeader.biHeight = r.bottom-r.top;
bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
DIBSectionBitmap = CreateDIBSection(MemDC,&bmi,DIB_RGB_COLORS,0,0,0);
if(!DIBSectionBitmap)
return 0;
OldBmp = (HBITMAP)SelectObject(MemDC,DIBSectionBitmap);
// Now create the HWND D2D1 RenderTarget.
About the freeing of the resources:
// Free the D2D1 RenderTarget here
if(MemDC && OldBmp)
SelectObject(MemDC,OldBmp);
if(DIBSectionBitmap)
DeleteObject(DIBSectionBitmap);
if(MemDC)
DeleteDC(MemDC);
MemDC = 0;
OldBmp = 0;
DIBSectionBitmap = 0;
EDIT: MemDC, OldBmp and DIBSectionBitmap are Per-Window.
MemDC is a HDC.
OldBmp is a HBITMAP.
DIBSectionBitmap is a HBITMAP.
At this point you can draw your child windows as if they were part of your very own main window, with a per-pixel alpha precision, but you'll need to handle focusing and messaging on your own.

Related

How to maintain transparency when writing text on a Windows System Tray icon from unmanaged C program

I have a MFC C++ (unmanaged) Windows application that uses a “standard” icon in the System Tray. This icon was created & edited using Visual Studio and is 32x32 pixels with only 4bit colour (according to VS's Resource Editor).
With Visual Studio, I also set a transparent background (shown as white in the “before” image).
I wish to dynamically change the icon by writing 2 digits (1-99) on top of it.
Using the code below (based on that in this question: How to draw text with transparency using GDI?) to superimpose “55” in yellow on the icon, it works except that the transparency disappears (it appears black in the “after” image and on the System Tray). My actual code differs very very slightly in that the font size (20), font name (Courier New), text colour (yellow - RGB(255, 255, 0)) and the numeric value (55) are run-time variables rather than fixed values.
Any suggestions on how to make the background remain transparent as far as the System Tray is concerned gratefully received.
These images have been captured using MS’s Snipping tool with the image open in MS Paint as a 32x32 icon wouldn't be very visible as-is.
Before Image:
After image:
Code:
void CreateNewIcon(HICON &hNewIcon, HICON hBackgroundIcon)
{
::DestroyIcon(hNewIcon);
// First create font
LOGFONT lf = { 0 };
lf.lfHeight = -20;
lf.lfWeight = FW_BOLD;
lf.lfOutPrecision = OUT_TT_PRECIS;
lf.lfQuality = CLEARTYPE_QUALITY;
wmemset(lf.lfFaceName, 0, LF_FACESIZE);
lstrcpy(lf.lfFaceName, L"Courier New");
HFONT hFont = ::CreateFontIndirect(&lf);
ICONINFO ii = { 0 };
::GetIconInfo(hBackgroundIcon, &ii);
BITMAP bm = { 0 };
::GetObject(ii.hbmColor, sizeof(bm), &bm);
SIZE szBmp = { bm.bmWidth, bm.bmHeight };
HDC hDc = ::GetDC(NULL);
HDC hMemDC = ::CreateCompatibleDC(hDc);
HGDIOBJ hOldBmp = ::SelectObject(hMemDC, ii.hbmColor);
HGDIOBJ hOldFont = ::SelectObject(hMemDC, hFont);
::SetBkMode(hMemDC, TRANSPARENT);
::SetTextColor(hMemDC, RGB(255, 255, 0));
::TextOut(hMemDC, 0, 8, L"55", 2);
::SelectObject(hMemDC, hOldFont);
::SelectObject(hMemDC, hOldBmp);
// We need a simple mask bitmap for the icon
HBITMAP hBmpMsk = ::CreateBitmap(szBmp.cx, szBmp.cy, 1, 1, NULL);
ICONINFO ii2 = { 0 };
ii2.fIcon = TRUE;
ii2.hbmColor = ii.hbmColor;
ii2.hbmMask = hBmpMsk;
// Create updated icon
hNewIcon = ::CreateIconIndirect(&ii2);
// Cleanup
::DeleteObject(hBmpMsk);
::DeleteDC(hMemDC);
::ReleaseDC(NULL, hDc);
::DeleteObject(ii.hbmColor);
::DeleteObject(ii.hbmMask);
::DeleteObject(hFont);
}
There are multiple issues with our code:
You are trying to draw cleartype-quality text over transparent icon part. But cleartype font rendering must be performed over opaque background because it needs to inspect background color. So you should switch to Anitialiased quality (or to not antialiased quality) or provide opaque background for your text.
When you create hBmpMsk you skip it's content initialization by supplying NULL for bits pointer, so resulting icon will actually have completely random transparency. You need to fill this mask bitmat appropriately.
Also you probably need to switch to higher bit depth because monochrome bitmap mask can't handle semitransparent parts of antialiased text.
update
I think you should draw cleartype text but with opaque background, then get text rectangle using something like GetTextExtentPoint32, then copy data from the original bitmap mask into hBmpMsk and then finally fill white (text) rectangle on it so the new icon will preserve transparency from original and has opaque text block.
Thanks for all the help from VTT without which I wouldn't have been able to get this far. This appears to work for me.
void CreateNewIcon(HICON &hNewIcon, HICON hBackgroundIcon)
{
::DestroyIcon(hNewIcon);
HDC hDc = ::GetDC(NULL);
HDC hMemDC = ::CreateCompatibleDC(hDc);
// Load up background icon
ICONINFO ii = { 0 };
::GetIconInfo(hBackgroundIcon, &ii);
HGDIOBJ hOldBmp = ::SelectObject(hMemDC, ii.hbmColor);
// Create font
LOGFONT lf = { 0 };
lf.lfHeight = -20;
lf.lfWeight = FW_BOLD;
lf.lfOutPrecision = OUT_TT_PRECIS;
lf.lfQuality = ANTIALIASED_QUALITY;
wmemset(lf.lfFaceName, 0, LF_FACESIZE);
lstrcpy(lf.lfFaceName, L"Courier New");
HFONT hFont = ::CreateFontIndirect(&lf);
HGDIOBJ hOldFont = ::SelectObject(hMemDC, hFont);
// Write text
::SetBkMode(hMemDC, TRANSPARENT);
::SetTextColor(hMemDC, RGB(255, 255, 0));
::TextOut(hMemDC, 0, 8, L"55", 2);
// Set up mask
HDC hMaskDC = ::CreateCompatibleDC(hDc);
HGDIOBJ hOldMaskBmp = ::SelectObject(hMaskDC, ii.hbmMask);
// Also write text on here
HGDIOBJ hOldMaskFont = ::SelectObject(hMaskDC, hFont);
::SetBkMode(hMaskDC, TRANSPARENT);
::SetTextColor(hMaskDC, RGB(255, 255, 0));
::TextOut(hMaskDC, 0, 8, L"55", 2);
// Get handle to create mask bitmap
HBITMAP hMaskBmp = (HBITMAP)::SelectObject(hMaskDC, hOldMaskBmp);
// Use new icon bitmap with text and new mask bitmap with text
ICONINFO ii2 = { 0 };
ii2.fIcon = TRUE;
ii2.hbmMask = hMaskBmp;
ii2.hbmColor = ii.hbmColor;
// Create updated icon
hNewIcon = ::CreateIconIndirect(&ii2);
// Cleanup bitmap mask
::DeleteObject(hMaskBmp);
::DeleteDC(hMaskDC);
// Cleanup font
::SelectObject(hMaskDC, hOldMaskFont);
::SelectObject(hMemDC, hOldFont);
::DeleteObject(hFont);
// Release background bitmap
::SelectObject(hMemDC, hOldBmp);
// Delete background icon bitmap info
::DeleteObject(ii.hbmColor);
::DeleteObject(ii.hbmMask);
::DeleteDC(hMemDC);
::ReleaseDC(NULL, hDc);
}

Win32 C++ Alphablend a Bitmap Partially Transparent

I've googled, seen examples, other questions here, MSDN and Downloaded Example code. I cannot figure out what is wrong with this.
// setting up the memory DC and selecting in the bitmap
HDC hdc = GetDC(hWnd);
HDC hdcMem = CreateCompatibleDC(hdc);
ReleaseDC(hWnd, hdc);
HBITMAP hbmOld = (HBITMAP)SelectObject(hdcMem, bitmap.hbmLogo);
// setting up the blend function
BLENDFUNCTION bStruct;
bStruct.BlendOp = AC_SRC_OVER;
bStruct.BlendFlags = 0;
bStruct.SourceConstantAlpha = 255;
bStruct.AlphaFormat = AC_SRC_ALPHA;
// try
BOOL check = AlphaBlend(buffer.getBufferDC(), 0, 0, bitmap.bmLogo.bmWidth, bitmap.bmLogo.bmHeight, hdcMem, 0, 0, bitmap.bmLogo.bmWidth, bitmap.bmLogo.bmHeight, bStruct);
if (check == FALSE) MessageBox(0,0,0,0);
// this is how I load the bitmap, it is a resource.
bitmap.hbmLogo = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_LOGO_0));
if (bitmap.hbmLogo == NULL) { MessageBox(NULL, "Could not read the logo bitmap.", "Error", MB_OK); return false; }
GetObject(bitmap.hbmLogo, sizeof(bitmap.bmLogo), &bitmap.bmLogo);
I use the message box to quickly check the result. Check always returns TRUE. The bitmap and its dimensions are correct.
I've tried it over different background colors, alpha values, and still nothing, replacing that with BitBlt or TransparentBitBlt, no problem, the logo displays. All my attempts with the AlphaBlend function has resulted in no change. The logo does not appear, even for a second, on the screen.
Any ideas?
Thanks.
Found the solution after looking closer at an example.
I set the BLENDFUNCTION as a global, and in the WM_CREATE message I used:
m_bf.BlendOp = AC_SRC_OVER;
m_bf.BlendFlags = 0;
m_bf.SourceConstantAlpha = 100; // any 0 to 255
m_bf.AlphaFormat = 0;
LoadBitmapsFromResource();
and it is now working.

WinAPI. How to redraw window which has no background?

Hi I have WNDCLASSEX structure which has this data:
m_wndClass.cbSize = sizeof(WNDCLASSEX);
m_wndClass.style = CS_NOCLOSE;
m_wndClass.lpfnWndProc = WndProc;
m_wndClass.cbClsExtra = 0;
m_wndClass.cbWndExtra = 0;
m_wndClass.hInstance = GetModuleHandle(NULL);
m_wndClass.hIcon = NULL;
m_wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
m_wndClass.hbrBackground = NULL;
m_wndClass.lpszMenuName = NULL;
m_wndClass.lpszClassName = Checkbox::CHECKBOX_CLASS.c_str();
m_wndClass.hIconSm = NULL;
I need to have window without background, because I need to draw text on parent window and text may be any color.
Drawing works fine, code for drawing:
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC dc = BeginPaint(window, &ps);
if (!classInfo->m_text.empty())
{
HDC wdc = GetDC(window);
SetBkMode(wdc,TRANSPARENT);
DrawText(wdc, classInfo->m_text.c_str(), -1, &classInfo->m_textRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_NOCLIP);
ReleaseDC(window, wdc);
}
EndPaint(window, &ps);
break;
}
However I have method to change text of label:
void Checkbox::SetText(const String& text)
{
m_text = text;
SetTextRectSize(); //calculates size of RECT
if (m_window != NULL)
InvalidateRect(m_window, NULL, TRUE);
}
After I create window with label I see label, however if I want to change text on it, it doesn't change (I need to manualy resize window and it changes after that). However I hadn't have this problem at the time when I used to have colored background, for example my window class had this:
m_wndClass.hbrBackground = HBRUSH(COLOR_3DFACE+1);
I want to ask, how to update window which, has no background.
EDIT: I have tried some stuff
FillRect(dc, &rect, (HBRUSH)GetStockObject(NULL_BRUSH));
also tried to change window procedure:
case WM_CTLCOLORSTATIC:
{
HDC hdc = (HDC) wp;
SetBkMode (hdc, TRANSPARENT);
return (LRESULT)GetStockObject(NULL_BRUSH);
}
And the result is that I draw new text on previous, after setting text into some longer text part of label becomes corupted! see this but after resizing the main window my label becomes readable.
Your code doesn't set the device context's text foreground color for DrawText(), although the default is black. See SetTextColor().
SetBkMode(..., TRANSPARENT) just sets the background color/mode for the DrawText() rect, not the entire window.
You are asking about how to draw the window so it is transparent, but the problem isn't with the drawing at all.
The answer is essentially that everything you have done so far towards making a transparent window is wrong. It looks like the window is transparent, but in fact it is not, as you can see from the behaviour you describe when you move and resize the window. That's the classic symptom.
In other words, you haven't made the window transparent, you have just stopped drawing the background. What you see as the background is just whatever happened to be underneath the window when it was first drawn.
You need make a Layered window. To find out how to make transparent windows, go here:
http://msdn.microsoft.com/en-us/library/ms997507.aspx
Do you want Text/Check/Label be transparent on parent form?
You can Add WS_CLIPSIBLINGS and WS_EX_TRANSPARENT..
Use SetBkMode(hDC, TRANSPARENT) When WM_PAINT message

window with transparent client area

I register the window class like this:
WNDCLASSEX wctt;
wctt.cbSize = sizeof(WNDCLASSEX);
wctt.style = CS_DBLCLKS;
wctt.lpfnWndProc = WndProcTooltip;
wctt.cbClsExtra = 0;
wctt.cbWndExtra = 0;
wctt.hInstance = m_hAppInstance;
wctt.hIcon = NULL;
wctt.hCursor = LoadCursor(NULL, IDC_SIZE);
wctt.hbrBackground = NULL;
wctt.lpszMenuName = NULL;
wctt.lpszClassName = _T("myWindow");
wctt.hIconSm = NULL;
RegisterClassEx(&wctt)
As you can see I use wctt.hbrBackground = NULL; so it will have no background.
The window is created like this:
::CreateWindowEx(WS_EX_TOPMOST | WS_EX_TOOLWINDOW,
_T("myWindow"),
NULL,
WS_VISIBLE | WS_POPUP,
50,
50,
150,
100,
NULL,
NULL,
m_hAppInstance,
NULL);
In the paint section I draw icon on the window:
PAINTSTRUCT ps;
HDC hdc;
BITMAP bitmap;
ICONINFO iconinfo;
hdc = ::BeginPaint(hWnd, &ps);
::SetBkMode(hdc,TRANSPARENT);
::GetIconInfo(localIcon, &iconinfo);
::GetObject(iconinfo.hbmColor, sizeof(bitmap), &bitmap);
::DeleteObject(iconinfo.hbmColor);
::DeleteObject(iconinfo.hbmMask);
::DrawIconEx(hdc, 0,0, localIcon, bitmap.bmWidth, bitmap.bmHeight, 0, NULL, DI_NORMAL);
The icon size is smaller than the window size and I get on the background the current view on the window below the popup.
But now when I move the window (or minimize the window below the popup) the background is not changing.
I was trying to make a timer that each time do the flowing:
RECT rcClient;
GetClientRect(hWnd, &rcClient);
InvalidateRect(hWnd,&rcClient,TRUE);
This makes the print function run again but the background of the icon is not changing.
Should I do anything in WM_ERASEBKGND?
Does Anyone have any idea how to make it work?
thanks,
guy
It's not enough to just let the background stay unpainted; you also need to get the window below yours to repaint itself when necessary.
If the windows are part of the same hierarchy, created by the same thread, it is sufficient to give your window the WS_EX_TRANSPARENT extended style. This causes the window underneath to paint itself first so the background is always up-to-date.
Otherwise you need to use SetWindowRgn so that your window actually doesn't exist outside of the borders you wish to paint.
Look at Layered Window. This feature allows creating semi-transparent windows of different shapes.
Add WS_EX_LAYERED extended attribute in your window class.
You can control the transparency of your window with these two functions:
SetLayeredWindowAttributes:
bAlpha controls the opacity of the entire window, if you pass LWA_ALPHA in dwFlags.
When bAlpha is 0, the window is completely transparent. When bAlpha is 255, the window is opaque.
crKey sets the color that would transparent.
All pixels painted by the window in this color will be transparent.
UpdateLayeredWindow gives you precise control over window transparency, you can give different parts of window different levels of transparency.
If you're trying to create a non-rectangular window, this is not sufficient. Setting "no background" simply means the background will not be drawn, and you'll see whatever happens to be in memory at that location.
To create a non-rectangular window, have a look at the SetWindowRgn function.

Creating a transparent window in C++ Win32

I'm creating what should be a very simple Win32 C++ app whose sole purpose it to ONLY display a semi-transparent PNG. The window shouldn't have any chrome, and all the opacity should be controlled in the PNG itself.
My problem is that the window doesn't repaint when the content under the window changes, so the transparent areas of the PNG are "stuck" with what was under the window when the application was initially started.
Here's the line where I setup the new window:
hWnd = CreateWindowEx(WS_EX_TOPMOST, szWindowClass, szTitle, WS_POPUP, 0, height/2 - 20, 40, 102, NULL, NULL, hInstance, 0);
For the call to RegisterClassEx, I have this set for the background:
wcex.hbrBackground = (HBRUSH)0;
Here is my handler for WM_PAINT message:
case WM_PAINT:
{
hdc = BeginPaint(hWnd, &ps);
Gdiplus::Graphics graphics(hdc);
graphics.DrawImage(*m_pBitmap, 0, 0);
EndPaint(hWnd, &ps);
break;
}
One thing to note is that the application is always docked to the left of the screen and doesn't move. But, what's underneath the application may change as the user opens, closes or moves windows under it.
When the application first starts, it looks perfect. The transparent (and simi-transparent) parts of the PNG show through perfectly. BUT, when the background underneath the application changes, the background DOESN'T update, it just stays the same from when the application first started. In fact, WM_PAINT (or WM_ERASEBKGND does not get called when the background changes).
I've been playing with this for quite a while and have gotten close to getting 100% right, but not quite there. For instance, I've tried setting the background to (HBRUSH) NULL_BRUSH and I've tried handling WM_ERASEBKGND.
What can be done to get the window to repaint when the contents under it changes?
I was able to do exactly what I wanted by using the code from Part 1 and Part 2 of this series:
Displaying a Splash Screen with C++
Part 1: Creating a HBITMAP archive
Part 2: Displaying the window archive
Those blog posts are talking about displaying a splash screen in Win32 C++, but it was almost identical to what I needed to do. I believe the part that I was missing was that instead of just painting the PNG to the window using GDI+, I needed to use the UpdateLayeredWindow function with the proper BLENDFUNCTION parameter. I'll paste the SetSplashImage method below, which can be found in Part 2 in the link above:
void SetSplashImage(HWND hwndSplash, HBITMAP hbmpSplash)
{
// get the size of the bitmap
BITMAP bm;
GetObject(hbmpSplash, sizeof(bm), &bm);
SIZE sizeSplash = { bm.bmWidth, bm.bmHeight };
// get the primary monitor's info
POINT ptZero = { 0 };
HMONITOR hmonPrimary = MonitorFromPoint(ptZero, MONITOR_DEFAULTTOPRIMARY);
MONITORINFO monitorinfo = { 0 };
monitorinfo.cbSize = sizeof(monitorinfo);
GetMonitorInfo(hmonPrimary, &monitorinfo);
// center the splash screen in the middle of the primary work area
const RECT & rcWork = monitorinfo.rcWork;
POINT ptOrigin;
ptOrigin.x = 0;
ptOrigin.y = rcWork.top + (rcWork.bottom - rcWork.top - sizeSplash.cy) / 2;
// create a memory DC holding the splash bitmap
HDC hdcScreen = GetDC(NULL);
HDC hdcMem = CreateCompatibleDC(hdcScreen);
HBITMAP hbmpOld = (HBITMAP) SelectObject(hdcMem, hbmpSplash);
// use the source image's alpha channel for blending
BLENDFUNCTION blend = { 0 };
blend.BlendOp = AC_SRC_OVER;
blend.SourceConstantAlpha = 255;
blend.AlphaFormat = AC_SRC_ALPHA;
// paint the window (in the right location) with the alpha-blended bitmap
UpdateLayeredWindow(hwndSplash, hdcScreen, &ptOrigin, &sizeSplash,
hdcMem, &ptZero, RGB(0, 0, 0), &blend, ULW_ALPHA);
// delete temporary objects
SelectObject(hdcMem, hbmpOld);
DeleteDC(hdcMem);
ReleaseDC(NULL, hdcScreen);
}
Use the SetLayeredWindowAttributesarchive function, this allows you to set a mask color that will become transparent, thus allowing the background to show through.
You will also need to configure your window with the layered flag, e.g.:
SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_LAYERED);
After that it's fairly simple:
// Make red pixels transparent:
SetLayeredWindowAttributes(hwnd, RGB(255,0,0), 0, LWA_COLORKEY);
When your PNG contains semi-transparent pixels that you want to blend with the background, this becomes more complicated. You could try looking at the approach in this CodeProject article:
Cool, Semi-transparent and Shaped Dialogs with Standard Controls for Windows 2000 and Above