Drawing and clearing the desktop canvas with Delphi - 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);

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.

How can I make a form visible and maximize it to fill a secondary monitor without it activating?

I have an application which is designed for multiple monitors. It starts up, and we try to avoid activating windows that do not need to be activated, because the user only does keyboard input in one place, and each time we Activate a new form on a secondary monitor, it grabs keyboard focus, something we wish to avoid.
Our in-house base TForm class has a method like this, which is using the Win32 ShowWindow function directly, avoiding the VCL framework's internal visibility change system which grabs focus:
procedure TOurForm.ShowWithoutActivate;
begin
ShowWindow(Self.Handle, SW_SHOWNOACTIVATE);
Self.Visible := true;
end;
If I just did this, it would grab focus:
Self.Visible := true; // TWindow.Visible = true, will grab focus, plus make window visible.
This works, but the next thing I'd like to be able to do is set Maximized state so that the
form will maximize itself on the Monitor that it is currently on. How do we get it onto a particular monitor? The same way it always worked, with modification of the Left and Top properties of the Form. We have to take care that if we store Left/Top/Width/Height on form, and then restore it, that the results are still valid when we reload it. That is NOT what I'm asking about.
I'm specifically asking about how to maximize the form now that I have "showed" it using the above custom function. One hack begets another hack. Here is how far down this rabbit hole I've gone:
When a TForm, which is also a TWinControl's private field FShowing is false, setting Form.Maximized has no effect.
When a TForm has its TWinControl.FShowing field set true, setting the windowState to wsMaximized also causes the form to activate.
Is it possible to both make this form visible and make it take the window state I want it to take without activating? If I can't do this, then users are going to lose their keyboard focus when I show this form on a secondary monitor, something that I really want to avoid.
What I tried is to use Win32 ShowWindow API to do SW_SHOWMAXIMIZED:
ShowWindow(Self.Handle, SW_SHOWMAXIMIZED);
The above seems to grab focus (activate).
When you create the top-level window set the extended window style to
WS_EX_NOACTIVATE | WS_EX_APPWINDOW
WS_EX_NOACTIVATE stops the window activating. This also makes it disappear from the taskbar, so you need WS_EX_APPWINDOW to fix that problem.
Call ShowWindow(hWnd, SW_MAXIMIZE) and the window will be maximized but not activated.
You need to be able to activate the window once it is visible, so in the WM_ACTIVATE handler (the irony!) you need to clear the WS_EX_NOACTIVATE flag thus:
case WM_ACTIVATE:
{
DWORD exstyle = GetWindowLong(hWnd, GWL_EXSTYLE);
if (exstyle & WS_EX_NOACTIVATE)
{
SetWindowLong(hWnd, GWL_EXSTYLE, exstyle & ~(DWORD)WS_EX_NOACTIVATE);
}
}
Apologies for the C++. This should be simple to translate into Delphi.
EnumDisplayMonitors API enumerates the monitors and their coordinates on the joint desktop, area into which a particular monitor maps to its specific position.
To find out which monitor is "current" you would want to compare the current window position against monitor coordinates rcMonitor/rcWork. Or, you have MonitorFromPoint and friends to help you.
Once to decided which monitor you want, you can either move your window (MoveWindow, SetWindowPos) to monitor's work area, or use this rectangle in response to WM_GETMINMAXINFO message to send the window to this position as a part of standard maximization.
To add to this, this small C++ application [1, 2, 3] demos the concept mentioned above, shows monitor information, and changes position where the window would be maximized to.

How to avoid setting focus when showing multiple forms?

First of all, this question is continued off of an answer of mine on another question...
Assume I'm using this particular solution to place a frame around any given window. How can I make this to where the focused form keeps its focus without jumping focus to these frame forms? Currently, i'd have to call SetFocus to set anything straight, but, then the windows get glitchy and don't show right.
How can I make sure the focused form keeps its focus at all times regardless of these 4 border forms showing?
Instead of showing edge windows with directly Show procedure (FTop.Show), use show window without activation:
ShowWindow(FTop.Handle, SW_SHOWNOACTIVATE);
FTop.Visible := True;
Daniel Rikowski wrote here

TToolbar incompatible with TForm.DoubleBuffered?

I am using Delphi XE3.
When I create a new VCL project and drop a TToolbar on it, everything works fine - except when I activate Form1.DoubleBuffered.
From that moment on, drawing of the toolbar is broken - in designtime, it's either black, transparent, or parts of the IDE (statusbar, toolbar, etc.) are copied into it. It changes between these options when I click on it and when I change to source code (F12) and back.
In runtime, it's always black.
With DrawingStyle=dsGradient, everything works as expected. Switching back to dsNormal breaks it again.
Deactivating Form1.DoubleBuffered repairs it.
Any hints how to work around that issue?
In my opinion the solution is to set TForm.DoubleBuffered to False. Using a value of True causes all sorts of other problems. This property worked reasonably well before XP themes, but since their arrival, using TForm.DoubleBuffered has not been viable, in my view.
In addition to the problems you have encountered, I've come across lots of painting flaws when using the Windows Basic theme. I know that's not mainstream, but I happen to see that a lot with remote access. And there's more. When you double buffer a form you stop the theme animation from working. For example, a default button pulses to indicate that it is the default button.
If you get resize flickering without double buffering the form, use the ideas from my answer to another question. From the comments it seems that the flickering you wish to combat is in a paint box. I obviously don't know the details, but my experience and instincts say that you should be able to deal quite easily with that flickering at a local level. There surely should be no need for the global form wide double buffering.
As reported in this article, When not to use DoubleBuffered, some controls like TToolBar and TRichEdit don't work well with the DoubleBuffered property set to true.
The reason be be found in documentation, TWinControl.DoubleBuffered, where some WinControls can't draw themselves on a temporary bitmap (as happens when DoubleBuffered is true).
One workaround can be to put the TToolBar on a TPanel.

How to make a window to stay under all the other windows ('bottom-most')

Exact Duplicate: How to make 'always on bottom' window?
Related: Window on desktop?
I'm developing a small application in Delphi that need to behave like a shell (replacement) launching pad (for Windows Embedded). I have some icons/buttons on it that will launch other applications. The point is that applications need to stay all the time in front of the "shell". Additionally the applications are started using simple-click, but if double click (accidentally) is used the application will go behind (the "shell" will be focused)
Since this application will replace the actual shell (Explorer) will have to behave similarly to Explorer ... so it has to stay in "background" all the time and should not appear in ALT+TAB list.
I tested a lot of combinations of SetWindowPos with HWND_BOTTOM, SWP_NOACTIVATE etc. without success..
Additionally I found some info regarding this but it doesn't work as advertised:
How to keep a form always in the background (bottommost)
Any hints how to achieve all these ?
Update: For hiding the window from ALT-TAB list/switcher (and from Taskbar, but since I'm interested to create shell replacement that will be no Taskbar) I found the following articles:
Hide a Delphi Application Button from the TaskBar
Hide a Delphi 2007 Application Button from the TaskBar (with MainFormOnTaskBar)
Hasn't that been asked before?
It's unclear if you are trying to make your application behave as a 'launchpad' on top of the current shell, or if you are trying to make your application be the shell.
There is a major difference between those two.
It sound like the last option is what you really want, and then taskbars etc shouldn't be any trouble at all - they won't be there, since they are part of the old shell (explorer.exe) that you have replaced.
SharpEnvironment, an open source shell-replacement made with Delphi, may give you some hints on the way.
I'm not sure what the exact effect is that you're trying to achieve, but I think you basically want to change the workarea on your screen.
You can obtain the current workarea as a TRect with Monitor.WorkareaRect.
You can set it via SystemparametersInfo()
The example below turns your form into something that more or less resembles the way the Windows taskbar works:
no border
always on top
stuck at the bottom of the screen
other windows applications that maximize, will only maximize until the top of our form.
Let me know if this is what you mean.
procedure TfrmMain.FormCreate(Sender: TObject);
var
Rect :TRect;
begin
Height := 25;
BorderStyle := bsNone;
FormStyle := fsStayOnTop;
Rect := Monitor.WorkareaRect;
Rect.Bottom := Rect.Bottom - Height;
Left := Rect.Left;
Width := Rect.Right;
Top := Rect.Bottom;
SystemparametersInfo(SPI_SETWORKAREA,0, #Rect,SPIF_SENDCHANGE);
end;
Some things to keep in mind:
When your program finishes, the workarea on your desktop will still be the way you've set it, so remember to restore it before terminating the application. I'm sure you'll figure out how to do that.
When you resize the windows taskbar, running applications are notified of the change, and maximized applications adjust their size to the new workarea. The above code doesn't seem to trigger that, so you might need to find a way to do that.
#dummzeuch
The solution you mentioned does not include any code just some suggestions ... additionally does not handle the ALT-TAB issue that I need to solve too (My window should not apear into the ALT-TAB list, similar to how Explorer - as a shell - behave).
#Wouter
I need to create a shell replacement (borderless full screen form) that need to stay on the "bottom" all the time similar to regular Explorer shell (that will replace). So it should stay on the bottom not on the top as you suggested.
This "shell" replacement is used for a Windows Embeded solution where I can not use the regular Explorer shell. This shell form include some "soft buttons" (TImage to emulate icons) for some selected executable files that can be started from it. The problem is
that is that I need to make sure that the shell stay behind all the time, otherwise it can hide some other applications (ex: multiple fast click on an icon that will start the application but will refocus on shell, or when using ALT TAB).
In this context, I have another question: there is any way to wait for any application to start ?
I will really appreciate some code hints to get started.
It's a Win32 FAQ for decades.
See on professional Win32 api group
Is this what you want?
procedure SetWindowPosToBack(handle: HWND);
begin
SetWindowPos(Handle,HWND_BOTTOM,0,0,0,0,SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOSIZE);
end;

Resources