How to Get TCanvas DC in Firemonkey? - winapi

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.

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.

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.

How to draw outside a window?

Looking at a Windows tooltips class hint window, i see that it draws its drop-shadow outside the hint window's actual rectangle.
Using SpyXX - i can get the tooltip's window rect, and class styles:
Rectangle: (440, 229)-(544, 249), 104x20
Restored Rect: (440, 229)-(544, 249), 104x20
Client Rect: (0, 0)-(104, 20), 104x20
You'll notice that the drop shadow you see is physically outside the window that's being drawn. How can i draw a shadow outside around my window, while being outside my window?
Note: The shadow is not drawn using the standard CS_DROPSHADOW class style. i've confirmed this experimentally, and can also see the class style's for the window in SpyXX; it does not use CS_DROPSHADOW:
Windows Styles: 94000001
WS_POPUP 80000000
WS_VISIBLE 10000000
WS_CLIPSIBLINGS 4000000
TTS_ALWAYSTIP 1
Extended Styles: 00080088
WS_EX_LAYERED 80000
WS_EX_TOOLWIN 80
WS_EX_TOPMOST 8
So how can i draw outside my window?
Note: Trying to draw on the desktop DC is out. From Greg Schechter's Redirecting GDI, DirectX, and WPF applications:
Drawing To and Reading From the Screen
-- Baaaad!
Lastly, since we're on the redirection
topic, one particularly dangerous
practice is writing to the screen,
either through the use of GetDC(NULL)
and writing to that, or attempting to
do XOR rubber-band lines, etc. There
are two big reasons that writing to
the screen is bad:
It's expensive... writing to the
screen itself isn't expensive, but it
is almost always accompanied by
reading from the screen because one
typically does read-modify-write
operations like XOR when writing to
the screen. Reading from the video
memory surface is very expensive,
requires synchronization with the DWM,
and stalls the entire GPU pipe, as
well as the DWM application pipe.
It's unpredictable... if you somehow
manage to get to the actual primary
and write to it, there can be no
predictability as to how long what you
wrote to the primary will remain on
screen. Since the UCE doesn't know
about it, it may get cleared in the
next frame refresh, or it may persist
for a very long time, depending on
what else needs to be updated on the
screen. (We really don't allow direct
writing to the primary anyhow, for
that very reason... if you try to
access the DirectDraw primary, for
instance, the DWM will turn off until
the accessing application exits)
You can't draw outside your window in the manner you describe.
If you right click your desktop then go to properties/appearance/effects and uncheck 'Show shadows under menus' ... you will no longer have the shadow.
Bottom line is that this is a product of the window manager not your program.
Q: How do you draw outside of one window? A: Draw inside another window!
First thing to note is that the tooltip class actually does use the CS_DROPSHADOW style - but note that this is a class style, not a window style, so you have to look at the Class tab in the Spy++ properties dialog to find it. You'll see that the tooltips_class32 windows does indeed have this - and a few others.
But that just leads to the next question - how does that work? Well, it seems that Windows implements this by creating a helper HWND to draw the shadow - presumably it's creating another popup window the same size and shape as the one it's shadowing, filling it with gray, placing it directly underneath the main window, and setting it as a WS_EX_LAYERED window so that the shadow can be transparent and fade out around the edges using alpha-blending. And there's nothing to stop you from using the same or similar techniques yourself if you want to add a different type of shadow effect to one of your own windows.
So, long story short: if you want to draw outside of your own window, create a helper transparent window in the general area that you want to draw on, and draw on that helper window instead.
--
Now, if you try to find one of these helper shadow windows in Spy++, you won't find much. Unlike the tooltip_class32 windows, which are long-lived and just hide/show themselves as needed, these shadow windows are a more elusive creature: they are only created for as long as needed, so you'd have to refresh Spy++ while there's a tooltip or popup menu or other window using the shadow present - and that's tricky, since most tooltips and menus will disappear as soon as you move the mouse to switch to Spy++. But it turns out that the tooltips on Spy++'s own toolbar will stick around: so start Spy++, hover over an item in the toolbar, and hit F5 to refresh the HWND tree while the tooltip and shadow are present. Now scroll down, and you should see the third and fourth visible HWNDs in the tree are the tooltip itself, and right after that, a SysShadow window. Unfortunately, since the tooltip and shadow have by now disappeared, if you attempt to get the properties dialog for that HWND, you'll get a get a blank property dialog with an 'Invalid Window' message. If you really want to poke around and see how that SysShadow works, what styles it itself uses and so on, you could create a target app with a long-lived popup that uses CS_DROPSHADOW that you can then explore in Spy++ at leisure.
(Finally, note that these shadows are a completely different thing than the shadows that you see when one app window is on top of another above another since Vista: this type of shadow is part of Aero Glass mode, and handled by the same Desktop Composition Manager that adds the glass titlebar effect, and it doesn't use or need helper windows to implement the shadows.)
I wouldn't be surprised if that shadow is intimately tied to the window manager itself; it is after all the window manager who decides what window gets to paint which parts of itself and when it can do it. I don't see it as rocket science to paint that shadow if control over all that is gained, which the window manager has.

Resources