Solved

Update progress bar from thread on other than main form doesn't work

Posted on 2009-03-31
12
710 Views
Last Modified: 2013-12-03
Hello experts,

I need to perform some download functions but not to block user buttons (abort, pause, continue) and also show a progress on form. Everything is OK in test app, progress and other controls are updated from thread.

When I try the same thing from a modal form (or not mainform) controls are not updated anymore.
Any help for a beginner with threads ? Also I'm not sure my implementation of threading is ok.

unit Unit1;
 
interface
 
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Gauges, ComCtrls;
 
type
 
  // simple thread class
 TMyThread=class(TThread)
  i,j, count:integer;
  constructor create(CreateSuspended: Boolean);
  procedure execute; override;
  procedure show;
 end;
 
 
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    ProgressBar1: TProgressBar;
    Memo1: TMemo;
    Button3: TButton;
    procedure Button1Click(Sender: TObject); 
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    { Private declarations }
    procedure threaddone(Sender:TObject);
  public
    { Public declarations }
 
  end;
 
var
  Form1: TForm1;
  NewThread: TMyThread; // global thread
 
implementation
 
{$R *.dfm}
 
constructor TMyThread.Create(CreateSuspended: Boolean);
begin
  inherited;
  count := 0;
  //Priority := tpIdle;
end;
 
 
 
procedure TMyThread.execute;
var
  i, j: Integer;
begin
  FreeOnTerminate:=True;
 
  // dummy :)
  for i:=1 to 1000 do
  begin
      inc(count);
      //Synchronize(show);
      show;
      sleep(50);
  end;
 
  // about done
  count :=0;
end;
 
 
 procedure TMyThread.show;
 begin
    // if form1 is mainform, progress bar is updated, otherways nope :(
    // any help ?
    form1.ProgressBar1.Position := count;
 end;
 
 
 
 
procedure TForm1.Button1Click(Sender: TObject);
begin
    if newthread = nil then // only if newthread is not created yet
    begin
 
    NewThread:=TMyThread.Create(true); // so create a new thread, but in suspended state
    if newthread.Suspended then // if not running
    begin
        // do some stuff BEFORE starting thread
        ProgressBar1.Position := 0;
        button1.Enabled := false;
        button2.Enabled := true;
 
        // let's start THREAD
        newthread.OnTerminate := threaddone; // assign DONE event of thread
        caption := 'Thread started, ID='+inttostr(newthread.ThreadID);
        newthread.Resume; // start THREAD
 
    end;
    end;// newthread = nil
end;
 
 
 
procedure TForm1.Button2Click(Sender: TObject);
begin
    if newthread <> nil then // prevents accessing already destroyed thread
    begin
      newthread.Suspend; // pause thread
      newthread.DoTerminate; // terminate (force fires OnTerminate)
    end;
end;
 
// Fired when thread is done
procedure TForm1.threaddone(Sender: TObject);
begin
  caption := 'Thread was destroyed';
  newthread := nil;
  button1.Enabled := true;
  button2.Enabled := false;
  ProgressBar1.Position := 0;
end;
 
procedure TForm1.Button3Click(Sender: TObject);
begin
  memo1.Lines.Add('This text was added no matter THREAD is running or not.');
end;
 
end.

Open in new window

0
Comment
Question by:mxzuzu
  • 9
  • 3
12 Comments
 
LVL 37

Expert Comment

by:Geert Gruwez
ID: 24036392
you shouldn't use the form variables in a thread
basically you can't use this thread to update controls any form
actually you shouldn't let the thread update the form,
you should send a message informing it with the current status of the thread
the form should update itself

i wrote an article once, i'll post it here
0
 
LVL 37

Expert Comment

by:Geert Gruwez
ID: 24036410
A lot of questions regard threads in Delphi.  
One of the more specific questions is how to show progress of the thread.  
Updating a progressbar from inside a thread is a mistake.
A solution to this would be to send a synchronized message to the main thread.
This message would then contain the progress of the thread from 0 to 100.

1
    Setting the progress bar


    The progressbar will be displaying from 0 to 100


Property Max = 100
Property Min = 0

2
    The base thread: TProgressThread


    I defined a thread class for sending messages back via a synchronize procedure


type
  TProgressProc = procedure (aProgress: Integer) of object; // 0 to 100
 
  TProgressThread = class(TThread)
  private
    FProgressProc: TProgressProc;
    FProgressValue: integer;
    procedure SynchedProgress;
  protected
    procedure Progress(aProgress: integer); virtual;
  public
    constructor Create(aProgressProc: TProgressProc; CreateSuspended: Boolean = False); reintroduce; virtual;
  end;
 
{ TProgressThread }
 
constructor TProgressThread.Create(aProgressProc: TProgressProc; CreateSuspended: Boolean = False); 
begin
  inherited Create(CreateSuspended);
  FreeOnTerminate := True;
  FProgressProc := aProgressProc;
end;
 
procedure TProgressThread.Progress(aProgress: Integer);
begin
  FProgressValue := aProgress;
  Synchronize(SynchedProgress);
end;
 
procedure TProgressThread.SynchedProgress;
begin
  if Assigned(FProgressProc) then
    FProgressProc(FProgressValue);
end;

Open in new window

0
 
LVL 37

Expert Comment

by:Geert Gruwez
ID: 24036412
   Define your own thread: TMyThread


    Now define your own thread as a descendant of the TProgressThread.
    This thread just counts to 100 over 100 seconds


 
type
  TMyThread = class(TProgressThread)
  protected
    procedure Execute; override;
  end; 
procedure TMyThread.Execute; 
var I: Integer;
begin
  Progress(0);
  for I := 1 to 100 do 
  begin 
    Sleep(1000);
    Progress(I);
  end;
end;

Open in new window

0
Salesforce Made Easy to Use

On-screen guidance at the moment of need enables you & your employees to focus on the core, you can now boost your adoption rates swiftly and simply with one easy tool.

 
LVL 37

Expert Comment

by:Geert Gruwez
ID: 24036413
Define the procedure to adjust the progress bar position


This procedure will receive the messages from the progress thread.
type  
  TForm1 = class(TForm)
    ProgressBar1: TProgressBar;
  private
    procedure UpdateProgressBar(aProgress: Integer);
  end; 
procedure TForm1.UpdateProgressBar(aProgress: Integer);
begin
  ProgressBar1.Position := aProgress;
  ProgressBar1.Update; // Make sure to repaint the progressbar
end;

Open in new window

0
 
LVL 37

Expert Comment

by:Geert Gruwez
ID: 24036414
Start a thread from the form


This starts the progress thread
type
  TForm1 = class(TForm)
    btnStart: TButton;
    procedure btnStartClick(Sender: TObject);
  private
    fMyThread: TMyThread;
  end;   
procedure TForm1.btnStartClick(Sender: TObject);
begin
  if not Assigned(fMyThread) then 
    fMyThread := TMyThread.Create(UpdateProgressbar);
end;

Open in new window

0
 
LVL 37

Accepted Solution

by:
Geert Gruwez earned 500 total points
ID: 24036415
   Complete code


    Here is a copy of the complete unit


unit Unit1; 
interface 
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ComCtrls, StdCtrls; 
type
  TProgressProc = procedure (aProgress: Integer) of object; // 0 to 100 
  TProgressThread = class(TThread)
  private
    FProgressProc: TProgressProc;
    FProgressValue: integer;
    procedure SynchedProgress;
  protected
    procedure Progress(aProgress: integer); virtual;
  public
    constructor Create(aProgressProc: TProgressProc; CreateSuspended: Boolean = False); reintroduce; virtual;
  end; 
  TMyThread = class(TProgressThread)
  protected
    procedure Execute; override;
  end; 
  TForm1 = class(TForm)
    btnStart: TButton;
    ProgressBar1: TProgressBar;
    procedure btnStartClick(Sender: TObject);
  private
    fMyThread: TMyThread;
    procedure UpdateProgressBar(aProgress: Integer);
  end; 
var
  Form1: TForm1; 
implementation 
{$R *.dfm} 
{ TProgressThread } 
constructor TProgressThread.Create(aProgressProc: TProgressProc; CreateSuspended: Boolean = False);
begin
  inherited Create(CreateSuspended);
  FreeOnTerminate := True;
  FProgressProc := aProgressProc;
end; 
procedure TProgressThread.Progress(aProgress: Integer);
begin
  FProgressValue := aProgress;
  Synchronize(SynchedProgress);
end; 
procedure TProgressThread.SynchedProgress;
begin
  if Assigned(FProgressProc) then
    FProgressProc(FProgressValue);
end; 
{ TMyThread } 
procedure TMyThread.Execute;
var I: Integer;
begin
  Progress(0);
  for I := 1 to 100 do
  begin
    Sleep(1000);
    Progress(I);
  end;
end; 
{ TForm1 } 
procedure TForm1.UpdateProgressBar(aProgress: Integer);
begin
  ProgressBar1.Position := aProgress;
  ProgressBar1.Update; // Make sure to repaint the progressbar
  if aProgress >= 100 then
    fMyThread := nil;
end; 
procedure TForm1.btnStartClick(Sender: TObject);
begin
  if not Assigned(fMyThread) then
    fMyThread := TMyThread.Create(UpdateProgressBar);
end; 
end.

Open in new window

0
 

Author Comment

by:mxzuzu
ID: 24038947
I searched a lot for a good tutorial but none are for beginners and most important, code is not commented so we (beginners) cannot understand well mechanisms involved. I am some kind familiar as MCU programmer but win programming can overkill me sometimes.  

Anyhow, I'll try these days your solution and I'll post here if any problems. Thank you for your effort.
0
 
LVL 37

Expert Comment

by:Geert Gruwez
ID: 24038964
the general comments are above the code ...
0
 

Author Closing Comment

by:mxzuzu
ID: 31565167
I hope you can future guide me if any problems. Thanks a lot.
0
 
LVL 37

Expert Comment

by:Geert Gruwez
ID: 24038981
Author Comments:
I hope you can future guide me if any problems. ...

if any problems you can still post here,
i should see it , otherwise a new Q in Delphi Programming will get my attention too :)
0
 

Author Comment

by:mxzuzu
ID: 24039204
It's too interesting to probe right now.... ok so let see if I understand well :

1) You created a thread class TProgressThread derived form TThread.
( why 'reintroduce' in constructor ? )

2) You create another one TMyThread derived from TProgressThread only with 'execute'
( why derived twice, not just TProgressThread with 'execute' method ? )

3) How can safely PAUSE / CONTINUTE and STOP (kill) thread already running from mainform.

4) Above code works if mainform is some non- principal form in my app ?

5) The download code which I wanted to run in some thread should I put in TMyThread.Execute ?
This code may acces global vars of my program, it's safe ? It's suppose to give commands over already connected channel (buy I'd like to have possibility to check if socket is still connected to remote), retrieve some data packets and save to some file.

This already works as draft in my app. but problem is suppose I have to download 100 packets from remote. Normally I put a fol loop and give command to remote -> receive packet -> save -> again....

I want give user chance to abort any time this process. Hope you understand.

6) This task in thread I need for 1 session only, my remote doesn't allow multi user, and my app once connected, just downloads data. Eg. 1 PC app and 1 remote, no multi user, no multi connections, no database, etc. So once if I ALREADY KNOW behavior why not use a simpler threading scheme and update my progress from thread ? Just asking.
 

Tnx,




 
0
 
LVL 37

Expert Comment

by:Geert Gruwez
ID: 24039346
1) reintroduce because i changed the parameters (override doesn't work then) and i only want my constructor visible to descendants
2) i don't want to reinvent the wheel every time
   i wanted a thread with a message for a progress using a procedural variable
    if i create a thread and need that functionality i just derive from TProgressThread
  just assume you have 2 threads which do totally different things,
  but need both to notify the progress
  this way you only need to implement the progress system once
3) don't follow this one ...
4) works for anything, even an object, the thread just calls the procedure you give it (synchronized)
5) yes, but you may need to adapt to allow other messages to be sent
6) basically your thread should do a task and once started use it's own variables, at start (before execute), done, synchronized or controlled with Critical sections it can access global vars otherwise no
0

Featured Post

Free Tool: Site Down Detector

Helpful to verify reports of your own downtime, or to double check a downed website you are trying to access.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

Title # Comments Views Activity
Press three keys together and trigger a function 3 62
What is MicroStrategy.NET? 2 73
tidtcpserver connection lost handle 2 103
Tvertscrollbox like a whatsapp layout 5 36
In this tutorial I will show you how to use the Windows Speech API in Delphi. I will only cover basic functions such as text to speech and controlling the speed of the speech. SAPI Installation First you need to install the SAPI type library, th…
Hello everybody This Article will show you how to validate number with TEdit control, What's the TEdit control? TEdit is a standard Windows edit control on a form, it allows to user to write, read and copy/paste single line of text. Usua…
This is Part 3 in a 3-part series on Experts Exchange to discuss error handling in VBA code written for Excel. Part 1 of this series discussed basic error handling code using VBA. http://www.experts-exchange.com/videos/1478/Excel-Error-Handlin…

830 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question