Entwickler-Ecke

Grafische Benutzeroberflächen (VCL & FireMonkey) - Progressbar in eigenem Thread


LittleBen - Do 08.09.11 11:07
Titel: Progressbar in eigenem Thread
Hallo,
wollte euch fragen, ob das so OK ist, wie die Progressbar laufen lasse:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
type
  TMyThread = class(TThread)
  private
    FProgressBar: TProgressBar;
    procedure SetProgressBar(const Value: TProgressBar) ;
  protected
    procedure execute; override;
  public
    constructor Create;
    property ProgressBar: TProgressBar read FProgressBar write SetProgressBar;
  end;


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
procedure TMyThread.Execute;
var n: integer;
begin
 try
  FProgressbar.Position:= 0;
  FProgressbar.Max:= 100;

  for n:= 0 to 100 do
   FProgressbar.Position:= FProgressbar.Position+1;
 except
  on e: exception do;
 end;
end;

constructor TMyThread.Create;
begin
 inherited Create(true); 
 FreeOnTerminate:= true;
end;


Delphi-Quelltext
1:
2:
3:
4:
procedure TMyThread.SetProgressBar(const Value: TProgressBar) ;
begin
 FProgressBar:= Value;
end;
Kann ich das so machen, ohne die Regeln des Threaden zu brechen?

Viele Grüße,
Benny


Moderiert von user profile iconNarses: Topic aus Sonstiges (Delphi) verschoben am Do 08.09.2011 um 21:27


Gausi - Do 08.09.11 11:41

Ein ganz klares "Nein": Eine Progressbar ist eine visuelle Komponente, und es gilt:

Quelltext
1:
Visuelle Komponenten + zweiter Thread = Furchtbar Böse™.                    

Zugriff auf die VCL von einem zweiten Thread aus ist nur per Synchronize oder vergleichbaren Techniken erlaubt.


LittleBen - Do 08.09.11 11:44

Ah, also habe ichs doch falsch gemacht. Also brauche ich eine Procedure die in der Schleife Synchronisiert wird?


jaenicke - Do 08.09.11 12:07

Nein, übergib dem Thread einfach das Handle der ProgressBar und setze mit PBM_SETPOS direkt die Position. ;-)
http://msdn.microsoft.com/en-us/library/bb760844.aspx

// EDIT:
Das sieht so aus als würdest du hier den Thread pausiert starten, mit Resume fortsetzen und per FreeOnTerminate freigeben? Ganz böse. ;-)
(Ja, ich weiß, bei deinen Delphiversionen war Resume noch nicht entsprechend markiert. ;-))

Übergib das Handle lieber in einem eigenen Konstruktor und lasse den Thread sofort loslaufen.


LittleBen - Do 08.09.11 12:14

Wie ist das denn passiert? Entschuldigung für den Doppelpost. War keine absicht!


LittleBen - Do 08.09.11 12:32

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Das sieht so aus als würdest du hier den Thread pausiert starten, mit Resume fortsetzen und per FreeOnTerminate freigeben? Ganz böse.
Echt, ist das böse? Habe es so in irgendwelchen Tutorials gesehen. Wie mache ich es dann?

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Nein, übergib dem Thread einfach das Handle der ProgressBar und setze mit PBM_SETPOS direkt die Position.
Mhh, ist das nicht ein bisschen unsauber?

EDIT: Darf ich das so machen?

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
procedure TMyThread.Execute;
var n: integer;
begin
 try
  for n:= 0 to 100 do
   Synchronize(Refresh);
 except
  on e: exception do;
 end;
end;

procedure TMyThread.Refresh;
begin
 FProgressbar.Position:= FProgressbar.Position + 1;
end;

constructor TMyThread.Create;
begin
 inherited Create(true); 
 FreeOnTerminate:= true;
end;


Andreas L. - Do 08.09.11 13:04

Sinnvoller wäre es über ein Ereignis:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
  TMyOnProgressEvent = procedure(APos, AMax: Integer) of object;
  
  TMyThread = class(TThread)
  private
    FOnProgress: TMyOnProgressEvent;
  published
    property OnProgress: TMyOnProgressEvent read FOnProgress write FOnProgress;
  end;

...

procedure TMyThread.Execute;
begin

  for i := 0 to 100 do
  begin
    if Assigned(FOnprogress) then
      Synchronize(OnProgress(i, 100));
  end;


end;


LittleBen - Do 08.09.11 13:13

Dann bekomme ich die Fehlermeldung: [Fehler] U_Main.pas(120): Es gibt keine überladene Version von 'Synchronize', die man mit diesen Argumenten aufrufen kann

EDIT:

So darf ich das doch machen, oder?


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
type
  TMyThread = class(TThread)
  private
    FProgressBar: TProgressBar;
    procedure DoProgress;
    procedure SetProgressBar(const Value: TProgressBar) ;
  protected
    procedure execute; override;
  public
    constructor Create;
    property ProgressBar: TProgressBar read FProgressBar write SetProgressBar;
  end;


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
procedure TMyThread.Execute;
var n: integer;
begin
 try
  for n:= 0 to 100 do
     Synchronize(DoProgress) ;
  except
  on e: exception do;
 end;

end;


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
procedure TMyThread.SetProgressBar(const Value: TProgressBar) ;
begin
 FProgressBar:= Value;
end;

procedure TMyThread.DoProgress;
begin
 FProgressBar.Position:= FProgressBar.Position+1;
end;


jaenicke - Do 08.09.11 13:39

Synchronisieren ist hier wenig sinnvoll...

Schließlich soll die Operation durch den Thread doch gerade vom Hauptthread abgekoppelt werden. Und dann für die Fortschrittsanzeige zu synchronisieren... :shock:

user profile iconLittleBen hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Das sieht so aus als würdest du hier den Thread pausiert starten, mit Resume fortsetzen und per FreeOnTerminate freigeben? Ganz böse.
Echt, ist das böse? Habe es so in irgendwelchen Tutorials gesehen. Wie mache ich es dann?
Das Problem ist, dass der Thread schon fertig sein kann, bevor Resume selbst durch ist. Und dann knallt es.
Deshalb ist Resume mittlerweile auch als veraltet markiert. Es sollte schlicht nie verwendet werden. Wozu auch? Die Startparameter kannst du als Parameter mitgeben und warten kannst du mit TEvent oder ähnlichem, wenn der Start verzögert werden soll.

user profile iconLittleBen hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Nein, übergib dem Thread einfach das Handle der ProgressBar und setze mit PBM_SETPOS direkt die Position.
Mhh, ist das nicht ein bisschen unsauber?
Nein, warum? Das ist genau der beste Weg hier.


LittleBen - Do 08.09.11 13:58

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Synchronisieren ist hier wenig sinnvoll...
Schließlich soll die Operation durch den Thread doch gerade vom Hauptthread abgekoppelt werden. Und dann für die Fortschrittsanzeige zu synchronisieren... :shock:
Stimmt, das macht kein Sinn. Hab ich von hier abgeschaut: http://delphi.about.com/od/kbthread/a/thread-gui.htm

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Nein, übergib dem Thread einfach das Handle der ProgressBar und setze mit PBM_SETPOS direkt die Position.
Kannst du mir das bitte erklären. Verstehe das mit dem PBM_SETPOS nicht.


jaenicke - Do 08.09.11 14:08

Ich weiß nicht genau wo jetzt das Problem liegt?

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
uses
  CommCtrl;

  TMyLittleThread = class(TThread)
  private
    FProgressBarHandle: THandle;
  protected
    procedure Execute; override;
  public
    constructor Create(AProgressBarHandle: THandle);
  end;

constructor TMyLittleThread.Create(AProgressBarHandle: THandle);
begin
  FProgressBarHandle := AProgressBarHandle;
  FreeOnTerminate := True;
  inherited Create(False);
end;

procedure TMyLittleThread.Execute;
var
  i: Integer;
begin
  for i := 1 to 100 do
  begin
    SendMessage(FProgressBarHandle, PBM_SETPOS, i, 0);
    Sleep(100);
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  TMyLittleThread.Create(ProgressBar1.Handle);
end;


LittleBen - Do 08.09.11 14:19

Ahhhh, so meinst du das! Vielen Dank!
Wenn ich auch noch ein Memo beschreiben will, dann kann ich dass ja auch mit Sendmessage machen, oder?


jaenicke - Do 08.09.11 14:26

Klar:

Delphi-Quelltext
1:
2:
    SendMessage(FMemoHandle, WM_SETTEXT, 0, lParam(PChar(IntToStr(i))));
    SendMessage(FMemoHandle, CM_TEXTCHANGED, 00);


LittleBen - Do 08.09.11 14:28

Perfekt!!! Vielen Dank, hast mir mal wieder geholfen!