Why does CreateDIBSection() fail when the window is offscreen? - delphi

I'm building a Delphi component to embed an SDL rendering surface on a VCL form. It works just fine as long as the form is on-screen at the moment that the SDL surface is created. Otherwise, it's not able to create any rendering textures.
I traced into the SDL code and ended up with the following function call, which fails (returns NULL):
data->hbm = CreateDIBSection(renderdata->memory_hdc, bmi, DIB_RGB_COLORS, &data->pixels, NULL, 0);
The HDC is a valid handle to the drawing context owned by a control that has its own HWND window handle, that's been set up properly. But when the control is created offscreen, which commonly happens in Delphi, (all forms, with their controls, are created in a hidden state until it's time to display them,) the CreateDIBSection call will fail until the control is actually visible.
Also, if it's created onscreen, then hidden and re-shown (if it's on a tab sheet and I switch tabs, for example,) any textures I create get blanked during this process.
This is driving me nuts. Anyone know what's going on and how I can work around it?

The drawing context is only valid during Paint processing.

Related

Delphi, Firemonkey - How to draw on component canvas outside of paint

I am writing my first component for Firemonkey. It is very dynamic control and to keep refresh times minimal, when a property changes, I only want to modify the effected attributes rather than repaint the entire control.
The first issue I found was that unless you are in the Paint loop, you need to call Canvas.SetMatrix(AbsoluteMatrix) first otherwise canvas functions are referenced to the parents coordinates. I don't quite understand this.
The second issued is that when use this control on OSX, unless I call the inherited paint procedure (which I override) nothing changes on the canvas gets displayed. This works fine in Win32
Component is based off of TControl
You might not like this, but you're not supposed to paint outside of a paint event. So don't do it. Windows is a bit more forgiving if you break that rule, but you shouldn't do it on Windows either. For example, if your window is (partly) hidden, no updates are needed and the OS will skip the paint event. So instead of trying to work against the OS it is better to work with it. And usually there is a better alternative.
You can keep an internal "cache" bitmap and update that as needed. Then when the paint event comes, you can draw this entire bitmap. If possible, update this cached bitmap in the paint event if it needs changing.
If you want to temporarily highlight items, you can have a transparent window on top and paint on that window. Let the OS window manager do the heavy work for you.

Get location of the control after hooking to Drawtext function

I have a VCL application that i'm testing but don't have the source code.
I need to validate what text was drawn to the labels on the screen but the labels are not a window so I create a hook to the drawtext function and i can get the text that was drawn to the screen.
But i cant validate where on the screen that text was drawn as the function gets hdc and a point where to draw the text in the context.
I need to figure where on the screen that hdc is located and make sure the the label is correct.
Is there a way to do it?
Managed unmanaged dose not matter to me.
You could build a DLL in Delphi and inject that DLL in the target process. As your are happily hooking Win32 APis, I suppose you will not have problems with that.
In that DLL, you could find (using Win32 APIs) the HWND which is the "parent" of the Label.
With that HWND, you can find the associated TWinControl. For that you must dive into the VCL source code.
For Delphi 4 (yes, I know, it's old) you have to build an Atom String, use GlobalFindAtom, and then GetProp. The result is a pointer to the TWinControl.
For Delphi7 (old, too) you have to use RegisterWindowMessage with a string made up off "ControlOfs" followed by the module handle and the thread ID. The LRESULT of a SendMessage is then a pointer to the TWinControl.
Sorry, I don't know for other Delphi versions, but all should be findable in the VCL sources.
Once you have the TWinControl pointer, you can enumerate the children as TComponent, and get their Name's (as they appear in the source code you don't have...), their ClassName's, and so on, you get the idea.
I think you would need to do something like this:
Call WindowFromDC to find out the window that hosts the label. As you know, a Delphi label is non-windowed and the control actually paints on its parent window. This may fail if the parent is double buffered. In that case you've no hope of getting the window handle from the device context since all you have is the device context of a bitmap.
Now that you have the window on which the label paints, you need to find out the location of the label on the window. The VCL calls SetWindowOrgEx to arrange that the device context has logical coordinates 0,0 at the top-left of the label. So you can call GetWindowOrgEx to find out the location of the label relative to the parent.
Now you know the location of the label relative to the parent, and the window handle of the parent, you can work out where the text is being drawn on screen with ClientToScreen.
Since device contexts only have meaning in the process that owns them, you'd need to inject into the target process to call this.

Delphi, chromium, tabs and flash trouble

So I have this project in which I need to have few tabs (TPageControl) with flash pages running and grab a screenshot of them from time to time without switching active tab.
First I tried TChromiumOSR but the flash is not visible in it, kind of works if I click on a page's button that opens a popup with flash maximized. so it doesn't look like a problem with flash but rather with frames or sth. anywas a no-go.
then I've tried TChromium, everything works alright but I don't know how to grab a screenshot of a control that is on non-active TTabSheet without switching tabs.
using latest dcef3 trunk
any ideas? thanks
edit: It turned out that flash not being visible in OSR is an old chromium bug. It's actually playing but not visible if embedding code has "wmode" set to something else than 'transparent'. I've managed to change that property through js. To actually see the change, flash object needs to be removed from DOM and added again:
chr1.browser.GetFrame(str[i]).ExecuteJavaScript('document.getElementsByName("wmode")[0].value="transparent";', '', 0);
chr1.browser.GetFrame(str[i]).ExecuteJavaScript('document.getElementsByName("wmode")[1].value="transparent";', '', 0);
chr1.browser.GetFrame(str[i]).ExecuteJavaScript('var wtf = document.getElementById("gameApp2");', '', 0);
chr1.browser.GetFrame(str[i]).ExecuteJavaScript('document.getElementById("gameApp2").remove();', '', 0);
chr1.browser.GetFrame(str[i]).ExecuteJavaScript('document.body.appendChild(wtf);', '', 0);
If the tab is not being shown (it's not the front one), it doesn't get drawn (this is an operating system thing). If it's not being drawn, there's nothing there for a screenshot to capture.
If your requirement is to capture a Flash page without it being displayed, you're going to have to find another way to do it.
Assuming your Chrome is still based on WebKit, I know WebKitGTK has its own snapshot engine, if it carries down deep enough, perhaps you can tap into that for your captures.
At one point, there was a behavioural issue with TPageControl that until a tab had been selected, none of its children had valid handles created. I do not know if that is still the case, but if it is, it may complicate your desire to not switch tabs.
Assuming the control HAS a valid window handle, you might create a TBITMAP, lock its canvas, grab the Canvas's handle (DC) and fake a WM_PAINT to the control to get it to render to your bitmap.

How to make my Deskband's (Taskbar Toolbar) Form transparent

I'm working on a Windows Deskband in Delphi XE2 for Windows XP, Vista and 7 (Win32 and Win64)...
I've implemented all the necessary interfaces (ITrayDeskBand, IDeskBandInfo, IDeskBand2, IDeskBand) in my code, and that all works exactly as it should (there are no warnings on Vista/7 complaining about compatibility as others have experienced).
The problem I have is that my Deskband Form appears with a non-transparent band. Also, only certain Controls are displaying (in this case TBitBtn and TImage containing a PNG). I need it to display TEdit and TComboBox objects properly too, but they won't appear at all.
I've tried enabling GlassFrame and SheetOfGlass properties on my Form, but this doesn't help one bit.
Furthermore, the Form itself is exceeding the top boundary of the Taskbar, meaning you cannot (for example) resize the Taskbar if the cursor is in-line with the top of the Taskbar immediately above my Deskband.
I believe there is something Delphi's VCL TForm type is doing behind the scenes which renders the TForm type incompatible as a Deskband container... but this is just a suspicion.
Here's a screenshot illustrating the various problems:
As you can see (above), the Deskband's Form is pale (instead of Transparent), it overlaps the top of the Taskbar (preventing resizing and Autohide triggering when the Taskbar is "hidden")
Any ideas?
UPDATE 1
Okay, I have been playing around and noticed that a totally different behaviour is observed when creating a TToolBar control to be used for the Deskband, rather than a form:
Notice there are three TToolButton controls (with their text virtually invisible due to the Glass theme)? There should also be a TEdit and TComboBox between two separators, but these refuse to display at all.
Also notice the artefacting (the repetition of actual Taskbar Icons)?
I'm not sure if this is a step in the right direction or not, but it might help you (or others) to deduce a solution!
Okay... I've finally figured this out, and it is the most absurd thing I've ever come across.
I'm posting my findings here for the benefit of others (to save you going through the nuisance I've just been through).
To get all of the controls on your Deskband Form to display and function properly, simply set the Visible property of your Form (in the IDE designer) to True.
Ridiculous, I know, but it works and is easily repeatable.

Drawing and clearing the desktop canvas with Delphi

I'm trying to draw over the whole screen by using the desktop canvas and painting to it directly. The problem is I can't clear that desktop canvas. I've tried setting the canvas.pen.style to psNotXOR and draw over the old image but unfortunately, this is not reliable enough and some left overs are still present in some conditions.
My need is to draw a selection rectangle around a window / control when the mouse is over it.
You don't write on what OS you have problems with the artefacts after clearing.
At least with desktop composition activated it is a very bad idea to draw directly to the desktop and to do XOR painting (see "Drawing To and Reading From the Screen -- Baaaad!" in this blog post). Apart from the negative performance implications you can't be sure what other painting happens at the same time and what effects and animations alter the displayed content, so a simple XOR may not be enough to completely erase everything.
One possible way to implement it would be a transparent overlay window of desktop size, and to draw your rubber band selector over that. Invalidating the whole window if the size changes should be enough, no need to erase the old selection line. If the overlay is removed the line will be gone too. Desktop composition will make sure that no flicker occurs. However, switching applications while selecting an area will be problematic, you need to catch this and immediately cancel the selection.
Edit:
I just tested it with Delphi 2009, and with the following test app:
a form with FormStyle set to fsStayOnTop and with AlphaBlend set to True
with an overridden CreateParams() method to add the WS_EX_TRANSPARENT extended style flag
I can pass all mouse clicks through to the underlying windows while being able to draw into a window on top of them. This should get you started.
I've done some stuff like this in the past and I've never come up with an acceptable solution.
Having a think about it though you could send the desktop HWND an invalidate command.
Because the desktop is a modified listview control you should able to use something like
procedure InvalidateDesktop;
begin
RedrawWindow(GetDesktopWindow, 0, 0, RDW_INVALIDATE);
//if that doesn't work then try this:
//Sendmessage(GetDesktopWindow, wm_setredraw, 1, 0);
end;
That might do what you want, I haven't tested it as I've just knocked it up for the example.
the problem is that Windows won't return me the control behind the mouse
I think you need to hook the mouse event messages for this - the hooked message then gives you the window handle that the mouse is over.
Look up SetWindowsHookEx(WH_MOUSE,,,) and MOUSEHOOKSTRUCT.
This is how we do it in our app:
FBitmap.Canvas.Brush.Color := clWhite;
FBitmap.Canvas.FillRect(FBitmap.Canvas.ClipRect);

Resources