Delphi - open form on the second monitor - forms

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;

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.

Form not closing as it should

I am automating an open source program written in Delphi. From the main form, I am performing the following loop:
for i := 0 to analysisNames.Count - 1 do begin
currentAnalysisName := analysisNames[i];
analysisID := DatabaseModule.GetAnalysisIDForName(analysisNames[i]);
frmIIGraph.autoMode := true;
frmIIGraph.ShowModal();
end;
As you can see, it opens a form called frmIIGraph. Inside that form, I must open another form, which I do with the following code:
procedure TfrmIIGraph.FormActivate(Sender: TObject);
begin
if autoMode then begin
events := DatabaseModule.GetEvents(analysisID);
frmEventEdit.autoMode := true;
frmEventEdit.OpenDialog(events,0,analysisID);
frmEventEdit.ShowModal();
//frmEventEdit.Close;
SetFocus;
ModalResult := mrOK;
PostMessage(Self.Handle,wm_close,0,0);
end;
end;
The form opened from the above method is called frmEventEdit. Within that form I am running this code:
procedure TfrmEventEdit.FormActivate(Sender: TObject);
begin
if autoMode then begin
btnRTK_CalcClick(nil);
ModalResult := mrOK;
PostMessage(Self.Handle,wm_close,0,0);
end;
end;
The problem is that the PostMessage(Self.Handle,wm_close,0,0); in the latter code works fine and closes the form, resuming the code on the frmIIgraph at SetFocus;. However, the PostMessage(Self.Handle,wm_close,0,0); in the IIGraph form code, does not close the graph form, so that execution can resume on the main form, for the next iteration of the loop. You have to manually close the graph for it to proceed.
Any help is appreciated.
Your fundamental problem is that you have coded all your business logic in GUI code. So you are not able to execute the code that you want to execute without the convoluted code seen in the question.
If you want to solve your real problem you will deal with the root cause of your woes. You will separate the business logic and the GUI code. You will arrange for your business logic to be able to be executed in the absence of GUI.
If you don't want to solve your real problem, and wish to continue with this madness, you need to post a WM_CLOSE message to frmIIGraph.Handle in the OnDeactivate event handler for TfrmEventEdit. Presumably the one you post in TfrmIIGraph.FormActivate is getting consumed by the sub-form's message loop, or perhaps some call to ProcessMessages. But I cannot endorse this as a sane way to proceed.

Delphi2006 - How to create in DLL a message pump for a new window in a thread?

I have multithreaded application that loads my custom dll.
In this dll I need to create a window.
I'm doing it by creating new thread and inside it I'm trying to create this window, but I have got error that tells me: EInvalidOperation - Canvas does not allow drawing.
By searching in the net, I have discovered that I need custom message pump for that thread.
So, my question is, how properly do this?
What I do now is:
- external app is loading dll
- than this app in separte thread is calling Init function from dll
- Init function creates thread
- TMyThread is declared as:
type
TMyThread = class(TThread)
private
Form: TMyForm;
FParentHWnd: HWND;
FRunning: Boolean;
protected
procedure Execute; override;
public
constructor Create(parent_hwnd: HWND); reintroduce;
end;
constructor TMyThread.Create(parent_hwnd: HWND);
begin
inherited Create(False); // run after create
FreeOnTerminate:=True;
FParentHWnd:=parent_hwnd;
FRunning:=False;
end;
procedure TMyThread.Execute;
var
parent_hwnd: HWND;
Msg: TMsg;
XRunning: LongInt;
begin
if not Terminated then begin
try
try
parent_hwnd:=FParentHWnd;
Form:=TMyForm.Create(nil); // <-- here is error
Form.Show;
FRunning:=True;
while FRunning do begin
if PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE) then begin
if Msg.Message <> WM_QUIT then
Application.ProcessMessages
else
break;
end;
Sleep(1);
XRunning:=GetProp(parent_hwnd, 'XFormRunning');
if XRunning = 0 then
FRunning:=False;
end;
except
HandleException; // madExcept
end;
finally
Terminate;
end;
end;
end;
The exception EInvalidOperation - Canvas does not allow drawing is fired before thread reaches my existing message pump code.
What do I do wrong or what is the right way to make it work?
Thanks for any help.
To create second GUI thread in a DLL, I must do things exactly as in standard application.
Can anyone confirm my thinking?
In the DLL begin...end. section I do:
begin
Application.CreateForm(THiddenForm, HiddenForm);
Application.Run;
end.
In the TMyThread.Execute I must do:
procedure TMyThread.Execute;
begin
if not Terminated then begin
try
try
Application.CreateForm(TMyForm, Form);
???? how to make a thread that has remained in this place until you close this window ???
except
HandleException; // madExcept
end;
finally
Terminate;
end;
end;
end;
Is this the right way? Could it be that simple?
The simplest way to run a message queue in a thread is as follows:
procedure PerformThreadLoop;
var
Msg: TMsg;
begin
while GetMessage(Msg, 0, 0, 0) and not Terminated do begin
Try
TranslateMessage(Msg);
DispatchMessage(Msg);
Except
Application.HandleException(Self);
End;
end;
end;
And in your thread procedure would look like this:
procedure TMyThread.Execute
begin
InitialiseWindows;
PerformThreadLoop;
end;
All that said, what you are attempting is not going to work. You appear to be trying to use VCL components away from the main thread. That is specifically not allowed. The VCL's threading model dictates that all VCL code is run on the main thread. Your attempts to create a VCL form away from the main thread are doomed to failure.
I would question your desire to create a new thread. A Delphi DLL can show VCL forms provided that it runs those forms out of the thread that loaded and called the DLL. You can call Show from that thread and show a modeless form. This means that you are relying on the host application's message queue to deliver messages to your windows. By and large this can be made to work. If your form is modal then you can simply call ShowModal and the form will be serviced by the standard Delphi modal message loop.
So my advice to you is to keep all your GUI in the host app's GUI thread. If your DLL is expected to show GUI, and is also expected to do that away from the host app's GUI thread then you are in trouble. But I think that's highly unlikely to be the case.
Earlier (a year ago) I stated this: "To create second GUI thread in a DLL, I must do things exactly as in standard application".
This is exactly what everybody who is searching for this solution should do.
Let me explain, step by step:
we must add our application object to our thread:
type
TMyThread = class(TThread)
private
ThreadApplication: TApplication;
now some modification to definition of procedure TMyThread.Execute;
procedure TMyThread.Execute;
begin
if not Terminated then begin
try
ThreadApplication:=TApplication.Create(nil);
try
ThreadApplication.Initialize;
ThreadApplication.CreateForm(TMyForm, Form);
ThreadApplication.Run;
finally
ThreadApplication.Free;
end;
finally
Terminate;
end;
end;
end;
so, this is it, now we have message pump in a second GUI thread in a DLL.
Recently I found confirmation to this solution in a Delphi-Jedi blog, wrote by Christian Wimmer:
http://blog.delphi-jedi.net/2008/05/27/winlogon-notification-package/
Thank You very much.

delphi 7 closing available modal form Onactivate under certain condition

im working on a project using delphi 7, The project is a maintenance project and im not the original coder of the project, i have a situation where i need to close a available form after it had been created through code under certain situations,The form is model
here is sample code of that
var
frmStratum : TfrmStratum;
begin
if not assigned(frmStratum) then myMainForm.OnExecute(PropAction);
end;
inside myMainForm.OnExecute(PropAction); i have
frmStratum := TfrmStratum.Create(Self, Self as IStratum,inttostr(m_surveyno),Module,m_stations,false);
now the procedure TfrmStratum.FormActivate of TfrmStratum i do lots of calucaltion and write to database
var
if (bMassStratumExport) AND (bDoneOne) then
begin
//write to database..
end;
now i have to do this atleast 20 times
that is
1. Create the form
2. onactivate do database writing
3. close TfrmStratum
since it is a modal form i cannot close if below from where i create it,so i wanted to close it onactivate as soon as the step 2 is done
now i have tried this
if (bMassStratumExport) AND (bDoneOne) AND NOT (bReadyToclose) then
begin
//do database writing
if bNowClo then frmStratum.close;
end
EDIT :(edited to make the question more clear)
Onactivate of the form(frmStratum) , i want to close the modal form (frmStratum),so i do this
procedure TfrmStratum.FormActivate(Sender: TObject);
begin
if (bMassStratumExport) AND (bDoneOne) AND NOT (bReadyToclose) then
begin
//do database writing
if bNowClo then self.close;// i need to close the form after after doing database write
end
end;
but the control while bugging goes to self.close but it doesnt close the form.
how to tackle this ?
In the past when I needed to close a form during activation I posted a message to myself instead of calling self.close.
PostMessage(Self.Handle, WM_CLOSE, 0, 0);
I tried to find my original source that pointed me in this direction but I could not find it.
PostMessage will return immediately and not wait for the message to be processed. Once the OnActivate function is finished and the message Delphi processing loop processes the message close will be called on your form.
Assuming its frmStatum, being invalid that's giving you the A/V exception
if bNowClo then self.close;

Make sure Form does not get focus when routine is run

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.