1

I have a secondary form that shows up while doing some heavy processing in mainform.
I send messages to the secondary form (form2) about the progress of the processing - that works fine.
I want a button on form2 to cancel the processing by closing form2 and re-setting a global variable to false. No buttons work on form2 if it is opened with form2.show (onclick and mousedown do nothing and the button does not move)
They do with form2.showmodal but that stops any processing in Mainform, it also stops seeing the normal window X to close Form2.

1
  • 7
    Put the long running stuff in a thread Commented Dec 6, 2013 at 14:14

2 Answers 2

3

This happens because main thread is busy and cannot process window messages.

You should move heavy processing in a thread and use synchronization to control it.

An ugly hack would be calling

application.processmessages; 

during heavy processing to force form message processing when main form is busy.

You'd better find an example with thread implementation and give a look at it.

Sign up to request clarification or add additional context in comments.

6 Comments

Please do not recommend application.processmessages
TApplication.ProcessMessages is not a hack.
It's not a real hack, it's just bad coding. That's why I talk about "ugly hack".
Given the context of the question, you agree that using application.ProcessMessages in this case is bad coding. This doesn't mean it's always bad coding.
application.processmessages works - the button does not move but as I send each message from mainform it finds the onclick event. Why is it ugly or bad coding?
|
1

Since I do not advocate the usage of Application.ProcessMessages, I will show you an alternative with threading. In this example, I used the excellent AsyncCalls threading library (made by Andreas Hausladen) because I like it's simplicity, an other excellent library is OmniThreadLibrary made by SO member Primož Gabrijelčič but it works only from Delphi version 2007 and up.

The example contains 2 forms, the main form with a Calculate button and a progress dialog that show a progress bar and a Cancel button. The code is made in such a way you can reuse the progress dialog for other calculations since there are no hardcoded dependencies.

.dpr code:

program SO20424238; uses Forms, u_frm_main in 'u_frm_main.pas' {Frm_main}, u_dlg_progress in 'u_dlg_progress.pas' {ProgressDialog}; {$R *.res} begin Application.Initialize; Application.MainFormOnTaskbar := True; Application.CreateForm(TFrm_main, Frm_main); Application.Run; end. 

main form :

unit u_frm_main; interface uses u_dlg_progress, AsyncCalls, Windows, Messages, SysUtils, Classes, Controls, Forms, StdCtrls; const INT_MAX_CALCULATIONS = 100; type TFrm_main = class(TForm) Btn_docalculate: TButton; procedure Btn_docalculateClick(Sender: TObject); procedure FormDestroy(Sender: TObject); private { Private declarations } CancelCalculation : Boolean; function SomeLongCalculation(OnProgress : TProgressEvent) : Integer; function ShowProgressDialog : TProgressDialog; procedure DoCalculate; procedure CancelEvent; public { Public declarations } Async : IAsyncCall; end; var Frm_main: TFrm_main; implementation {$R *.dfm} procedure TFrm_main.CancelEvent; begin // set cancelation flag CancelCalculation := True; end; procedure TFrm_main.Btn_docalculateClick(Sender: TObject); begin DoCalculate; end; function TFrm_main.ShowProgressDialog: TProgressDialog; begin Result := TProgressDialog.Create(CancelEvent); Result.ProgressBar1.Max := INT_MAX_CALCULATIONS; end; function TFrm_main.SomeLongCalculation(OnProgress : TProgressEvent) : Integer; var Index : Integer; begin // BEWARE - this function runs in a different thread // *any* call to the VCL/GUI/shared variables must happen in the main (GUI) thread // AsyncCalls make this easy by providing the EnterMainThread and LeaveMainThread functions for Index := 0 to INT_MAX_CALCULATIONS do begin Sleep(100); // replace this line with the actual calculation // now check if the user has canceled, check this in the main thread EnterMainThread; try if CancelCalculation then begin // notify progress window we are done if Assigned(OnProgress) then OnProgress(INT_MAX_CALCULATIONS); // exit calculation loop Break; end else // report actual progress if Assigned(OnProgress) then OnProgress(Index); finally LeaveMainThread; end; end; end; procedure TFrm_main.DoCalculate; var ProgressDialog : TProgressDialog; begin // create our progress dialog ProgressDialog := ShowProgressDialog; // reset cancelation flag CancelCalculation := False; // fire up calculation on a separate thread and hook up OnProgress function of our Progress dialog Async := TAsyncCalls.Invoke<TProgressEvent>(SomeLongCalculation, ProgressDialog.OnProgress); // show progress dialog, this will block all other forms from user input ProgressDialog.ShowModal; end; procedure TFrm_main.FormDestroy(Sender: TObject); begin if Assigned(Async) then Async.Forget; end; end. 

main form dfm:

object Frm_main: TFrm_main Left = 0 Top = 0 Caption = 'Threading example' ClientHeight = 82 ClientWidth = 273 Color = clBtnFace Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -11 Font.Name = 'Tahoma' Font.Style = [] OldCreateOrder = False OnDestroy = FormDestroy PixelsPerInch = 96 TextHeight = 13 object Btn_docalculate: TButton Left = 92 Top = 28 Width = 75 Height = 25 Caption = 'Calculate!' TabOrder = 0 OnClick = Btn_docalculateClick end end 

progress dialog:

unit u_dlg_progress; interface uses AsyncCalls, SysUtils, Controls, Forms, Dialogs, StdCtrls, ComCtrls, Classes; type TCancelEvent = procedure of object; TProgressEvent = procedure(Value : Integer) of object; TProgressDialog = class(TForm) ProgressBar1: TProgressBar; Btn_cancel: TButton; Label1: TLabel; procedure FormCreate(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); procedure Btn_cancelClick(Sender: TObject); private { Private declarations } FCancelEvent : TCancelEvent; public { Public declarations } procedure OnProgress(Value : Integer); constructor Create(CancelEvent : TCancelEvent); end; implementation {$R *.dfm} { TProgressDialog } procedure TProgressDialog.Btn_cancelClick(Sender: TObject); begin if Assigned(FCancelEvent) then FCancelEvent; end; procedure TProgressDialog.FormClose(Sender: TObject; var Action: TCloseAction); begin // make sure our dialog is freed after use Action := caFree; end; procedure TProgressDialog.FormCreate(Sender: TObject); begin // reset progress bar ProgressBar1.Position := 0; end; procedure TProgressDialog.OnProgress(Value: Integer); begin if Value >= ProgressBar1.Max then Close; ProgressBar1.Position := Value; Label1.Caption := IntToStr(Value); end; constructor TProgressDialog.Create(CancelEvent: TCancelEvent); begin inherited Create(nil); FCancelEvent := CancelEvent; end; end. 

progress dialog dfm:

object ProgressDialog: TProgressDialog Left = 0 Top = 0 BorderIcons = [] BorderStyle = bsDialog Caption = 'ProgressDialog' ClientHeight = 101 ClientWidth = 364 Color = clBtnFace Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -11 Font.Name = 'Tahoma' Font.Style = [] OldCreateOrder = False Position = poScreenCenter OnClose = FormClose OnCreate = FormCreate PixelsPerInch = 96 TextHeight = 13 object Label1: TLabel Left = 18 Top = 55 Width = 77 Height = 26 Caption = 'Label1' end object ProgressBar1: TProgressBar Left = 8 Top = 16 Width = 341 Height = 25 Smooth = True MarqueeInterval = 1 Step = 1 TabOrder = 0 end object Btn_cancel: TButton Left = 136 Top = 59 Width = 75 Height = 25 Cancel = True Caption = '&Cancel' TabOrder = 1 OnClick = Btn_cancelClick end end 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.