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
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.
I am trying to make a radio button control with a transparent background using only Win32 when themes are enabled. The reason for doing this is to allow a radio button to be placed over an image and have the image show (rather than the grey default control background).
What happens out of the box is that the control will have the grey default control background and the standard method of changing this by handling either WM_CTLCOLORSTATIC or WM_CTLCOLORBTN as shown below does not work:
case WM_CTLCOLORSTATIC:
hdcStatic = (HDC)wParam;
SetTextColor(hdcStatic, RGB(0,0,0));
SetBkMode(hdcStatic,TRANSPARENT);
return (LRESULT)GetStockObject(NULL_BRUSH);
break;
My research so far indicates that Owner Draw is the only way to achieve this. I've managed to get most of the way with an Owner Draw radio button - with the code below I have a radio button and a transparent background (the background is set in WM_CTLCOLORBTN). However, the edges of the radio check are cut off using this method - I can get them back by uncommenting the call to the function DrawThemeParentBackgroundEx but this breaks the transparency.
void DrawRadioControl(HWND hwnd, HTHEME hTheme, HDC dc, bool checked, RECT rcItem)
{
if (hTheme)
{
static const int cb_size = 13;
RECT bgRect, textRect;
HFONT font = (HFONT)SendMessageW(hwnd, WM_GETFONT, 0, 0);
WCHAR *text = L"Experiment";
DWORD state = ((checked) ? RBS_CHECKEDNORMAL : RBS_UNCHECKEDNORMAL) | ((bMouseOverButton) ? RBS_HOT : 0);
GetClientRect(hwnd, &bgRect);
GetThemeBackgroundContentRect(hTheme, dc, BP_RADIOBUTTON, state, &bgRect, &textRect);
DWORD dtFlags = DT_VCENTER | DT_SINGLELINE;
if (dtFlags & DT_SINGLELINE) /* Center the checkbox / radio button to the text. */
bgRect.top = bgRect.top + (textRect.bottom - textRect.top - cb_size) / 2;
/* adjust for the check/radio marker */
bgRect.bottom = bgRect.top + cb_size;
bgRect.right = bgRect.left + cb_size;
textRect.left = bgRect.right + 6;
//Uncommenting this line will fix the button corners but breaks transparency
//DrawThemeParentBackgroundEx(hwnd, dc, DTPB_USECTLCOLORSTATIC, NULL);
DrawThemeBackground(hTheme, dc, BP_RADIOBUTTON, state, &bgRect, NULL);
if (text)
{
DrawThemeText(hTheme, dc, BP_RADIOBUTTON, state, text, lstrlenW(text), dtFlags, 0, &textRect);
}
}
else
{
// Code for rendering the radio when themes are not present
}
}
The method above is called from WM_DRAWITEM as shown below:
case WM_DRAWITEM:
{
LPDRAWITEMSTRUCT pDIS = (LPDRAWITEMSTRUCT)lParam;
hTheme = OpenThemeData(hDlg, L"BUTTON");
HDC dc = pDIS->hDC;
wchar_t sCaption[100];
GetWindowText(GetDlgItem(hDlg, pDIS->CtlID), sCaption, 100);
std::wstring staticText(sCaption);
DrawRadioControl(pDIS->hwndItem, hTheme, dc, radio_group.IsButtonChecked(pDIS->CtlID), pDIS->rcItem, staticText);
SetBkMode(dc, TRANSPARENT);
SetTextColor(hdcStatic, RGB(0,0,0));
return TRUE;
}
So my question is two parts I suppose:
Have I missed some other way to achieve my desired result?
Is it possible to fix the clipped button corners issue with my code and still have a transparent background
After looking at this on and off for nearly three months I've finally found a solution that I'm pleased with. What I eventually found was that the radio button edges were for some reason not being drawn by the routine within WM_DRAWITEM but that if I invalidated the radio button control's parent in a rectangle around the control, they appeared.
Since I could not find a single good example of this I'm providing the full code (in my own solution I have encapsulated my owner drawn controls into their own class, so you will need to provide some details such as whether the button is checked or not)
This is the creation of the radiobutton (adding it to the parent window) also setting GWL_UserData and subclassing the radiobutton:
HWND hWndControl = CreateWindow( _T("BUTTON"), caption, WS_CHILD | WS_VISIBLE | BS_OWNERDRAW,
xPos, yPos, width, height, parentHwnd, (HMENU) id, NULL, NULL);
// Using SetWindowLong and GWL_USERDATA I pass in the this reference, allowing my
// window proc toknow about the control state such as if it is selected
SetWindowLong( hWndControl, GWL_USERDATA, (LONG)this);
// And subclass the control - the WndProc is shown later
SetWindowSubclass(hWndControl, OwnerDrawControl::WndProc, 0, 0);
Since it is owner draw we need to handle the WM_DRAWITEM message in the parent window proc.
case WM_DRAWITEM:
{
LPDRAWITEMSTRUCT pDIS = (LPDRAWITEMSTRUCT)lParam;
hTheme = OpenThemeData(hDlg, L"BUTTON");
HDC dc = pDIS->hDC;
wchar_t sCaption[100];
GetWindowText(GetDlgItem(hDlg, pDIS->CtlID), sCaption, 100);
std::wstring staticText(sCaption);
// Controller here passes to a class that holds a map of all controls
// which then passes on to the correct instance of my owner draw class
// which has the drawing code I show below
controller->DrawControl(pDIS->hwndItem, hTheme, dc, pDIS->rcItem,
staticText, pDIS->CtlID, pDIS->itemState, pDIS->itemAction);
SetBkMode(dc, TRANSPARENT);
SetTextColor(hdcStatic, RGB(0,0,0));
CloseThemeData(hTheme);
return TRUE;
}
Here is the DrawControl method - it has access to class level variables to allow state to be managed since with owner draw this is not handled automatically.
void OwnerDrawControl::DrawControl(HWND hwnd, HTHEME hTheme, HDC dc, bool checked, RECT rcItem, std::wstring caption, int ctrlId, UINT item_state, UINT item_action)
{
// Check if we need to draw themed data
if (hTheme)
{
HWND parent = GetParent(hwnd);
static const int cb_size = 13;
RECT bgRect, textRect;
HFONT font = (HFONT)SendMessageW(hwnd, WM_GETFONT, 0, 0);
DWORD state;
// This method handles both radio buttons and checkboxes - the enums here
// are part of my own code, not Windows enums.
// We also have hot tracking - this is shown in the window subclass later
if (Type() == RADIO_BUTTON)
state = ((checked) ? RBS_CHECKEDNORMAL : RBS_UNCHECKEDNORMAL) | ((is_hot_) ? RBS_HOT : 0);
else if (Type() == CHECK_BOX)
state = ((checked) ? CBS_CHECKEDNORMAL : CBS_UNCHECKEDNORMAL) | ((is_hot_) ? RBS_HOT : 0);
GetClientRect(hwnd, &bgRect);
// the theme type is either BP_RADIOBUTTON or BP_CHECKBOX where these are Windows enums
DWORD theme_type = ThemeType();
GetThemeBackgroundContentRect(hTheme, dc, theme_type, state, &bgRect, &textRect);
DWORD dtFlags = DT_VCENTER | DT_SINGLELINE;
if (dtFlags & DT_SINGLELINE) /* Center the checkbox / radio button to the text. */
bgRect.top = bgRect.top + (textRect.bottom - textRect.top - cb_size) / 2;
/* adjust for the check/radio marker */
// The +3 and +6 are a slight fudge to allow the focus rectangle to show correctly
bgRect.bottom = bgRect.top + cb_size;
bgRect.left += 3;
bgRect.right = bgRect.left + cb_size;
textRect.left = bgRect.right + 6;
DrawThemeBackground(hTheme, dc, theme_type, state, &bgRect, NULL);
DrawThemeText(hTheme, dc, theme_type, state, caption.c_str(), lstrlenW(caption.c_str()), dtFlags, 0, &textRect);
// Draw Focus Rectangle - I still don't really like this, it draw on the parent
// mainly to work around the way DrawFocus toggles the focus rect on and off.
// That coupled with some of my other drawing meant this was the only way I found
// to get a reliable focus effect.
BOOL bODAEntire = (item_action & ODA_DRAWENTIRE);
BOOL bIsFocused = (item_state & ODS_FOCUS);
BOOL bDrawFocusRect = !(item_state & ODS_NOFOCUSRECT);
if (bIsFocused && bDrawFocusRect)
{
if ((!bODAEntire))
{
HDC pdc = GetDC(parent);
RECT prc = GetMappedRectanglePos(hwnd, parent);
DrawFocus(pdc, prc);
}
}
}
// This handles drawing when we don't have themes
else
{
TEXTMETRIC tm;
GetTextMetrics(dc, &tm);
RECT rect = { rcItem.left ,
rcItem.top ,
rcItem.left + tm.tmHeight - 1,
rcItem.top + tm.tmHeight - 1};
DWORD state = ((checked) ? DFCS_CHECKED : 0 );
if (Type() == RADIO_BUTTON)
DrawFrameControl(dc, &rect, DFC_BUTTON, DFCS_BUTTONRADIO | state);
else if (Type() == CHECK_BOX)
DrawFrameControl(dc, &rect, DFC_BUTTON, DFCS_BUTTONCHECK | state);
RECT textRect = rcItem;
textRect.left = rcItem.left + 19;
SetTextColor(dc, ::GetSysColor(COLOR_BTNTEXT));
SetBkColor(dc, ::GetSysColor(COLOR_BTNFACE));
DrawText(dc, caption.c_str(), -1, &textRect, DT_WORDBREAK | DT_TOP);
}
}
Next is the window proc that is used to subclass the radio button control - this
is called with all windows messages and handles several before then passing unhandled
ones on to the default proc.
LRESULT OwnerDrawControl::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam,
LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
// Get the button parent window
HWND parent = GetParent(hWnd);
// The page controller and the OwnerDrawControl hold some information we need to draw
// correctly, such as if the control is already set hot.
st_mini::IPageController * controller = GetWinLong<st_mini::IPageController *> (parent);
// Get the control
OwnerDrawControl *ctrl = (OwnerDrawControl*)GetWindowLong(hWnd, GWL_USERDATA);
switch (uMsg)
{
case WM_LBUTTONDOWN:
if (controller)
{
int ctrlId = GetDlgCtrlID(hWnd);
// OnCommand is where the logic for things like selecting a radiobutton
// and deselecting the rest of the group lives.
// We also call our Invalidate method there, which redraws the radio when
// it is selected. The Invalidate method will be shown last.
controller->OnCommand(parent, ctrlId, 0);
return (0);
}
break;
case WM_LBUTTONDBLCLK:
// We just treat doubleclicks as clicks
PostMessage(hWnd, WM_LBUTTONDOWN, wParam, lParam);
break;
case WM_MOUSEMOVE:
{
if (controller)
{
// This is our hot tracking allowing us to paint the control
// correctly when the mouse is over it - it sets flags that get
// used by the above DrawControl method
if(!ctrl->IsHot())
{
ctrl->SetHot(true);
// We invalidate to repaint
ctrl->InvalidateControl();
// Track the mouse event - without this the mouse leave message is not sent
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(TRACKMOUSEEVENT);
tme.dwFlags = TME_LEAVE;
tme.hwndTrack = hWnd;
TrackMouseEvent(&tme);
}
}
return (0);
}
break;
case WM_MOUSELEAVE:
{
if (controller)
{
// Turn off the hot display on the radio
if(ctrl->IsHot())
{
ctrl->SetHot(false);
ctrl->InvalidateControl();
}
}
return (0);
}
case WM_SETFOCUS:
{
ctrl->InvalidateControl();
}
case WM_KILLFOCUS:
{
RECT rcItem;
GetClientRect(hWnd, &rcItem);
HDC dc = GetDC(parent);
RECT prc = GetMappedRectanglePos(hWnd, parent);
DrawFocus(dc, prc);
return (0);
}
case WM_ERASEBKGND:
return 1;
}
// Any messages we don't process must be passed onto the original window function
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
Finally the last little piece of the puzzle is that you need to invalidate the control (redraw it) at the right times. I eventually found that invalidating the parent allowed the drawing to work 100% correctly. This was causing flicker until I realised that I could get away by only invalidating a rectangle as big as the radio check, rather than as big as the whole control including text as I had been.
void InvalidateControl()
{
// GetMappedRectanglePos is my own helper that uses MapWindowPoints
// to take a child control and map it to its parent
RECT rc = GetMappedRectanglePos(ctrl_, parent_);
// This was my first go, that caused flicker
// InvalidateRect(parent_, &rc_, FALSE);
// Now I invalidate a smaller rectangle
rc.right = rc.left + 13;
InvalidateRect(parent_, &rc, FALSE);
}
A lot of code and effort for something that should be simple - drawing a themed radio button over a background image. Hopefully the answer will save someone else some pain!
* One big caveat with this is it only works 100% correctly for owner controls that are over a background (such as a fill rectangle or an image). That is ok though, since it is only needed when drawing the radio control over a background.
I've done this some time ago as well. I remember the key was to just create the (radio) buttons as usual. The parent must be the dialog or window, not a tab control. You could do it differently but I created a memory dc (m_mdc) for the dialog and painted the background on that. Then add the OnCtlColorStatic and OnCtlColorBtn for your dialog:
virtual HBRUSH OnCtlColorStatic(HDC hDC, HWND hWnd)
{
RECT rc;
GetRelativeClientRect(hWnd, m_hWnd, &rc);
BitBlt(hDC, 0, 0, rc.right - rc.left, rc.bottom - rc.top, m_mdc, rc.left, rc.top, SRCCOPY);
SetBkColor(hDC, GetSysColor(COLOR_BTNFACE));
if (IsAppThemed())
SetBkMode(hDC, TRANSPARENT);
return (HBRUSH)GetStockObject(NULL_BRUSH);
}
virtual HBRUSH OnCtlColorBtn(HDC hDC, HWND hWnd)
{
return OnCtlColorStatic(hDC, hWnd);
}
The code uses some in-house classes and functions similar to MFC, but I think you should get the idea. As you can see it draws the background of these controls from the memory dc, that's key.
Give this a try and see if it works!
EDIT: If you add a tab control to the dialog and put the controls on the tab (that was the case in my app) you must capture it's background and copy it to the memory dc of the dialog. It's a bit of an ugly hack but it works, even if the machine is running some extravagant theme that uses a gradient tab background:
// calculate tab dispay area
RECT rc;
GetClientRect(m_tabControl, &rc);
m_tabControl.AdjustRect(false, &rc);
RECT rc2;
GetRelativeClientRect(m_tabControl, m_hWnd, &rc2);
rc.left += rc2.left;
rc.right += rc2.left;
rc.top += rc2.top;
rc.bottom += rc2.top;
// copy that area to background
HRGN hRgn = CreateRectRgnIndirect(&rc);
GetRelativeClientRect(m_hWnd, m_tabControl, &rc);
SetWindowOrgEx(m_mdc, rc.left, rc.top, NULL);
SelectClipRgn(m_mdc, hRgn);
SendMessage(m_tabControl, WM_PRINTCLIENT, (WPARAM)(HDC)m_mdc, PRF_CLIENT);
SelectClipRgn(m_mdc, NULL);
SetWindowOrgEx(m_mdc, 0, 0, NULL);
DeleteObject(hRgn);
Another interesting point, while we're busy now, to get it all non-flickering create the parent and children (buttons, statics, tabs etc) with the WS_CLIPCHILDREN and WS_CLIPSIBLINGS style. The the order of creation is essential: First create the controls you put on the tabs, then create the tab control. Not the other way around (although it feels more intuitive). That because the tab control should clip the area obscured by the controls on it :)
I can't immediately try this out, but so far as I recall, you don't need owner draw. You need to do this:
Return 1 from WM_ERASEBKGND.
Call DrawThemeParentBackground from WM_CTLCOLORSTATIC to draw the background there.
Return GetStockObject(NULL_BRUSH) from WM_CTLCOLORSTATIC.
Knowing the sizes and coordinates radio button, we will copy the
image to them closed.
Then we create a brush by means of
BS_PATTERN style CreateBrushIndirect
Farther according to the
usual scheme - we return handle to this brush in reply to COLOR -
the message (WM_CTLCOLORSTATIC).
I have no idea why you are doing it so difficult, this is best solved via CustomDrawing
This is my MFC Handler to draw a Notebook on a CTabCtrl control. I'm not really sure why i need to Inflate the Rectangle, because if i don't do it a black border is drawn.
And another conceptional bug MS made is IMHO that i have to overwrite the PreErase drawing phase instead of the PostErase. But if i do the later the checkbox is gone.
afx_msg void AguiRadioButton::OnCustomDraw(NMHDR* notify, LRESULT* res) {
NMCUSTOMDRAW* cd = (NMCUSTOMDRAW*)notify;
if (cd->dwDrawStage == CDDS_PREERASE) {
HTHEME theme = OpenThemeData(m_hWnd, L"Button");
CRect r = cd->rc; r.InflateRect(1,1,1,1);
DrawThemeBackground(theme, cd->hdc, TABP_BODY, 0, &r,NULL);
CloseThemeData(theme);
*res = 0;
}
*res = 0;
}
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