I have to develop a launcher for an exe file but I have some trouble with the closing of the mainform.
I want to keep open the executable but close the form.
I achieved success with the execution of the application, the .exe is executed and the form is "closed" after the .exe is open. It is almost what I wanted but the launcher.exe is still active in the windows Task Manager.
This is the procedure for executing the .exe :
procedure TForm2.LancerVersion(aExe: String);
var
SEInfo: TShellExecuteInfo;
begin
FillChar(SEInfo, SizeOf(SEInfo), 0);
SEInfo.cbSize := SizeOf(TShellExecuteInfo);
with SEInfo do
begin
fMask := SEE_MASK_NOCLOSEPROCESS;
Wnd := Application.Handle;
lpFile := PChar(aExe);
nShow := SW_SHOWNORMAL;
end;
ShellExecuteEx(#SEInfo);
if Blight then
begin
free;
Close; **//HERE I WOULD LIKE TO CLOSE CLEANLY MY FORM**
end
else
hide;
end;
This is the custom procedure for the closing :
procedure TForm2.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
if BClose then
begin
Canclose := false;
Bshow := false;
end;
Canclose := true; **//IT GOES HERE AFTER CLOSE IS CALLED**
end;
I have written this custom procedure because sometimes I just want to hide the form in the trayicon in function of a parameter when we click on the X window's button. So, don't care about the first condition "if Bclose then".
I ensured myself to free all my object in the FormDestroy that I've created in the FormCreate but nothing to do, the processus persist...
I'll appreciate if you could help me or just if you look at my problem.
Thank you in advance..
Here is a small working SSCCE:
procedure TForm1.Button1Click(Sender: TObject);
var
SEInfo: TShellExecuteInfo;
ExecuteFile: string;
begin
ExecuteFile := 'notepad.exe';
FillChar(SEInfo, SizeOf(SEInfo), 0);
SEInfo.cbSize := SizeOf(TShellExecuteInfo);
with SEInfo do
begin
Wnd := Application.Handle;
lpFile := PChar(ExecuteFile);
nShow := SW_SHOWNORMAL;
end;
Win32Check(ShellExecuteEx(#SEInfo));
Close;
end;
The problem lies in the fact that you are calling Free in your procedure, don't do that.
Related
I need to create frames at runtime, there are 44 frames so I intend to use multithreading for that. Now we have 44 Tabsheets on PageControls which takes so long to load.
But I can not.
The idea is as follows:
I have OnCreate from my main form:
procedure TFConfMERC.FormCreate(Sender: TObject);
begin
//Here you should instantiate the threads that will instantiate the frames and then only proceed with the main thread when all other threads have instantiated the frames of this form
end;
Is it possible to implement this?
I need threads because the form takes 20 seconds to open, and we need to improve performance on this.
OBS: This code works on Delphi 11, but it gets stuck in Delphi XE 7.
var
Tasks: Array[0..1] of ITask;
begin
Tasks[0] := TTask.Create(procedure
begin
frame2 := TFrame2.Create(Self); //stuck here
frame2.parent := TabSheet1;
sleep(5000);
end);
Tasks[1] := TTask.Create(procedure
begin
frame3 := TFrame3.Create(Self); //stuck here
frame3.parent := TabSheet2;
sleep(5000);
end);
Tasks[0].Start;
Tasks[1].Start;
TTask.WaitForAll(Tasks);
Here is the problem solved!!!!!! =D
procedure TForm1.FormCreate(Sender: TObject);
var
T1: TThread;
T2: TThread;
FSemaforo: TSemaphore;
begin
FSemaforo := TSemaphore.Create(nil, 2, 2, '');
T1 := TThread.CreateAnonymousThread(procedure
begin
FSemaforo.Acquire;
try
TThread.Sleep(5000); //Teste de paralelismo
TThread.Synchronize(TThread.CurrentThread,
procedure
begin
frame3 := TFrame3.Create(Self);
frame3.Parent := Panel2;
end);
finally
FSemaforo.Release;
end;
end);
T2 := TThread.CreateAnonymousThread(procedure
begin
FSemaforo.Acquire;
try
TThread.Sleep(5000); //Teste de paralelismo
TThread.Synchronize(TThread.CurrentThread,
procedure
begin
frame2 := TFrame2.Create(Self);
frame2.Parent := Panel1;
end);
finally
FSemaforo.Release;
end;
end);
T1.FreeOnTerminate := False;
T2.FreeOnTerminate := False;
T1.Start;
T2.Start;
{wait the threads to finish, then proceed =D}
while (not t1.Finished) or (not t2.Finished) do
begin
Application.ProcessMessages;
end;
T1.Free;
T2.Free;
FSemaforo.Free;
end;
My form contains a TIdSMTP, TIdMessage, TOpenDialog, SSL Handlers, and other visual components. I also have buttons for sending and attaching the different files.
procedure TForm1.Button1Click(Sender: TObject);
begin
email_connecter_TIdSMTP.Host := entered_host_TEdit.Text;
email_connecter_TIdSMTP.Username := entered_username_TEdit.Text;
email_connecter_TIdSMTP.Password := entered_password_TEdit.Text;
message_parts_TIdMessage.Clear();
message_parts_TIdMessage.Recipients.EMailAddresses := to_sender_email_TEdit.Text;
message_parts_TIdMessage.Subject := email_subject_TEdit.Text;
message_parts_TIdMessage.Body.Text := email_body_message_TMemo.Text;
email_connecter_TIdSMTP.Connect();
email_connecter_TIdSMTP.Send(message_parts_TIdMessage);
email_connecter_TIdSMTP.Disconnect();
end;
procedure TForm1.Button3Click(Sender: TObject);
var t:textfile;
s:string;
selected_file:string;
attatchment_message: TIdMessageBuilderHtml;
begin
selected_file := '';
try
attatchment_finder_TOpenDialog.InitialDir := 'C:\Documents';
attatchment_finder_TOpenDialog.Filter := 'All files (*.*)|*.*';
if attatchment_finder_TOpenDialog.Execute(Handle) then
selected_file := attatchment_finder_TOpenDialog.FileName;
if selected_file <>'' then
attatchment_message := TIdMessageBuilderHtml.Create;
attatchment_message.HtmlContentTransfer := 'quoted-printable';
memo_attachment_box_TMemo.Lines.Add(selected_file);
attatchment_message.Attachments.Add(selected_file);
attatchment_message.FillMessage(message_parts_TIdMessage);
finally
attatchment_finder_TOpenDialog.Free;
end;
end;
end.
What am I doing wrong when adding my selected file? How can I make it so that I can send any file type?
When I click send on my program it adds the file directory text to my memo box but it doesn't actually attach the file onto my TIdMessage component.
thank you!
The code you have shown clears the entire TIdMessage just before sending it, wiping out any attachments that Button3Click() may have added beforehand.
In fact, Button3Click() shouldn't be doing anything with the TIdMessage directly at all. That responsibility belongs solely in Button1Click() when it is populating the TIdMessage after clearing it.
Also, you are not using TIdMessageBuilderHtml correctly. You should be using its PlainText or HTML property (depending on what kind of text you are sending) instead of setting the TIdMessage.Body directly. Without that, FillMessage() doesn't set the TIdMessage.ContentType correctly. If you are sending plain text instead of HTML, you should be using TIdMessageBuilderPlain instead.
Try something more like this:
procedure TForm1.Button1Click(Sender: TObject);
var
email_builder: TIdMessageBuilderPlain;
I: integer;
begin
email_connecter_TIdSMTP.Host := entered_host_TEdit.Text;
email_connecter_TIdSMTP.Username := entered_username_TEdit.Text;
email_connecter_TIdSMTP.Password := entered_password_TEdit.Text;
message_parts_TIdMessage.Clear;
message_parts_TIdMessage.Recipients.EMailAddresses := to_sender_email_TEdit.Text;
message_parts_TIdMessage.Subject := email_subject_TEdit.Text;
email_builder := TIdMessageBuilderPlain.Create;
try
email_builder.PlainText.Assign(email_body_message_TMemo.Lines);
email_builder.PlainTextContentTransfer := 'quoted-printable';
for I := 0 to memo_attachment_box_TMemo.Lines.Count-1 do
email_builder.Attachments.Add(memo_attachment_box_TMemo.Lines[I]);
email_builder.FillMessage(message_parts_TIdMessage);
finally
email_builder.Free;
end;
email_connecter_TIdSMTP.Connect;
try
email_connecter_TIdSMTP.Send(message_parts_TIdMessage);
finally
email_connecter_TIdSMTP.Disconnect;
end;
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
attatchment_finder_TOpenDialog.InitialDir := 'C:\Documents';
attatchment_finder_TOpenDialog.Filter := 'All files (*.*)|*.*';
if attatchment_finder_TOpenDialog.Execute(Handle) then
begin
memo_attachment_box_TMemo.Lines.Add(attatchment_finder_TOpenDialog.FileName);
// or, if ofAllowMultiSelect is enabled:
// memo_attachment_box_TMemo.Lines.AddStrings(attatchment_finder_TOpenDialog.Files);
end;
end;
I have a TAction.OnExecute event handler triggered from my main form,
FormPrincipal, which opens other Forms.
procedure TFormPrincipal.AbreFormBancoExecute(Sender: TObject);
begin
Formbanco := Tformbanco.Create(self);
Formbanco.Parent := PanelCorpo;
Formbanco.Align := alclient;
Formbanco.BorderIcons := [];
Formbanco.Show;
Formbanco.BorderStyle := bsNone;
Formbanco.SetFocus;
end;
Once I'll have several forms, how to know which one is opened and how to close it, before triggering OnExecute to open another Form?
=========== Finally it is Working as I expected =======
The main form is form1 from which I call form2 and form3. In form1 I have a panel1 which is parent of form2 and form3. See form1 code :
...
var
Form1: TForm1;
implementation
{$R *.dfm}
uses unit2, unit3;
procedure Tform1.CloseActiveForm (Formname : string);
// Free memory allocated to the current form , set it to nil
// I'll have to find a better way to perform FreeanNil without
// use many IFs command
begin
if Formname = 'form2' then FreeAndnil(Form2) else
if Formname = 'form3' then FreeandNil(Form3);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
CloseActiveForm(Edit1.Text); //Edit1 has the current active form name
if form2 = nil then
begin
Application.CreateForm(Tform2,Form2);
Form2.Parent := Panel1;
Form2.Align := alclient;
Form2.Show;
Form2.BorderStyle := bsnone;
Form2.SetFocus;
Form2.OnActivate(Sender); //Method Show blocks Activate event
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
CloseActiveForm(Edit1.Text); //Edit1 has the current active form name
if form3 = nil then
begin
Application.CreateForm(Tform3,Form3);
Form3.Parent := Panel1;
Form3.Align := alclient;
Form3.Show;
Form3.BorderStyle := bsnone;
Form3.SetFocus;
Form3.OnActivate(Sender); //Method Show blocks Activate event
end;
end;
procedure TForm1.FormActivate(Sender: TObject);
begin
Edit1.Text := Screen.ActiveForm.Name;
end;
end.
2) Code in form2 and form3 (consider form3 is identical)
...
var
Form2: TForm2;
implementation
{$R *.dfm}
uses unit1;
procedure TForm2.Button1Click(Sender: TObject);
begin
Edit2.Text := Screen.ActiveForm.Name;
end;
procedure TForm2.FormActivate(Sender: TObject);
begin
setfocus;
Edit1.Text := Form2.Name;
Form1.Edit1.Text := Form2.Name; //set form name
// the property Screen.ActiveForm.Name if used here, will always presents
// form1 name (main form) because this form2 is set to a parent panel
// in form1
end;
end.
Destroy the form if it exist and create a new instance of it.
procedure TFormPrincipal.AbreFormBancoExecute(Sender: TObject);
procedure CreateFormbanco;
begin
Formbanco := TFormbanco.Create(self);
Formbanco.Parent := PanelCorpo;
Formbanco.Align := alclient;
Formbanco.BorderIcons := [];
Formbanco.BorderStyle := bsNone;
Formbanco.Show;
Formbanco.SetFocus;
Formbanco.OnDestroy := FormDestroyEvent;
end;
begin
if not Assigned(Formbanco) then
begin
CreateFormbanco;
end
else
begin
Formbanco.Destroy;
CreateFormbanco;
end;
procedure TFormPrincipal.FormDestroyEvent(Sender: TObject);
begin
Formbanco := nil;
end;
This code will check if Formbanco existed, if so it will destroy it and create a new instance of it otherwise it will create a new one.
Edit: create different forms and use the code above, just change Formbanco and TFormbanco to their respected new form name.
Some of my forms can be shown both as normal and modal forms.
In case they're showing as modal forms, I have to hide some components which are not usefull in modal state.
if(fsModal in Self.FormState) then
begin
//hiding some components...
end;
I would like to execute my code before the form is drawn, in order to avoid it's drawn more times unnecessarily.
I thought that OnShow executed before the form is visible, but it seems that is not so. So you can do this:
TMyForm = class( TForm ) // this will already be in your source
public
function ShowModal: Integer; override;
end;
function TMyForm.ShowModal: Integer;
begin
// hide some components
Result := inherited;
// show them again in case next time it is a Show
end;
You can't override Show in the same way - you would have to override the visible property, so easier to reset the visibility of the components as shown.
You can write some initial procedure for two type of showing:
(in Form)
procedure TfrmForm01.Init(p_Modal: Boolean);
begin
if p_Modal then
begin
edtForModalForm.Visible := False; // hide some components
ShowModal;
end
else
Show;
end;
and you can call form by parameter. True for Modal, False for NoModal form:
(In main program)
procedure TForm1.btnShowFormClick(Sender: TObject);
var
v_F : TfrmForm01;
begin
v_F := TfrmForm01.Create(self);
v_F.Init(True);
end;
procedure TForm1.btnShowModalFormClick(Sender: TObject);
var
v_F : TfrmForm01;
begin
v_F := TfrmForm01.Create(self);
v_F.Init(False);
end;
I wrote and tested this example in Delphi7.
How to close properly a TIdTCPServer with Indy/Lazarus if we press a 'Close' button in the GUI?. Thanks for helping! (Changed my original question)
How to close the TIdTCPServer if a client disconnects?
Should the exception handle anything?
The IO works but it's a bit unstable yet.
Here is the code below:
unit pt_socket;
{$mode objfpc}//{$H+}
interface
uses
Classes, SysUtils,
Forms,
IdGlobal,
IdContext, IdComponent,
IdTCPServer, IdTCPClient,
Controls, Graphics, Dialogs;
type
TSocket = class
private
IdTcpServer1: TIdTCPServer;
IdTcpClient1: TIdTCPClient;
procedure IdTCPServer1Connect(AContext: TIdContext);
procedure IdTCPServer1Disconnect(AContext: TIdContext);
//procedure IdTCPServer1Exception(AContext: TIdContext; AException: Exception);
procedure IdTCPServer1Execute(AContext: TIdContext);
public
procedure init;
function Open: boolean;
procedure Close;
function Write(str: TByteArray; len: integer): integer;
end;
var
lst: Tlist;
implementation
uses main, pt_settings, pt_ctlpanel, pt_terminal;
procedure TSocket.init;
begin
end;
procedure TSocket.IdTCPServer1Connect(AContext: TIdContext);
begin
MainApp.GuiPortOpen;
lst := IdTcpServer1.Contexts.LockList;
end;
procedure TSocket.IdTCPServer1Disconnect(AContext: TIdContext);
begin
IdTcpServer1.Contexts.UnlockList;
MainApp.GuiPortClose;
end;
//procedure TSocket.IdTCPServer1Exception(AContext: TIdContext; AException: Exception);
//begin
// MainApp.GuiPortClose;
//end;
procedure TSocket.IdTCPServer1Execute(AContext: TIdContext);
var
Socket_Receive_Buffer: TIdBytes;
Socket_Input_Length: integer;
Input_Buffer: TByteArray;
begin
with AContext.Connection do
begin
IOHandler.ReadBytes(Socket_Receive_Buffer, -1, false);
Socket_Input_Length := Length(Socket_Receive_Buffer);
if Socket_Input_Length > 0 then
begin
BytesToRaw(Socket_Receive_Buffer,Input_Buffer,Socket_Input_Length);
Terminal.GuiTerminalPutInput(Input_Buffer, Socket_Input_Length);
end;
end;
end;
function TSocket.Open: boolean;
begin
if Settings.SocketModeRadioGroup.ItemIndex = 0 then
begin
IdTcpServer1 := TIdTCPServer.Create(nil);
IdTCPServer1.OnExecute := #IdTCPServer1Execute;
IdTCPServer1.OnConnect := #IdTCPServer1Connect;
IdTCPServer1.OnDisconnect := #IdTCPServer1Disconnect;
//IdTcpServer1.OnException := #IdTCPServer1Exception;
IdTcpServer1.DefaultPort := StrToInt(Settings.SocketPortEdit.Text);
IdTcpServer1.MaxConnections := 1;
IdTCPServer1.Bindings.Add.IPVersion := Id_IPv4;
IdTcpServer1.Active := True;
end
else
begin
IdTcpClient1 := TIdTCPClient.Create(nil);
//IdTcpClient1.DefaultPort := StrToInt(Settings.SocketPortEdit.SelText);
end;
end;
procedure TSocket.Close;
begin
if Settings.SocketModeRadioGroup.ItemIndex = 0 then
begin
IdTcpServer1.Destroy;
end
else
begin
IdTcpClient1.Destroy;
end;
end;
function TSocket.Write(str: TByteArray; len: integer): integer;
var
Socket_Transmit_Buffer: TIdBytes;
begin
Socket_Transmit_Buffer := RawToBytes(str,len);
if len > 0 then
// Only one connection by design
with TIdContext(lst.Items[0]).Connection do
begin
IOHandler.Write(Socket_Transmit_Buffer);
end;
Result := len;
end;
end.
This code has tons of mistakes in it. Misuse of the Contexts list. Improper use of BytesToRaw() and RawToBytes(). Thread-unsafe GUI logic in worker threads. This code is very prone to memory corruption and deadlocks. It is no wonder your code is unstable. You need to fix that.
To answer your specific questions:
How to close properly a TIdTCPServer with Indy/Lazarus if we press a 'Close' button in the GUI?.
Simply deactivate/destroy the server. It will automatically close any active client connection. However, due to the multi-threaded nature of TIdTCPServer you have to make sure NOT to block any of the server's event handlers during deactivation, or you will deadlock your code. If the event handlers have to sync with the main thread while the main thread is deactivating the server, use asynchronous syncs (TThread.Queue(), TIdNotify, etc) or do the deactivation in a worker thread so the main thread is not blocked. Also, if you need to catch exceptions in your event handlers, be sure to re-raise any EIdException-derived exception you catch and let the server handle it, otherwise the client threads will not terminate correctly, deadlocking deactivation as well.
How to close the TIdTCPServer if a client disconnects?
The server cannot be deactivated from inside its own events (deadlock). You will have to perform the deactivation asynchronously. In the OnDisconnect event, you can send yourself an asynchronous signal so the event handler can exit, and then deactivate the server when the signal is processed. Or spawn a worker thread to perform the deactivation.