Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   GUI-Design mit VCL / FireMonkey / Common Controls (https://www.delphipraxis.net/18-gui-design-mit-vcl-firemonkey-common-controls/)
-   -   Label Text ändern aus einem Thread heraus (https://www.delphipraxis.net/182040-label-text-aendern-aus-einem-thread-heraus.html)

CreativeMD 25. Sep 2014 18:09

Label Text ändern aus einem Thread heraus
 
Hallo,

Viele von euch werden das Problem kennen, es ist kompliziert einen Labeltext aus einem Thread heraus zu update.

Der einfachste Weg ist das ganze über Synchronize zu lösen.

Eigentlich sollte es aber dann funktionieren, tut es aber nicht. Ich habe schon alles mögliche versucht und ich bekomme den Text nicht geupdatet.

Was irgendjemand wo das Problem liegen könnte? Eine Einstellung der TForm oder irgendetwas anderes?
Die andere TForms kann ich ohne Probleme aus dem Thread heraus bearbeiten nur nicht die Hauptform :(.

Vielen Dank für eure Hilfe.

DeddyH 25. Sep 2014 18:15

AW: Label Text ändern aus einem Thread heraus
 
Wie soll man darauf antworten, wenn man den Code nicht kennt?

CreativeMD 25. Sep 2014 18:23

AW: Label Text ändern aus einem Thread heraus
 
Ja ich will auch immer den Code sehen, aber da ist überhaupt nichts auffälliges


hier die Thread-Klasse:
Delphi-Quellcode:
TBackgroundThread = class(TThread)
    Text : String;
    procedure setLabelText;
    procedure Execute; override;
    constructor Create;
  end;

procedure TBackgroundThread.setLabelText;
begin
  if Text <> OverviewF.lblTaskBackground.Caption then
  begin
    Log.AddLog('Setting label to from "' + OverviewF.lblTaskBackground.Caption + '" to "' + Text + '"');
  end;
  OverviewF.lblTaskBackground.Caption := Text;
//Ja, ich hab auch alles möglich versucht update zu daten oder mit progressmessages zu arbeiten
end;

procedure TBackgroundThread.Execute;
begin
  while Application.Active do
  begin
    OverViewF.cmdProgressBackground.Reset;
    if Tasks.Count > 0 then
    begin
      Tasks[0].runTask(OverViewF.cmdProgressBackground);
      Text := Log.getLastRow;
      Synchronize(setLabeltext);
    end
    else
    begin
      Text := 'Nothing to do';
      Synchronize(setLabeltext);
    end;
    Sleep(10);
  end;
  Log.AddLog('Terminating Background Thread!');
end;

constructor TBackgroundThread.Create;
begin
  inherited Create;
  Log.AddLog('Creating Background Thread!');
end;
Das Thread wird beim OnShow der Form gestartet.

himitsu 25. Sep 2014 18:50

AW: Label Text ändern aus einem Thread heraus
 
MSDN-Library durchsuchenSendMessage und MSDN-Library durchsuchenPostMessage synchronisieren den Aufruf auch in den Thrad, in welchem die Windows-Komponente erstellt wurde, womit man dort selber nichts synchronisieren müsste.
Nur hat TLabel ja kein Handle, aber das wirkliche LABEL (Delphi-Referenz durchsuchenTStaticText) hätte Eines.


Und wenn man erstmal lernt, daß man doppelten Code besser vermeiden sollte und sich dann auch noch an die anonymen Methoden gewöhnt hat, dann wird das Leben viel einfacher.
:stupid:

Zitat:

Delphi-Quellcode:
if Tasks.Count > 0 then
begin
  Tasks[0].runTask(OverViewF.cmdProgressBackground);
  Text := Log.getLastRow;
  Synchronize(setLabeltext);
end
else
begin
  Text := 'Nothing to do';
  Synchronize(setLabeltext);
end;

Delphi-Quellcode:
if Tasks.Count > 0 then begin
  Tasks[0].RunTask(OverViewF.cmdProgressBackground);
  Text := Log.GetLastRow;
end else
  Text := 'Nothing to do';
Synchronize(SetLabeltext);
Delphi-Quellcode:
if Tasks.Count > 0 then begin
  Tasks[0].RunTask(OverViewF.cmdProgressBackground);
  Text := Log.GetLastRow;
end else
  Text := 'Nothing to do';
Synchronize(procedure
  begin
    if Text <> OverviewF.lblTaskBackground.Caption then
      Log.AddLog('Setting label to from "' + OverviewF.lblTaskBackground.Caption + '" to "' + Text + '"');
    OverviewF.lblTaskBackground.Caption := Text;
  end);

DeddyH 25. Sep 2014 18:58

AW: Label Text ändern aus einem Thread heraus
 
Oder man regelt das über Events, oder man schickt die Messages an das Fensterhandle statt an das nicht vorhandene Label-Handle. Fehlt da übrigens nicht ein override beim Konstruktor?

CreativeMD 25. Sep 2014 19:10

AW: Label Text ändern aus einem Thread heraus
 
Erstmal dankeschön für eure schnellen Antworten.

@himitsu ich hab es schon mit den Methoden SendMessage/PostMessage probiert. Es könnte aber auch sein das es falsch war, könntet ihr mir mal ein SendMessage Beispiel geben?
P.S. der Code ist durcheinandergewürfelt weil ich momentan erstmal nur das Problem beheben will und der Rest erstmal egal ist.

@DeddyH Beispiel?

DeddyH 25. Sep 2014 19:36

AW: Label Text ändern aus einem Thread heraus
 
Schnell zusammengetippt, kann noch Fehler enthalten:
Delphi-Quellcode:
type
  TOnSomethingHappenedEvent = procedure(var OldValue: string; const NewValue: string) of object;
 
  TMyThread = class(TThread)
  private
    FText: string;
    FOnSomethingHappened: TOnSomethingHappenedEvent;
    procedure DoOnSomethingHappened;
    ...
  public
    ...
    property OnSomethingHappened: TOnSomethingHappenedEvent read FOnSomethingHappened write FOnSomethingHappened;
  end;
 
procedure TMyThread.DoOnSomethingHappened;
var
  OldValue: string;
begin
  if Assigned(FOnSomethingHappened) then
    begin
      FOnSomethingHappened(OldValue, FText);
      Log('Changed from ' + OldValue + ' to ' + FText);
    end;
end;

procedure TMyThread.Execute;
begin
  ...
  Synchronize(DoOnSomethingHappened);
end;
Nun kannst Du dem Thread eine Methode vom Typ TOnSomethingHappenedEvent als Eventhandler für OnSomethingHappened zuweisen, der dann synchronisiert ausgeführt wird. Allerdings musst Du den Thread nun Suspended erzeugen, damit er nicht gleich losrennt, bevor die Property belegt wurde.

Medium 25. Sep 2014 19:41

AW: Label Text ändern aus einem Thread heraus
 
Bist du sicher, dass dein Thread überhaupt bis da hin kommt? Ich sehe null Exception-Handling, dafür aber diverse Zugriffe auf Komponenten im Mainthread ohne Synchronisierung, die den Thread frühzeitig ins Nirvana schicken können. (Und auch wenn du meinst nichts auffälliges mehr zu sehen, so kann man fast garantieren, dass so lange etwas nicht geht etwas dran verkehrt ist, was andere vielleicht eher erkennen - sei es wegen größerer Erfahrung oder einfach weil man nicht wie man selbst oft im Wald steht. IMMER Source mit posten. Das ist NIE falsch. BITTE!!!)

Delphi-Quellcode:
TBackgroundThread = class(TThread)
    Text : String;
    procedure setLabelText;
    procedure Execute; override;
    constructor Create;
  end;

procedure TBackgroundThread.setLabelText;
begin
  if Text <> OverviewF.lblTaskBackground.Caption then
  begin
    Log.AddLog('Setting label to from "' + OverviewF.lblTaskBackground.Caption + '" to "' + Text + '"');
  end;
  OverviewF.lblTaskBackground.Caption := Text;
//Ja, ich hab auch alles möglich versucht update zu daten oder mit progressmessages zu arbeiten
end;

procedure TBackgroundThread.Execute;
begin
  while Application.Active do
  begin
    OverViewF.cmdProgressBackground.Reset; // <-------- Kann knallen
    if Tasks.Count > 0 then
    begin
      Tasks[0].runTask(OverViewF.cmdProgressBackground); // <-------- Kann knallen, woher kommt Tasks überhaupt!? Es ist nicht Member der Threadklasse! Also: Syncen!
      Text := Log.getLastRow; // <-------- Kann knallen (ich vermute Log ist letztlich etwas, was in ein Memo schreibt.)
      Synchronize(setLabeltext);
    end
    else
    begin
      Text := 'Nothing to do';
      Synchronize(setLabeltext);
    end;
    Sleep(10);
  end;
  Log.AddLog('Terminating Background Thread!');
end;

constructor TBackgroundThread.Create;
begin
  inherited Create;
  Log.AddLog('Creating Background Thread!');
end;
Das sind alleine schon 3 Stellen in 7 Zeilen Code vor dem Aufruf um den es geht, in dem du genau DAS tust, was man eben NICHT tut - und du sogar versuchst richtig zu machen, aber leider nur in diesem einen Fall. Gewöhne dir einfach folgendes an: immer wenn ein Thread auf Variablen zugreift, die NICHT in eben genau dieser Threadklasse bzw. ihrer Execute-Methoden liegen, synchronisieren. Wenn du damit deadlocks bekommst, ist 100%ig am Konzept etwas falsch. Wenn es zu langsam wird, hast du Racing-Conditions ohne Sync, und am Konzept stimmt etwas nicht. Behandle Threads am besten wie komplett separate Programme. (Das sind sie sogar fast, bis auf eben, dass sie im selben Prozessraum liegen wie der Hauptthread, und daher auch einfach so auf den gemeinsamen Speicher zugreifen können. Aber eben nicht unbedingt immer sollten.)

CreativeMD 26. Sep 2014 17:55

AW: Label Text ändern aus einem Thread heraus
 
Zitat:

Zitat von DeddyH (Beitrag 1273836)
Schnell zusammengetippt, kann noch Fehler enthalten:
Delphi-Quellcode:
type
  TOnSomethingHappenedEvent = procedure(var OldValue: string; const NewValue: string) of object;
 
  TMyThread = class(TThread)
  private
    FText: string;
    FOnSomethingHappened: TOnSomethingHappenedEvent;
    procedure DoOnSomethingHappened;
    ...
  public
    ...
    property OnSomethingHappened: TOnSomethingHappenedEvent read FOnSomethingHappened write FOnSomethingHappened;
  end;
 
procedure TMyThread.DoOnSomethingHappened;
var
  OldValue: string;
begin
  if Assigned(FOnSomethingHappened) then
    begin
      FOnSomethingHappened(OldValue, FText);
      Log('Changed from ' + OldValue + ' to ' + FText);
    end;
end;

procedure TMyThread.Execute;
begin
  ...
  Synchronize(DoOnSomethingHappened);
end;
Nun kannst Du dem Thread eine Methode vom Typ TOnSomethingHappenedEvent als Eventhandler für OnSomethingHappened zuweisen, der dann synchronisiert ausgeführt wird. Allerdings musst Du den Thread nun Suspended erzeugen, damit er nicht gleich losrennt, bevor die Property belegt wurde.

Aber das wird auch nichts an der Tatsache ändern, dass das Label nicht updatet

@Medium daran liegt es nicht hab, dass updaten klappt auch wenn das Thread leer ist nicht :(

Mit der Form stimmt etwas nicht :(

DeddyH 26. Sep 2014 18:07

AW: Label Text ändern aus einem Thread heraus
 
Ob mit der Form was nicht stimmt, kannst Du ja testen, indem Du ohne Thread mal einfach in einem ButtonClick oder so die Beschriftung änderst.

CreativeMD 26. Sep 2014 18:35

AW: Label Text ändern aus einem Thread heraus
 
Zitat:

Zitat von DeddyH (Beitrag 1274006)
Ob mit der Form was nicht stimmt, kannst Du ja testen, indem Du ohne Thread mal einfach in einem ButtonClick oder so die Beschriftung änderst.

Guter Tipp :D

OnClick:
Delphi-Quellcode:
procedure TOverviewF.btn1Click(Sender: TObject);
begin
  lblTaskBackground.Caption := '';
  editLabel;
end;
Die editLabel funktion befindet sich in einer anderen Unit:
Delphi-Quellcode:
procedure editLabel;
begin
  OverviewF.lblTaskBackground.Caption := 'Test';
end;
Das Label zeigt keinen Text an, obwohl Test dort stehen müsste, es liegt also nicht am Thread sondern irgendetwas wird da blockiert :(

EDIT
hier mal mein Sourcecode, die Dateien heißen Overview.pas und Task.pas

DeddyH 26. Sep 2014 19:01

AW: Label Text ändern aus einem Thread heraus
 
Das kann ich zwar in meinem Delphi nicht öffnen, aber setz doch mal einen Haltepunkt ins ButtonClick und steppe durch, ob editLabel auch tatsächlich dorthin springt, wo Du glaubst.

CreativeMD 26. Sep 2014 19:04

AW: Label Text ändern aus einem Thread heraus
 
Zitat:

Zitat von DeddyH (Beitrag 1274016)
Das kann ich zwar in meinem Delphi nicht öffnen, aber setz doch mal einen Haltepunkt ins ButtonClick und steppe durch, ob editLabel auch tatsächlich dorthin springt, wo Du glaubst.

Jaja tut es, auch der Wert wird geändert und bleibt auch so (alles schon überprüft), aber die Anzeige ändert sich nicht :(.

DeddyH 26. Sep 2014 19:20

AW: Label Text ändern aus einem Thread heraus
 
Dann gehen mir auch die Ideen aus :(

himitsu 26. Sep 2014 19:27

AW: Label Text ändern aus einem Thread heraus
 
Ein gutes Beispiel für böse globale Variablen. :stupid:

An deiner Stelle würde ich mal in die Projektoptionen > Formulare schauen (oder in die DPR).
Ich wette fast da versteckt sich der "doppelte" Fehler.

Sir Rufo 26. Sep 2014 19:31

AW: Label Text ändern aus einem Thread heraus
 
Die Hautfrage ist hier, wird auch das Label von der Form geändert, die gerade betrachtet wird?

Ich vermute mal eben nicht.

himitsu 26. Sep 2014 19:57

AW: Label Text ändern aus einem Thread heraus
 
Oder greift er überhaupt auf die richtige Form zu? :stupid:

CreativeMD 26. Sep 2014 21:59

AW: Label Text ändern aus einem Thread heraus
 
Zitat:

Zitat von himitsu (Beitrag 1274023)
Oder greift er überhaupt auf die richtige Form zu? :stupid:

Ja das tue ich :D, ach man mir sind schon alle Ideen ausgegangen :(

Medium 27. Sep 2014 03:16

AW: Label Text ändern aus einem Thread heraus
 
Was passiert, wenn du in deinem Test editLabel so änderst?
Delphi-Quellcode:
procedure editLabel;
begin
  if GetForegroundWindow() = OverviewF.Handle then
    ShowMessage('Ja, wirklich das richtige Formular.');
end;
Wenn die MessageBox nicht kommt, dann steht in deiner globalen Variablen "OverviewF" nicht das Fenster mit dem Label.

Wenn doch, dann mal Neuzeichnen erzwingen:
Delphi-Quellcode:
procedure TOverviewF.btn1Click(Sender: TObject);
begin
   lblTaskBackground.Caption := '';
   editLabel;
   Invalidate;
end;
Mit deiner originalen editLabel Prozedur.

Wenn alles nichts bringt, mache den Test bitte mal mit einem frischen, leeren Projekt. Wenn es dort geht, dann ist in deinem eigentlichen definitiv ein Fehler.

CreativeMD 27. Sep 2014 09:46

AW: Label Text ändern aus einem Thread heraus
 
Hm, das ist verwirrend,
also es ist die gleiche Form, aber eine andere Instanz (nach meiner Vermutung),
aber ich greife auf die richtige Variable zu:
Delphi-Quellcode:
const
  WM_AFTER_SHOW = WM_USER + 300;
type
  ThreadClass = class of TThread;
  TOverviewF = class(TForm)
    lvInstances: TListView;
    mmMenu: TMainMenu;
    Settings1: TMenuItem;
    Logger1: TMenuItem;
    BugReport1: TMenuItem;
    MinecraftAccounts1: TMenuItem;
    Changelog1: TMenuItem;
    lblVersion: TLabel;
    N1: TMenuItem;
    CheckForUpdates1: TMenuItem;
    SendanEMail1: TMenuItem;
    lblInformation: TLabel;
    lblRetry: TLinkLabel;
    cmdProgressBackground: TCMDProgressBar;
    lblTaskBackground: TLabel;
    btn1: TButton;
    procedure Logger1Click(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure WmAfterShow(var Msg: TMessage); message WM_AFTER_SHOW;
    procedure lblRetryClick(Sender: TObject);
    function createThread(thread : ThreadClass) : TThread;
    procedure setEnabled(Enabled : Boolean);
    procedure btn1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  OverviewF: TOverviewF;
  BackgroundThread : TBackgroundThread;
Auf OverviewF, da bin ich mir sicher. Aber wo ist dann der Fehler :O


EDIT

In den Einstellungen (wo man das Hauptformular bestimmen kann) gibt es zwei Forms mit dem Namen OverviewF :O.

Das Problem ist gelöst nach ein paar mal hin und her klicken funktioniert es nun endlich :D, die eine Form ist dann irgendwann verschwunden. Vielen Dank für eure Hilfe :D

himitsu 27. Sep 2014 10:51

AW: Label Text ändern aus einem Thread heraus
 
Zitat:

Ja das tue ich
Dann wohl doch nicht. ;)

Wenn jetzt noch jemand den anderen Thread findet, wo schonmal jemand genau das gleiche Problem hatte .........



Das erste Fenster wird zur Mainform und Visible auf True umgestellt
und das zweite Fenster wird als Letztes in die böse gloable Variable geschrieben.

CreativeMD 27. Sep 2014 11:02

AW: Label Text ändern aus einem Thread heraus
 
Zitat:

Zitat von himitsu (Beitrag 1274058)
Zitat:

Ja das tue ich
Dann wohl doch nicht. ;)

Wenn jetzt noch jemand den anderen Thread findet, wo schonmal jemand genau das gleiche Problem hatte .........



Das erste Fenster wird zur Mainform und Visible auf True umgestellt
und das zweite Fenster wird als Letztes in die böse gloable Variable geschrieben.

Ja, das Problem ist echt echt böse, wer kommt den auf so etwas? Das war Rainer Zufall (:D), dass ich es gesehen habe.

Nochmal Danke für die Hilfe :thumb:

Medium 27. Sep 2014 13:53

AW: Label Text ändern aus einem Thread heraus
 
Und exakt genau das ist der Grund, weswegen diese globalen Formularvariablen echt gefährlich sein können. Andere globale auch, aber hier noch mehr, da Automatismen der IDE greifen, die man nicht bewusst selbst implementiert hat. Ich habe mir die Benutzung dieser Variablen komplett abgewöhnt. In der Form-Klasse gehört "self" genutzt (extrem wichtig, gerade in so fällen wie hier passiert sind!), und alles externe bekommt genau die Parameter mit, an denen es etwas ändern soll. Sei es ein Label, ein Canvas, oder im Zweifel gleich ein Form-Handle.

In deinem Fall wäre z.B. folgendes einigermaßen sauber:
Delphi-Quellcode:
procedure TOverviewF.btn1Click(Sender: TObject);
begin
    lblTaskBackground.Caption := '';
    ChangeLabelCaption(lblTaskBackground);
end;

procedure ChangeLabelCaption(aLabel: TLabel);
begin
  aLabel.Caption := 'Hallo zusammen!';
end;
So wäre immer sicher gestellt, dass das Label der Instanz geändert wird, auf der auch der Button liegt, der tatsächlich geklickt wurde. Komplett ohne diese gräßliche gloabele Form-Variable.

himitsu 27. Sep 2014 14:30

AW: Label Text ändern aus einem Thread heraus
 
Genau deswegen sollte man mit diesen Variablen vorsichtig sein.
Schlimm und vorallem grob fahrlässig ist die Verwenddung dieser Variablen innerhalb der eigenen Klasse.

Delphi-Quellcode:
procedure TForm1.Button1Click(Sender: TObject);
begin
  Form1.Label1.Caltion := 'Fehler, wenn Form1 nicht die eigene Instanz enthält';
  Self.Label1.Caltion := 'OK, auch wenn nicht unbedingt nötig';
  Label1.Caltion := 'OK';
end;
Und das betrifft nicht nur Form-Variablen.



Zitat:

Zitat von himitsu (Beitrag 1274019)
Ein gutes Beispiel für böse globale Variablen. :stupid:

An deiner Stelle würde ich mal in die Projektoptionen > Formulare schauen (oder in die DPR).
Ich wette fast da versteckt sich der "doppelte" Fehler.

Zitat:

Zitat von Sir Rufo (Beitrag 1274020)
Die Hautfrage ist hier, wird auch das Label von der Form geändert, die gerade betrachtet wird?

Ich vermute mal eben nicht.

Zitat:

Zitat von himitsu (Beitrag 1274023)
Oder greift er überhaupt auf die richtige Form zu? :stupid:

Zitat:

Zitat von CreativeMD (Beitrag 1274061)
Ja, das Problem ist echt echt böse, wer kommt den auf so etwas? Das war Rainer Zufall (:D), dass ich es gesehen habe.

Nochmal Danke für die Hilfe :thumb:

Bitte.


Alle Zeitangaben in WEZ +1. Es ist jetzt 00:07 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz