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

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

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.

Circular button using BM_SETIMAGE and SetWindowRgn

I'm attempting to create a circular push button. Here's my process so far:
Create BS_BITMAP style button:
hButton = CreateWindow(L"button",L"Label",WS_CHILD|WS_VISIBLE|BS_BITMAP,
122,363,65,65,hWnd,(HMENU)BUTTON_ID,NULL,NULL);
Load bitmap with LoadImage. The bitmap is a square, but I only want to display the circle in the center (more on this later):
buttonImage = (HBITMAP)LoadImage(hInstance,L"button.bmp",IMAGE_BITMAP,65,65,
LR_LOADFROMFILE|LR_CREATEDIBSECTION);
Set the button's image:
SendMessage(hButton,BM_SETIMAGE,IMAGE_BITMAP,(LPARAM)buttonImage);
In order to display just the circle, I use the following:
hButtonRgn = CreateEllipticRgn(0,0,65,65);
SetWindowRgn(hButton,hButtonRgn,TRUE);
Note that I define hButtonRgn globally and don't use it again, as the MSDN documentation for SetWindowRgn states that "the system owns the region specified by the region handle hRgn".
Here's the problem:
The button initially appears as only a circle. On being clicked and held, though, the full square bitmap appears, with white space surrounding the circle. However, upon release, only the circle appears again.
Here's my attempted solution:
As soon as the button is clicked, repaint the main window around the button. Within the main window's WndProc, then, I do the following:
case WM_PARENTNOTIFY:
if ((int)wParam == WM_LBUTTONDOWN)
{
PAINTSTRUCT ps;
BeginPaint(hWnd, &ps);
pRenderTarget->BeginDraw();
// paint the background surrounding the button in another function
pRenderTarget->EndDraw();
EndPaint(hWnd, &ps);
}
However, this has no discernible effect. The button appears circular after releasing the mouse, but appears as a square when the mouse is being held down.
Any ideas on where I've gone wrong?
Your redraw routine is incorrect. BeginPaint() should only be used in response to a WM_PAINT message - it tells you that something is dirty and needs repainting. What you want to do is trigger that mechanism, and the way you do that is to use InvalidateRect() to mark the appropriate area of the parent window for redraw.
Even if this does improve things for you I think you'll end up with flickering which will probably be unacceptable. Another mechanism you could investigate is making the button owner draw, because then you can just draw it as a circle (and invalidate the parent window) in the same step.

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.

Win32 transparent controls on all versions ofWindows

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.

Resources