Solved

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

Posted on 2009-03-31
12
701 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 36

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 36

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 36

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
 
LVL 36

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 36

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 36

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
6 Surprising Benefits of Threat Intelligence

All sorts of threat intelligence is available on the web. Intelligence you can learn from, and use to anticipate and prepare for future attacks.

 

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 36

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 36

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 36

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 Trending Threat Insights Every Day

Enhance your security with threat intelligence from the web. Get trending threat insights on hackers, exploits, and suspicious IP addresses delivered to your inbox with our free Cyber Daily.

Join & Write a Comment

As more and more people are shifting to the latest .Net frameworks, the windows presentation framework is gaining importance by the day. Many people are now turning to WPF controls to provide a rich user experience. I have been using WPF controls fo…
Entering time in Microsoft Access can be difficult. An input mask often bothers users more than helping them and won't catch all typing errors. This article shows how to create a textbox for 24-hour time input with full validation politely catching …
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…
When you create an app prototype with Adobe XD, you can insert system screens -- sharing or Control Center, for example -- with just a few clicks. This video shows you how. You can take the full course on Experts Exchange at http://bit.ly/XDcourse.

747 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

Need Help in Real-Time?

Connect with top rated Experts

12 Experts available now in Live!

Get 1:1 Help Now