Win32 transparent controls on all versions ofWindows - windows

I'm working on a Win32 GUI app using plain Win32 API (no MFC or .NET). The issue I'm having is making controls appear transparent. I've come up with a method which works for most things, in Windows Vista+ I do this in the WndProc:
case WM_CTLCOLORSTATIC:
{
SetBkMode((HDC)wParam, TRANSPARENT);
return (INT_PTR)::GetStockObject(NULL_PEN);
}
break;
In Windows XP, I do this in the WndProc:
case WM_CTLCOLORSTATIC:
{
HBRUSH hbr = (HBRUSH)DefWindowProc(hDlg, message, wParam, lParam);
::DeleteObject(hbr);
SetBkMode((HDC)wParam, TRANSPARENT);
return (LRESULT)(HBRUSH)(COLOR_WINDOW);
}
Now this works for most of the controls, however I get a transparent background on the label on the top of a group box control which draws the group box line through the text. I started working towards a case for just group boxes but I'm sure that this is a problem which must have been solved before and I don't want to go re-inventing the wheel.
Is there a tried and tested method for making controls appear transparent?
Thanks,
J

To achieve transparent controls you are going to have to be aware that:
You can't really. The standard windows controls just don't support "transparent" painting.
Even when you get it right, the dialog is going to flicker badly if you resize it.
The 'hacks' to get transparent painting of controls working tend to be different if theming is on or off, and change between windows versions.
Usually the goal of making controls "transparent" is so that a bitmap skin under the controls shows through. The way to achieve this kind of transparency is to create a bitmap for the background of the control. Then use CreatePatternBrush from the bitmap.
This chunk of DialogProc code implements the simplest skinning method possible and will then take care of painting both the background of the dialog, and most of the controls that support this form of painting:
// _hwnd is the dialogs handle
// _hbrSkin is a pattern brush handle
HWND hwndCtl;
POINT pt;
HDC hdc;
case WM_CTLCOLORDLG:
return (INT_PTR)_hbrSkin;
case WM_CTLCOLORSTATIC:
case WM_CTLCOLORBTN:
hdc = (HDC)wParam;
SetBkMode(hdc,TRANSPARENT); // Ensure that "static" text doesn't use a solid fill
pt.x = 0; pt.y = 0;
MapWindowPoints(hwndCtl,_hwnd,&pt,1);
SetBrushOrgEx(hdc,-pt.x,-pt.y,NULL);
return (INT_PTR)_hbrSkin;
Controls that overlap will draw incorrectly as one will paint its "transparent" background over the other. You can reduce the flicker by:
Not allowing the dialog to be resized.
setting the WS_EX_COMPOSITED style on the dialog, but as the Windows NT 6 DWM doesn't support it, its essentially useless from Vista on.
Setting the WS_CLIPCHILDREN style on the dialog & or WS_CLIPSIBLINGS - these styles prevent the use of group boxes and tab controls as they rely on controls overlapping.
subclassing all the controls, using the WM_PRINTCLIENT message to paint them to a backbuffer, then blitting the prepared backbuffer in one pass. Hard work and not all controls support WM_PRINTCLIENT.

Related

The correct method for drawing a bitmap image into a window

I have a function which takes a rectangular region of a bitmap image, rescales it to different dimensions, and draws it at some offset inside of a window within my dialog-box application:
void DrawImage(HANDLE hImageBitmap,
CDC* pDstDC,const CRect& dstRect,
CDC* pSrcDC,const CRect& srcRect)
{
pSrcDC->SelectObject(hImageBitmap);
pDstDC->SetStretchBltMode(HALFTONE);
pDstDC->StretchBlt
(
dstRect.left,dstRect.top,dstRect.Width(),dstRect.Height(),pSrcDC,
srcRect.left,srcRect.top,srcRect.Width(),srcRect.Height(),SRCCOPY
);
}
I create and maintain the window using a CWnd m_cImageWindow member variable.
I perform the drawing from the dialog-box's OnPaint handler as follows:
CDC* pDC = m_cImageWindow.GetDC();
CDC cDC;
cDC.CreateCompatibleDC(pDC);
CRect srcRect = ...;
CRect dstRect = ...;
DrawImage(m_hImageBitmap,pDC,dstRect,&cDC,srcRect);
cDC.DeleteDC();
m_cImageWindow.ReleaseDC(pDC);
I have two problems:
I see flickering whenever I change the drawing parameters. The standard way to solve this, from what I have read here and there, is by using a temporary DC for double-buffering. But as far as I understand, this is exactly what I am already doing.
If some of the destination region falls outside the window, it is painted over other controls within the dialog box. I am able to partially solve this by calling MoveWindow or SetWindowPos for each one of these controls. But I can still see the image flickering behind them. I have tried calling SetWindowPos in various different ways, hoping in vain that it would dictate a strict Z-order of the controls.
Thank you.
The painting of the image into the child window should be done in the WM_PAINT handler for that child window, not for the dialog. Your child window may need remember information provided by the parent dialog so that it can paint independently. By painting the window from the dialog's WM_PAINT handler, you're possibly painting more often than necessary (and possibly aren't causing a validation to occur in the image window).
The dialog should probably have the WS_CLIPCHILDREN window style and your image window should probably have WS_CLIPSIBLINGS. This will prevent the dialog controls from drawing over each other, and it can reduce flicker by allowing for more minimal updates.
If the image will always completely cover the entire image window, then you want to make sure there's no background erasing happening for the image window, as that can cause a flash of the background color which looks like painting. There are several ways to do this, but the easiest is probably to provide a WM_ERASEBKGND handler that just returns TRUE.
I found OnEraseBkgnd to be the right place to minimize flickering of drawn bitmaps.

How to Get TCanvas DC in Firemonkey?

What I need
I need to get the DC of a Firemonkey component's TCanvas. I need this to use Win API drawing functions not accessible through Firemonkey, mainly 100% control over font rendering.
Obviously, this is a pure Windows Application, so any compatibilities with OSX isn't an issue.
What I did
I managed to get hold of the TForm's handle and convert it into a HWND, then getting the DC with GetDC(FmxHandleToHWND(Handle));
This is the OnPaint handler for the Character_PaintBox control.
HWND hWND = FmxHandleToHWND(Handle);
HDC hDC = GetDC(hWND);
int x = PreviewBack_Rectangle->Position->X + Character_PaintBox->Position->X;
int y = PreviewBack_Rectangle->Position->Y + Character_PaintBox->Position->Y;
TextOut(hDC,x,y,L"Test",4);
ReleaseDC(hWND,hDC);
How ever this is the Form's DC and anything I write is overwritten at next update.
This was an easy task in VCL and it can't be that complicated in Firemonkey, or?
The problem is that with Firemonkey, you only have a single device context for the form and not one for each component. When a component needs to be redrawn, it gets passed the forms canvas but with clipping and co-ordinates mapped to the components location. As you already found, in Windows, you can get that context and draw on it at any time but you are then competing with the normal firemonkey painting which happens in the paint methods.
You can put a TImage on the form and do your custom drawing to that. Firemonkey will just keep redrawing the image when the form needs drawing.
I know you said you don't want MAC but for anyone else reading this, you can't get a graphics context on OSX and draw to it because the context isn't valid outside the paint method. So the image method would be the only way. This presumably explains why Firemonkey works with the single context.

WIN32 change the text insideTextOut

This will diplay the text on the screen with a TRANSPARENT BG and A colored text, but what if i want to change the
text later, how do i do? should i use: SendMessage(); or: SetWindowText( ) If yes, how and if
no, then what then??? and how
case WM_PAINT:
dc = BeginPaint(hwnd, &Ps);
SetBkMode(dc, TRANSPARENT);
SetTextColor(dc, RGB(454,0,0));
TextOut(dc, 10, 200, L"SEE? ", 5);
EndPaint(hwnd, &Ps);
break;
If you want to draw the text associated with your window - this is what you should do:
Don't call TextOut with the hard-coded string. Instead obtain it via GetWindowText.
Add a hander to WM_SETTEXT message. Upon receiving it - invalidate your window (or at least the area where the text is assumed to be drawn).
Now some explanations about transparency and etc.
I assume your window has an associated background brush (i.e. its WNDCLASS had non-zero hbrBackground member upon class registration). If not - you're painting a transparent text above a non-painted area, which may contain any junk.
During the call to BeginPaint your window procedure receives WM_ERASEBACKGROUND. Assuming you pass it to the DefWindowProc - the client area of your window will be filled by the background brush. So that every time you begin painting - the client are of your window will be filled by some brush. Then you draw your text transparently on the newly-filled background. So that no smearing should occur.
Whenever you want to change something visual on your window - drawing extra things in-place is not enough. Because at any time your window may be requested by the OS to redraw itself. So that your window must be able to paint itself adequately upon receiving WM_PAINT.
A common practice is to invalidate your window (or a part of it, using InvalidateRect or similar function) upon some change. And then, when you receive WM_PAINT - repaint your window.

Flashing in OnPaint

I have a children of CWind class, where I'm subscribing on OnPaint event:
BEGIN_MESSAGE_MAP(MyListBox, CWnd)
ON_WM_PAINT()
END_MESSAGE_MAP()
If in OnPaint handler I draw something simple in window, like border, all is good. But if I'll add Sleep(50);(by doing this I simulate some hard drawing operations) my window will be flashing. I cant understand why this happens... May be problem is that OnPaint function is called often, about 2-3 times in a sec.
Update: I am using double biffering: first I draw window content in PaintDeviceContent and then copy this DC to window's DC.
Update2: Here is code:
void CDirectionsListBox::OnPaint()
{
CRect rectClient;
GetClientRect(rectClient);
CPaintDC dc(this); // device context for painting
CDC DCMem;
DCMem.CreateCompatibleDC( &dc );
// Draw window here, workign with DCMem
dc.BitBlt(0, 0, rectClient.Width(), rectClient.Height(), &DCMem, 0, 0, SRCCOPY);
}
By default, the background is painted by clearing the client area using the background brush. You want to turn that off. Handle WM_ERASEBKGND and do nothing, since you're going to blit over the top of any existing image anyway.
Also see How to avoid flicker while handling WM_ERASEBKGND in Windows dialog
The reason your window is flashing is because it's trying to display things that have not finished drawing yet.
When drawing using MFC you'll need to manually double buffer the window you're drawing to if the drawing operation is non-trivial. In essence, what you want to do is to create a drawing context in-memory and draw to that instead. When the drawing is complete you copy what is found in your in memory context to the window context.

Making an owner-draw button transparent against its arbitrary background in WINCE

I am trying to place an owner-draw button transparently onto a background. I have no trouble doing this when the background is a solid colour but if the background is an image I cannot seem to get the correct HDC (handle to device context) to Bitblt() the area that button covers.
The HDC that is passed as part of the DRAWITEMSTRUCT gives me a button-default-grey area. If I attempt to get the parent of the HWND and then the device context of that i.e
pdc = GetDC(GetParent(hWnd));
then the background that gets BitBlt'd is the background of the last painted window.
I hope this question makes sense.
this is the code I have:
pdis = (LPDRAWITEMSTRUCT)(lParam);
hdc = pdis->hDC;
button = pdis->CtlID - IDC_BUTOFFSET;
//pdc = GetDC((hWnd));
pdc = GetDC(GetParent(hWnd));
hbm = CreateCompatibleBitmap(pdc, Buttons_[button]->bc.Size.cx, Buttons_[button]->bc.Size.cy);
SelectObject(hdc, hbm);
BitBlt(hdc, 0, 0, Buttons_[button]->bc.Size.cx, Buttons_[button]->bc.Size.cy,
pdc, Buttons_[button]->bc.Position.x, Buttons_[button]->bc.Position.y, SRCCOPY);
TIA
Best regards
Ends
It seems that what I have been doing is more or less correct. However Bitblt is copying the section of the window with the button already placed, so what I am in effect doing is copying the default button background and then overlaying that on top of the owner-draw button. A bit dumb but completely logical.
I hate it when computers do what you tell them to do and not what you want them to do. :P
Now I have to figure out how to copy the background as the ownerdraw function does not know what the window under the button is displaying at any given time...
Thanks to those who had a look at the question.
BTW:
pdc = GetDC(GetParent(hWnd)); // NOT CORRECT
pdc = GetDC(hWnd); // better

Resources