Solved

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

Posted on 2009-03-31
12
720 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
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 9
  • 3
12 Comments
 
LVL 38

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 38

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 38

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
Get your Conversational Ransomware Defense e‑book

This e-book gives you an insight into the ransomware threat and reviews the fundamentals of top-notch ransomware preparedness and recovery. To help you protect yourself and your organization. The initial infection may be inevitable, so the best protection is to be fully prepared.

 
LVL 38

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 38

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 38

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 38

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 38

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 38

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

[Webinar] Learn How Hackers Steal Your Credentials

Do You Know How Hackers Steal Your Credentials? Join us and Skyport Systems to learn how hackers steal your credentials and why Active Directory must be secure to stop them. Thursday, July 13, 2017 10:00 A.M. PDT

Question has a verified solution.

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

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…
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…
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…
There are cases when e.g. an IT administrator wants to have full access and view into selected mailboxes on Exchange server, directly from his own email account in Outlook or Outlook Web Access. This proves useful when for example administrator want…
Suggested Courses
Course of the Month6 days, 1 hour left to enroll

626 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