Solved

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

Posted on 2009-03-31
12
705 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
 
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
Is Your Active Directory as Secure as You Think?

More than 75% of all records are compromised because of the loss or theft of a privileged credential. Experts have been exploring Active Directory infrastructure to identify key threats and establish best practices for keeping data safe. Attend this month’s webinar to learn more.

 

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

Is Your Active Directory as Secure as You Think?

More than 75% of all records are compromised because of the loss or theft of a privileged credential. Experts have been exploring Active Directory infrastructure to identify key threats and establish best practices for keeping data safe. Attend this month’s webinar to learn more.

Question has a verified solution.

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

Suggested Solutions

Introduction I have seen many questions in this Delphi topic area where queries in threads are needed or suggested. I know bumped into a similar need. This article will address some of the concepts when dealing with a multithreaded delphi database…
Whether you've completed a degree in computer sciences or you're a self-taught programmer, writing your first lines of code in the real world is always a challenge. Here are some of the most common pitfalls for new programmers.
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…
Migrating to Microsoft Office 365 is becoming increasingly popular for organizations both large and small. If you have made the leap to Microsoft’s cloud platform, you know that you will need to create a corporate email signature for your Office 365…

895 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

17 Experts available now in Live!

Get 1:1 Help Now