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.
Related
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.
I need help with this solution.
There is application with several forms. One of this forms need to be opened on selected monitor. For example:
Solution 1. OnCreate form cheks if there are more than one monitor used, and open on the last one. I try this code, but with no luck:
Application.CreateForm(TfrmDashboard, frmDashboard);
for I := 0 to Screen.MonitorCount -1 do
begin
// Checking Screen Position
ShowMessageFmt('%d, %d, %d, %d',
[Screen.Monitors[i].BoundsRect.Left,
Screen.Monitors[i].BoundsRect.Top,
Screen.Monitors[i].BoundsRect.Right,
Screen.Monitors[i].BoundsRect.Bottom]);
end;
if Screen.MonitorCount > 1 then
begin
frmDashboard.Top:= Screen.Monitors[i].BoundsRect.Top;
frmDashboard.Top:= Screen.Monitors[i].BoundsRect.Left;
end;
Solution 2. Form is dragged to the selected monitor and OnDestroy event Top and Left position are written to the INI file. Next time form opens on the same monitor and same position. I try this code, but also with no luck:
procedure TfrmDashboard.FormCreate(Sender: TObject);
var
ini: TIniFile;
begin
ini:= TIniFile.Create(extractfilepath(paramstr(0))+'Dashboard.ini');
Left:= StrToInt(ini.ReadString('Left', 'Form_Left', '0'));
Top:= StrToInt(ini.ReadString('Top', 'Form_Top', '0'));
ini.Free;
end;
procedure TfrmDashboard.FormDestroy(Sender: TObject);
var
ini: TIniFile;
begin
ini:= TIniFile.Create(extractfilepath(paramstr(0))+'Dashboard.ini');
ini.WriteString('Left', 'Form_Left', IntToStr(Left));
ini.WriteString('Top', 'Form_Top', IntToStr(Top));
ini.Free;
end;
frmDashboard.Top:= ...
frmDashboard.Top:= ...
This appears to be a simple copy paste error. You set Top both times. Presumably you mean:
frmDashboard.Top:= ...
frmDashboard.Left:= ...
This code makes the same mistake:
if Screen.MonitorCount > 1 then
begin
frmDashboard.Top:= Screen.Monitors[i].BoundsRect.Top;
frmDashboard.Top:= Screen.Monitors[i].BoundsRect.Left;
end;
Furthermore, it refers to i when it is ill-defined. The compiler will warn about this. I hope you have compiler warnings and hints enabled, and do take heed of them.
Your OnCreate event handler will raise an exception if the INI file contains invalid data. For instance, if the user edits the position values to be non-numeric, then StrToInt will raise an exception. Your program should be resilient to that.
Both the OnCreate and OnDestroy event handlers don't manage the lifetime of the INI file object correctly. If the INI file access fails, or the calls to StrToInt fail (see above) then you will leak the object. This is the pattern to be followed:
obj := TMyObject.Create;
try
// do things with obj
finally
obj.Free;
end;
I have this problem. When I hide my main form, then the taskbar icon of my application is also hidden. I saw a new question about this problem as well and the answers didn't really help. They suggested to minimize it, but I do not want to minimize the application.
Is it possible to change the main form while the application is already running?
for instance. I have two forms. when I want to hide the one form and show the other form, then the taskbar icon should stay at the taskbar and the main form should switch to the other form.
I am using Delphi XE6 and it is a VCL Forms application.
I have also seen a different old question about changing main form while runtime, but it is very old and still based on Delphi 6.
Is it possible to change the main form while the application is already running?
It is not possible to change the VCL main form whilst the program is running. This property is determined once and for all when the program starts.
One possible way to proceed for you is to arrange for the secondary form, the form that is not the main form, to have a button on the taskbar. Do that by making it unowned, or by using the WS_EX_APPWINDOW extended window style.
Update
Well, you can change Application.MainForm, but it requires you to destroy the current main form, and then create a new one.
It is not possible to change the Application.MainForm once it has been assigned. However, you do not need to, either. The simpliest solution to this issue would be to create a blank hidden TForm to act as the real Application.MainForm and let it manage the taskbar normally, and then you can show/hide any secondary TForm objects when needed, where your desired "MainForm" is a secondary form not the real MainForm.
As David Heffernan already said it is not possible to change the Main Form of an already running application. This is the limitation of the windows itself.
What you can do is cheat and never actually change the Main Form but only make it look like you did.
How do you achieve that?
Step 1: Add code to the second Form to make its own Taskbar button
procedure TWorkForm.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW;
end;
Step 2: Dynamically create the second Form just before switching to it. Upon its creation previously added code will create a new Taskbar button for your second form.
Step 3: Now hide you actual Main Form. Hiding it will also hide the Taskbar button belonging to it. So you end up still having one Taskbar button shown and it is the one belonging to your second Form.
Step 4: In order to allow your second Form to terminate your application at its closure call Close method of your true Main Form from your second Forms OnClose or OnFormCloseQuery event.
If you wanna be able to switch back to your true Main Form call Show method of your Main Form instead of Close method.
This approach allows us of swapping forms pretty quickly so only most keen users will notice short animation of Taskbar button.
NOTE: If your second for is a complex one and because of that takes some time to create you might wanna create it hidden and then once its creation process is finished show it and do the swap. Otherwise you might end up with two Taskbar buttons being shown at same time which I believe you wanna avoid.
Here is a short example:
- LoginForm is a true Main Form that is created at the application startup
- WorkForm is the Form on which user will spend most of time after logging in and this one is created in login process
Login Form code:
unit ULoginForm;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TLoginForm = class(TForm)
BLogIn: TButton;
procedure BLogInClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
LoginForm: TLoginForm;
//Global variable to tell us if we are only logging out or closing our program
LoggingOut: Boolean;
implementation
uses Unit2;
{$R *.dfm}
procedure TLoginForm.BLogInClick(Sender: TObject);
begin
//Create second Form
Application.CreateForm(TWorkForm, WorkForm);
//Hide Main Form
Self.Hide;
//Don't forget to clear login fields
end;
end.
Work form code:
unit UWorkForm;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TWorkForm = class(TForm)
BLogOut: TButton;
//Used in overriding forms creating parameters so we can add its own Taskbar button
procedure CreateParams(var Params: TCreateParams); override;
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
procedure BLogOutClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
WorkForm: TWorkForm;
implementation
uses Unit1;
{$R *.dfm}
procedure TWorkForm.BLogOutClick(Sender: TObject);
begin
//Set to true so we know we are in the process of simply logging out
LoggingOut := True;
//Call close method to begin closing the current Form
Close;
end;
procedure TWorkForm.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW;
end;
procedure TWorkForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
//Check to see if we are in the process of simply logging out
if not LoggingOut then
begin
//If we are not in the process of logging out close the Main Form
LoginForm.Close;
//and then allow closing of current form
CanClose := True;
end
else
begin
//But if we are in the process of simply logging out show the Main Form
LoginForm.Show;
//Reset the LoggingOut to false
LoggingOut := False;
//and then alow closing of current form
CanClose := True;
end;
end;
end.
I implemented this in the same way #DavidHeffernan had already suggested and as yet have not come across any issues, it might not be the best way but it worked for me and what i was trying to achieve, which was have "normal" feeling behaviour when the user minimised their MainWork form.
Code looked similar to this:
procedure TfrmLogin.btnLoginClick(Sender: TObject);
begin
frmMainWork := TfrmMain.Create(Application);
try
Pointer((#Application.MainForm)^) := frmMainWork;
frmLogin.Hide;
frmMainWork.ShowModal;
finally
Pointer((#Application.MainForm)^) := frmLogin;
frmLogin.Show;
FreeAndNil(frmMainWork);
end;
end;
hope this helps :-)
You can change main form.
Do a variable F: ^TForm, then set it to #Application.MainForm. After that you can set main form as via F^ := YourAnotherForm.
If you set Application.MainFormOnTaskbar to false in your startup routine (in the .dpr file), then the VCL will create a hidden form whose sole purpose is to provide the taskbar icon. This is an older approach and is generally not recommended, but as long as other windows are visible, it would let you hide your main form without the application disappearing from the taskbar.
You can also provide an Application.OnGetMainFormHandle event handler to change your Application.MainFormHandle at runtime (but not Application.MainForm). MainFormHandle affects stuff like the owner of modal popup dialogs.
More information on Application.MainFormOnTaskbar and the disadvantages of disabling it: This gets complicated in a hurry. The short version is in the VCL docs, which explain that several of Windows' newer features (like live taskbar thumbnails) that were introduced in Vista require MainFormOnTaskbar := True. There's a lot more background reading in this SO discussion.
I've had an additional issue working with Delphi XE2 MDI apps that are also COM servers. My main form Main calls a secondary form MenuForm which contains shared icons and popup menus for the whole app.
In my example, the app works perfectly when run standalone.
Main gets created, and then creates a MenuForm at the end of FormCreate.
All good.
But when called from a COM server, Main.FormCreate is called first, but somehow MenuForm gets assigned to Application.MainForm. There is a race condition in the underlying RTL. This causes havoc when trying to create the first SDI child, as Application.MainForm is not MDI.
I tried working around this by Main.FormCreate posting a message back to itself, to delay creation of MenuForm until after Main.FormCreate has returned.
But there is still a race condition here - MenuForm was still assigned to Application.MainForm.
I was eventually able to work around this using code to poll Application.MainForm every 10ms, with a maximum of 10 seconds. I also had to remove any reference in Main to MenuForm's icon list (in the .dfm) until after MenuForm was created explicitly - otherwise MenuForm would get created implicitly at the end of MainForm.create.
Hope this helps someone!
const
CM_INITIAL_EVENT = WM_APP + 400;
TmainForm = class(TForm)
...
procedure afterCreate(var Message: TMessage); message CM_INITIAL_EVENT;
...
end;
procedure TmainForm.FormCreate(Sender : TObject);
begin
...
...standard init code
...
postmessage( handle, CM_INITIAL_EVENT, 0, 0 );
End;
procedure TmainForm.AfterCreate(var Message: TMessage);
var
i: Integer;
begin
//must assign these AFTER menuform has been created
if menuForm = nil then
begin
//wait for mainform to get assigned
//wait up to 10*1000ms = 10 seconds
for i := 0 to 1000 do
begin
if Application.Mainform = self then break;
sleep(10);
end;
Application.CreateForm(TmenuForm, menuForm);
menuForm.parent := self;
end;
//NOW we can assign the icons
Linktothisfilterfile1.SubMenuImages := menuForm.treeIconList;
ActionManager.Images := menuForm.treeIconList;
allFilters.Images := menuForm.treeIconList;
MainMenu.Images := menuForm.treeIconList;
...
end;
In the current Delphi implementation, I am sure that there are no consequences using a pointer to change Application.MainForm.
If you look into TApplication class, you can see that FMainForm is used only to check if the application has at least one form, and iterates the main loop inside the TApplication.Run method while the FMainForm exists. If you do not want to use a pointer to hack this property, you can implement your own TApplication class, like TMyApplication, copy all the routines inside it and define MainForm property to read and write
Delete the *.dproj file. Delete the following line in the *.dpr file. You get rid of all your troubles :)
Application.MainFormOnTaskbar := True;
Sorry for my bad English.
I have a main form and another, "secondary" form. If I close the secondary first, its OnClose handler is called. Unfortunately if I close the main form first, the application terminates and the OnClose of the secondary is not called. This is a problem because I'd like to save the position and size of every form before they are closed. How can I solve this?
A tidy place to handle this would be the OnCloseQuery event of the main form.
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
var i : integer;
begin
CanClose := true;
if Application.MainForm = self then begin
CanClose := MessageDlg('Close the application?', mtConfirmation,
[mbYes, mbNo], 0) = mrYes;
if CanClose then
for i := 0 to Screen.FormCount - 1 do
if Screen.Forms[i] <> self then Screen.Forms[i].Close;
end;
end;
The first check suggested above is only useful if you have multiple instances of the main form in use. The second check for self is necessary to prevent re-entry.
You can use OnDestroy instead of OnClose to save that. And you should probably destroy your secondary form after it is closed (creating before opening):
Form2 := TForm2.Create(Application);
Form2.Open;
And in Form2.OnClose:
Action := caFree;
You will also want to remove the lines in the .dpr that creates secondary forms automatically:
Application.CreateForm(TForm2, Form2);
And maybe disable this autocreation. In D7: Tools >> Environment Options >> Designer >> Disable "Auto-create ..."
Doing all this will save you some memory. Also consider using ShowModal, instead of Show, when you expect the user to close your secondary form before going back to the main one.
I have 2 forms - The first form has a bunch of edits, comboboxes, etc, and the 2nd form has a Webbrowser.
I have a routine in the 2nd form, that loads HTML into the Webbrowser, and that routine is fired in the OnChange event of all my controls on the first form.
The problem is, that my focused control on my first form looses focus, when the 2nd form is loading the HTML into the webbrowser.
How can I make sure that the 2nd form does not get focus when the routine is fired? Or, more importantly - make sure that the focused control on my first form, does not loose focus?
What you're trying to do goes against the VCL, and probably goes against usual user expectations: When a new window is shown, unless it's a tool window, the usual (and expected) behavior is to move focus to it. The Win32 Api to show a window is ShowWindow and it'll activate the window, unless the SW_SHOWNOACTIVATE flag (or one of it's variants) is specified.
When you make a VCL form visible, a call to that function is also made. The call to ShowWindow is buried in procedure TCustomForm.CMShowingChanged(var Message: TMessage), a 135 lines procedure that hard-codes the SW_SHOWNORMAL flag (ie: an Activating flag) for the ShowWindow call that the VCL makes. Unfortunately that's a big piece of code and overriding it is not going to be easy. If this was my program I'd probably attempt changing the code in place: I'd add a DoNotActivate:Boolean flag to TCustomForm and change the single line of code in CMShowingChanged that calls ShowWindow for non-MDI forms to take that flag into account and simply call ShowWindow(Handle, SW_SHOWNOACTIVATE). If changing the VCL is not something you'd do light-hearted, you can use the following hacky solution:
The trick I'm suggesting is to create the new form (the one holding TWebBrowser) but do NOT set it's Visible property to True. Instead, make manual calls to ShowWindow(Handle, SW_SHOWNOACTIVATE) to show the form without activating it. Because this code would no longer code through the usual Delphi VCL, owned controls would not automatically be created and shown, so the ShowWindow(...) call needs to be made recursively, for all TWinControl descendants of the form:
procedure TForm15.Button1Click(Sender: TObject);
var F: TForm16;
procedure RecursiveShowNoActivate(W: TWinControl);
var i:Integer;
begin
ShowWindow(W.Handle, SW_SHOWNOACTIVATE);
for i:=0 to W.ControlCount-1 do
if W.Controls[i] is TWinControl then
RecursiveShowNoActivate(TWinControl(W.Controls[i]));
end;
begin
F := TForm16.Create(Application);
F.Top := Top + height; // So the new form doesn't overlap mine
RecursiveShowNoActivate(F);
F.WebBrowser1.Navigate('http://msdn.microsoft.com/en-us/library/ms123401');
end;
There's an other catch with this code: Make sure you navigate to a web page that doesn't have a form that's automatically focused. Navigating to microsoft's MSDN library might be unusual for a code sample, but that canonical example (www.google.com) sets focus to the search form.
One simple solution can be to disable the form containing the web browser control. A disabled window will not gain the focus.
When the OnDocumentComplete event of TWebControl is fired, the browser control is ready to gain focus. Disable the form here, and post yourself a message so that you can enable the form shortly:
const
UM_POSTENABLE = WM_USER + 12;
type
TForm2 = class(TForm)
WebBrowser1: TWebBrowser;
procedure WebBrowser1DocumentComplete(ASender: TObject;
const pDisp: IDispatch; var URL: OleVariant);
private
procedure UMPostEnable(var Msg: TMessage); message UM_POSTENABLE;
end;
var
Form2: TForm2;
implementation
uses Unit1;
{$R *.dfm}
procedure TForm2.WebBrowser1DocumentComplete(ASender: TObject;
const pDisp: IDispatch; var URL: OleVariant);
begin
if Screen.ActiveForm = Form1 then begin
Enabled := False;
PostMessage(Handle, UM_POSTENABLE, 0, 0);
end;
end;
procedure TForm2.UMPostEnable(var Msg: TMessage);
begin
Enabled := True;
end;
Note that, according to the documentation, OnDocumentComplete can be fired multiple times. But since each call will receive a matching user message, this wouldn't be a problem.
procedure TForm1.FormCreate(Sender: TObject);
begin
Screen.OnActiveFormChange := ActiveFormChanged;
end;
procedure TForm1.ActiveFormChanged(Sender: TObject);
begin
if not (csDestroying in ComponentState) then
if ActiveControl <> nil then
ActiveControl.SetFocus
end;
procedure TForm1.EditOrComboChange(Sender: TObject);
begin
Form2.WebBrowser.SetFocus;
end;
Edit
Maybe this is not the most elegant way. As an alternative I tried
Form2.WebBrowser.Enabled := False;
to prevent the focus exchange at all. This keeps the focus on the edit control and strangely enough does the disabled WebBrowser update to the new page, but more mysteriously, this hides the caret in the edit control on Form1.