How to make transparent Form get mouse enter events? - forms

I want to create a Delphi application that does something interesting when the user moves his mouse over the top-left corner of the screen. I thought about it and plan to do it with the following strategy:
Create a very small 3x3 Form and make it transparent and always on top. Also make it with no title bar or border.
Define mouse enter event for the Form.
I use the following code for step 1:
procedure TopLeftForm.FormCreate(Sender: TObject);
begin
FormStyle := fsStayOnTop;
self.TransparentColor := true;
self.TransparentColorValue := self.Color;
self.BorderStyle := bsNone;
end;
The problem is that I found that when the Form is transparent, it can't capture mouse enter events. I could make the Form not transparent in order to get mouse enter events, but that way users can see the Form at the top-left screen corner, which is not what I want.
What's your suggestions to my problem?

What you want is a 'Hook' that can give you information about mouse events without the events being intended for your program. It's too big a topic to be able to give you a how-to in one go, but these two links should be helpful:
Understanding how to use Windows Hooks
Mouse Hook Tutorial with Delphi codes
Why use a Windows Hook?
The Windows environment is designed around messages being passed around. Typically, a program is only interested in messages that are sent directly to its own windows. Trying to make sure that your application has a window that will get the messages blocks other applications from receiving those messages when they are in the same location under yours, and if another window is over yours then you won't get the messages. If you want to know about activity that's happening that wouldn't normally be sent to you - for example, a mouse click outside of your application's window. To enable applications to have visibility of events that are not destined for itself, a windows hook can be used.
There are different types of hooks, depending on what you want to access. A mouse hook is appropriate for what you have specified here. The system maintains a 'Hook Chain' for all of the hooks that have been installed - it will be your responsibility to pass the messages on down the chain, and to uninstall yourself from the chain.
To access the messages, your hook function will look something like this (code taken from the 2nd link above and adapted):
function MouseHookHandler(ACode: Integer; WParam: WParam; LParam: LParam): LResult; stdcall;
var
vMouseInfo: PMouseHookStruct;
begin
if ACode < 0 then
Result := CallNextHookEx(Data^.MouseHook, ACode, WParam, LParam)
else
begin
vMouseInfo := PMouseHookStruct(Pointer(LParam));
PostMessage(Data^.AppHandle, WM_CUSTOM_MOUSE, vMouseInfo^.pt.X, vMouseInfo^.pt.Y);
// we dont want to block the mouse messages
Result := CallNextHookEx(nil, ACode, WParam, LParam);
end;
end;
In your hook function:
ACode is dependent on the type of hook and indicates the event
wParam and lParam have a meaning specific to the event
To pass the message on, you should call CallNextHookEx - however for some hooks, the message will always be passed on regardless.
Hooks can be installed as Global hooks - meaning they intercept messages on all threads running in the same Desktop / WinStation as the calling thread. So, if you have multiple users connected via RD, for example, the hook is specific to one of those desktops only.

Related

setted cursor on form wont show up on controls

I use Delphi xe-8 and I use thread for queries and I want to show an hourglass cursor while my thread is working.
I'm setting my cursor to hourglass but when I hover on controls on the form, for example over a grid, the cursor appears as the default; the cursor only changes when I hover over the form itself. What I want to do is to show an hourglass cursor over just the form and any controls on the form, but not the rest of the application.
I tried to set Screen.Cursor to crHourGlass but then it is for the whole application, so it is an hourglass even if I'm hovering on another form.
Is it possible to do that? If it is how can i do that?
When the mouse is over a given TWinControl, the OS sends it a WM_SETCURSOR message requesting it to set the onscreen cursor icon. The VCL's default processing of that message goes like this:
if the Screen.Cursor is not crDefault, that is the cursor used.
otherwise, if the TWinControl has a TGraphicControl child underneath the mouse, and its Cursor is not crDefault, that is the cursor used.
otherwise, if the TWinControl's own Cursor is not crDefault, that is the cursor used.
otherwise, the message is passed to the parent window. Repeat steps 2-3.
So, to do what you are asking for, try setting the Form's Cursor to the desired value, and make sure all child controls on the Form have their Cursor set to crDefault.
If that is not an option for you, or if it doesn't work, you can instead have your Form class override its virtual WndProc() method (or use a message method) to handle the WM_SETCURSOR message directly. Call the inherited handler first, and if the message's Result is FALSE and the worker thread is running then call the Win32 API SetCursor() function to set your desired cursor icon, and set the message's Result to TRUE, eg:
protected
procedure WndProc(var Message: TMessage); override;
...
procedure TMyForm.WndProc(var Message: TMessage);
begin
inherited;
if (Message.Msg = WM_SETCURSOR) and (Message.Result = 0) and (Thread is Running) then
begin
Windows.SetCursor(Screen.Cursors[crHourGlass]);
Message.Result := 1;
end;
end;
Or
private
procedure WMSetCursor(var Message: TMessage); message WM_SETCURSOR;
...
procedure TMyForm.WMSetCursor(var Message: TMessage);
begin
inherited;
if (Message.Result = 0) and (Thread is Running) then
begin
Windows.SetCursor(Screen.Cursors[crHourGlass]);
Message.Result := 1;
end;
end;
This way, Screen.Cursor and individual TControl.Cursor properties still take priority, but if the cursor remains unchanged (ie, all of the properties are crDefault) then you can change the cursor for the entire Form as a whole, without affecting other Forms.

Delphi: How to restore a form's original location when monitor configuration changes?

I have a multi-form application in which a child form is positioned on the second monitor on startup, at which time its BoundsRect is saved.
When the computer's display configuration changes, Windows moves the form to the first (primary) monitor. I can catch this change with WM_DISPLAYCHANGE:
procedure WMDisplayChange(var msg: TWMDisplayChange); message WM_DISPLAYCHANGE;
What I'm interested in doing is moving the child form back to the second monitor when it reappears in the configuration (i.e. Screen.MonitorCount goes from 1 to 2), e.g.:
childForm.BoundsRect := childForm.m_WorkingBounds;
// (or)
childForm.BoundsRect := Screen.Monitors[Screen.MonitorCount-1].BoundsRect;
However this assignment is have no affect -- the child form stays on monitor 0.
I've tried other approaches, such as SetWindowPos(), with no success ...
Root of your problem is in the fact that Delphi VCL does not refresh its internal list of monitors when they actually change. You have to force that refresh yourself.
Monitors are refreshed with TScreen.GetMonitors method that is unfortunately private method so you cannot call it directly.
However, TApplication.WndProc(var Message: TMessage) processes WM_WTSSESSION_CHANGE and upon receiving that message it calls Screen.GetMonitors - this is most benign way to achieve your goal.
When you receive notifications that monitors are changed just send it to Application:
SendMessage(Application.Handle, WM_WTSSESSION_CHANGE, 0, 0);
I tested this with old version Delphi5 and it worked easy just to:
Screen.Free;
Screen := TScreen.Create(Nil);
The screen handling has changed in later versions of Delphi, however a similar approach may work.

Delphi - TEdit Labels are not displaying (Windows 7/Vista only)

I we got few TEdit forms inserted on another form, but their labels are not displaying until I change size of the window. This happens just on Windows Vista / Windows 7. Windows XP has everything labeled correctly.
Ive already tested repaint / refresh (just TEdit / all form etc.) with no result.
Delphi 7.
Thank you for your answer
bad version
Correct version
code will be added soon enough :)
May be it is ALT Key press causes controls to disappear under Themes in Vista and XP bug?
I was able to mostly resolve this same problem on my project. It seems to be a painting order problem. The solution ultimately was to call frame.Refresh; on the frame that was not displaying properly. But, figuring out the right place to put that Refresh was a little tricky, I tried several places before I found a spot that worked. Where it worked for me was, in the method where I choose which nested frame to show on the options panel, and physically display the nested frame, to call frame.Refresh; on the inner-most frame surrounding the labels that are not drawing properly. Calling refresh on the inner frame rather than the frame for the entire window seemed to be key.
From the screen-shots you are showing, you look like you probably have a similar complex setup of frames where there are likely to be frames displayed on top of frames, which may change dynamically after the frame is displayed initially. That seemed to be the setup that would create the problem in the first place, the initially shown frame never seemed to have problems.
One note, however, is if the window moves off the screen or is re-sized, another window is dragged in front of it, or mouse-over buttons that are disappearing is that these actions could cause the problem to re-appear spontaneously. There may be additional places, such as in a special handler for window resize, etc. or on a timer where you to call refresh on the frame periodically, similar to some of the solutions mentioned for the ALT Key bug. There seems to be some overlap in the type of problem and how to fix it but does not the exact same cause (this bug seems to happen regardless of the Alt key)
There are bugs in TButton when running with windows Themes.
You can search 'ThemesEnabled' in StdCtrls.pas, remove/comment all its related branch, like this:
procedure TButton.CNCtlColorBtn(var Message: TWMCtlColorBtn);
begin
with ThemeServices do
{
if ThemesEnabled then
begin
DrawParentBackground(Handle, Message.ChildDC, nil, False);
// Return an empty brush to prevent Windows from overpainting we just have created.
Message.Result := GetStockObject(NULL_BRUSH);
end
else
}
inherited;
end;
Then compile it, and replaced lib/StdCtrls.dcu with your patched version.
{ Labels no Windows Vista, 7, 8 to Fix the problem, Delphi 7 32 bits }
on FormShow:
var
i : Integer;
begin
For i := 0 to (Form1.ComponentCount - 1) do
begin
If (Form1.Components[i].ClassType = TLabel) then
TLabel(Form1.Components[i]).Refresh;
end;
end;
Just run this.
I'm getting the same thing, except it only seems to be a problem while the application is themed. If it is unthemed (ie Project->Options->Application->Appearance->Default Style = Windows) it works fine, no refresh or repaint needed.
Seems to be related specifically to the TFrame class so I wonder if something about the redraw handler isn't amok (related to Invalidate). Something in the ChangeNotify process or the Windows message pump in the VCL might not making its way up the parent control chain and responding with a cascading repaint funnelling back down on everything "invalidated".
One other cludge I did try with success was setting the host control Visible property to False on one line and then to true in the next line ie:
procedure TFrame1.UpdatePanel;
Panel1.Visible := False;
Panel1.Visible := True;
end;
Then calling this method where the proper drawing is needed.
All of the other child controls of Panel1 were drawn perfectly. You might have to cache the location of the text cursor if your update occurs while modifying the contents of one of the child controls like a TEdit or TMemo. This should be trivial compared to hours of hunting down the cause of the problem. Maybe looking into the VCL source of the Setter method for the Visible property on the offending control host (such as the TPanel) might provide some insight to the overall problem of why Repaint and Refresh don't seem to work as they should in this case.
Actually it is much simpler than any of the solutions presented so far.
The only thing needed is to respond to the WM_UPDATEUISTATE message.
Add a procedure like the one bellow to the form:
...
protected
procedure WmUpdateUIState(var Msg: TMessage); message WM_UPDATEUISTATE;
...
procedure TForm1.WmUpdateUIState(var Msg: TMessage);
begin
inherited;
Invalidate;
end; { WmUpdateUIState }
Done!
Tested on Windows 10 64 bits.
One can speed things up by creating a unit like this:
unit FixAltKeyForm;
interface
uses
Windows, Messages, Classes, Forms;
type
TForm = class(Forms.TForm)
protected
procedure WmUpdateUIState(var Msg: TMessage); message WM_UPDATEUISTATE;
end; { TForm }
implementation
{ TForm }
procedure TForm.WmUpdateUIState(var Msg: TMessage);
begin
inherited;
Invalidate;
end; { WmUpdateUIState }
end.
Add the unit's name to the uses clause of the interface session of any form where this behavior is wanted and you are done. Only be sure to put the unit's name AFTER 'Forms' in the uses clause. No need to create a package nor to install anything at all. That's what I call Visual Subclassing, for lack of a better term.

Disable form rising to the top

I want a form that when I set at the bottom of the z-order it stays there. I tried:
SetWindowPos(Handle,HWND_BOTTOM,Left,Top,Width,Height,SWP_NOZORDER);
and when I overlap it with some other apps it stays at the bottom as I need. However when I click on it, it rises to the top. I then tried:
SetWindowPos(Handle, HWND_BOTTOM, Left, Top, Width, Height,
SWP_NOACTIVATE or SWP_NOZORDER);
and various other switches from this website...
http://msdn.microsoft.com/en-us/library/ms633545.aspx
But it still rises to the top.
SetWindowPos sets the position of a window only when it is called, it does not establish a state. Handling WM_WINDOWPOSCHANGING is the correct way to do this:
While this message is being processed, modifying any of the values in
WINDOWPOS affects the window's new size, position, or place in the Z
order. An application can prevent changes to the window by setting or
clearing the appropriate bits in the flags member of WINDOWPOS.
type
TForm1 = class(TForm)
..
private
procedure WindowPosChanging(var Msg: TWMWindowPosMsg);
message WM_WINDOWPOSCHANGING;
end;
..
procedure TForm1.WindowPosChanging(var Msg: TWMWindowPosMsg);
begin
if Msg.WindowPos.flags and SWP_NOZORDER = 0 then
Msg.WindowPos.hwndInsertAfter := HWND_BOTTOM;
inherited;
end;
Never tried it, but you might get somewhere trapping the message WM_WINDOWPOSCHANGING, and twiddling with Z order. Could get complicated though and I personally would find it irritatingly non-standard.
A menu option equivalent to cacsade but with a z-order sort might be a better option, I mean if they clicked on it they expect to see it.
Out of instinct, I wouldn't trust that the app will always work as you expect. Other applications may have a much more strict method of forcefully bringing its self in front of yours, or vice-versa in your case, sending it to the back. For example, a timer which repeatedly sends the window to the back. Then if you have two of such apps layered, you would watch them fight each other, basically flickering back and forth. In the end, I wouldn't count on a permanent 100% solution for this because you never know what other applications might do to override yours.

How to change controls simultaneously without repainting each one?

For example I need to disable two buttons in runtime. After I disabled first button it bacame gray, the second - it also became gray. But I do not know how to make the repainting simultaneous!
I need something like that:
freeze the Form (disable repainting)
disable first button
disable second button
Enable Form repainting
How to implement that?
Look at the Win32 API WM_SETREDRAW message. For example:
SendMessage(Handle, WM_SETREDRAW, False, 0);
Button1.Enabled := False;
Button2.Enabled := False;
SendMessage(Handle, WM_SETREDRAW, True, 0);
InvalidateRect(Handle, nil, True);
Messages cannot be processed until your application re-enters a message loop, so any attempt to modify/update control state that relies on message processing will not work within a single sequence of code that does not "pump" messages.
Fortunately the VCL controls typically provide a means for force repainting without waiting for messages to be processed, via the Update method:
Button1.Enabled := False;
Button2.Enabled := False;
Button1.Update;
Button2.Update;
This works independently of having to disable form repainting. The form will not repaint until your application goes into a message loop anyway, so disabling form painting and re-enabling within a single procedure that does not itself cause message processing is a waste of time.
This may not be exactly simultaneous repainting of the two buttons, but truly simultaneous painting of two separate control is impossible without getting into multithreaded GUI painting code which I think is way beyond the scope of this problem. Calling Update on two buttons in this way will be as near simultaneous in effect as you need however.
To Elias551:
LockWindowUpdate is probably not the best way to handle this since it is intended for drag and drop operations and can introduce subtle bugs when misused.
See http://blogs.msdn.com/b/oldnewthing/archive/2007/02/22/1742084.aspx
Instead use SendMessage(hwnd, WM_SETREDRAW, FALSE, 0)
The above decision with WM_SETREDRAW does not update child windows.
Instead, i recommend RedrawWindow:
RedrawWindow(Handle, nil, 0, RDW_INVALIDATE or RDW_ALLCHILDREN);
This could help: the API LockWindowUpdate(Handle: HWND) locks drawing to the handle and children.
ex:
procedure TForm1.ColorButtons();
begin
LockWindowUpdate(Self.Handle);
// Make some stuff
LockWindowUpdate(0);
end;
Once the locked handle is reset, the component is repainted