Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   TDBChart mit Queries in Thread füttern (https://www.delphipraxis.net/166792-tdbchart-mit-queries-thread-fuettern.html)

Medium 29. Feb 2012 13:33

TDBChart mit Queries in Thread füttern
 
Aloah :cheer:

Ich habe ein TDBChart, dass mir Messwerte aus einer Datenbank grafisch darstellen soll. Dieses Chart soll ca. jede Sekunde aktualisiert werden, und zeigt immer Werte der letzten paar Minuten an. In der Datenbank stehen jedoch ziemlich viele Werte (eine Tabelle mit den Daten von ~20 Messstellen, auch solchen die in der Vergangenheit gemessen wurden - nicht nur die anzuzeigenden).

Zunächst habe ich die AutoUpdate-Property vom Chart verwendet, jedoch werden die Abfragen mittlerweile etwas träge, so dass es die Bedienbarkeit des UI beeinträchtigt. Also wollte ich gerne das Abfragen in einen Thread auslagern, da das Refresh der Queries hier der Flaschenhals ist.

Problem ist: Das Chart bleibt seit dem einfach leer! Die Series werden nicht gezeichnet, obwohl laut Steema Doku die verwendete Methode "TLineSeries.CheckDataSource" auch die Aktualisierung des Charts auslösen sollte.

Auf die relevanten Teile eingedampfter Code:
Delphi-Quellcode:
unit _frmCurves;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, TeeProcs, TeEngine, Chart, DBChart, Contnrs, DB, MemDS,
  DBAccess, Uni, Series;

type
  TUpdateProc = procedure of Object;

  TCurveUpdateThread = class(TThread)
  protected
    procedure Execute; override;
  public
    Con: TUniConnection;
    Queries: TObjectList;
    UpdateProc: TUpdateProc;
    constructor Create(aConnection: TUniConnection);
    destructor Destroy; override;
    function AddQuery: TUniQuery;
  end;

  TfrmCurves = class(TForm)
    Chart: TDBChart;
  private
    { Private-Deklarationen }
    UThread: TCurveUpdateThread;
    Series: TObjectList;
    procedure Updated;
  public
    { Public-Deklarationen }
    procedure ShowData(aTitle: String; aDuration: Integer; aLabels: array of String; aUnits: array of String);
    constructor CreateCustom(aParent: TWinControl; aLeft, aTop, aWidth, aHeight: Integer);
  end;

implementation

uses
  DateUtils;

{$R *.dfm}

{ TfrmCurves }

constructor TfrmCurves.CreateCustom(aParent: TWinControl; aLeft, aTop, aWidth, aHeight: Integer);
begin
  inherited Create(nil);
  Doublebuffered := true;

  UThread := TCurveUpdateThread.Create(frmAnwahl.Con);
  UThread.UpdateProc := Updated;

  Series := TObjectList.Create;
  Series.OwnsObjects := false;
end;

procedure TfrmCurves.Updated;
var
  i: Integer;
begin
  for i := 0 to Series.Count-1 do
    (Series[i] as TLineSeries).CheckDataSource;
end;

procedure TfrmCurves.ShowData(aTitle: String; aDuration: Integer; aLabels, aUnits: array of String);
var
  i: Integer;
  q: TUniQuery;
begin
  for i := 0 to High(aLabels) do
  begin
    q := UThread.AddQuery;
    with q do
    begin
      SQL.Text := 'SELECT * FROM messwerte WHERE mstelle=:ms AND mtime > DATE_SUB(NOW(), INTERVAL :st MINUTE) ORDER BY mtime';
      ParamByName('ms').AsString := aLabels[i];
      ParamByName('st').AsInteger := aDuration;
      Open;
      (FieldByName('mtime') as TDateTimeField).DisplayFormat := 'hh:mm:ss';
    end;
    Series.Add(TLineSeries.Create(nil));
    with Series[i] as TLineSeries do
    begin
      Chart.AddSeries(Series[i] as TLineSeries);
      DataSource := q;
      XValues.ValueSource := 'mtime';
      YValues.ValueSource := 'mwert';
      XLabelsSource := 'mtime';
      XValues.DateTime := true;
      Title := aLabels[i] + ' ('+aUnits[i]+')';
    end;
  end;
  Show;
  UThread.Resume;
end;

{ TCurveUpdateThread }

function TCurveUpdateThread.AddQuery: TUniQuery;
begin
  result := TUniQuery.Create(nil);
  result.Connection := Con;
  Queries.Add(result);
end;

constructor TCurveUpdateThread.Create(aConnection: TUniConnection);
begin
  inherited Create(true);
  Con := TUniConnection.Create(nil);
  Con.ProviderName := aConnection.ProviderName;
  Con.Database := aConnection.Database;
  Con.Username := aConnection.Username;
  Con.Password := aConnection.Password;
  Con.Server := aConnection.Server;
  Con.Port := aConnection.Port;
  Con.Connect;
  Queries := TObjectList.Create;
  Queries.OwnsObjects := false;
end;

destructor TCurveUpdateThread.Destroy;
var
  i: Integer;
begin
  for i := 0 to Queries.Count-1 do Queries[i].Free;
  Queries.Clear;
  Queries.Free;
  Con.Disconnect;
  Con.Free;
  inherited;
end;

procedure TCurveUpdateThread.Execute;
var
  i: Integer;
begin
  repeat
    for i := 0 to Queries.Count-1 do
      (Queries[i] as TUniQuery).Refresh;
    Synchronize(UpdateProc);
    Sleep(1000);
  until Terminated;
end;

end.
Die UpdateProc wird durchlaufen, und CheckDataSource fehlerfrei abgearbeitet. Lasse ich mir in der UpdateProc die RecordCounts der Queries anzeigen, sind diese auch >0 (und korrekt). Mein Chart bleibt aber einfach leer.
Lasse ich den Thread weg, und stelle statt dessen TDBChart.AutoUpdate auf true (und trage ein UpdateInterval ein), bekomme ich wie gewohnt meine Daten, und diese auch korrekt. Nur eben leider mit sehr ruckeliger Bedienung. (Die Felder mstelle und mtime in der Tabelle sind bereits indiziert.)
Erstellt werden diese Formulare durch "TfrmCurves.CreateCustom().ShowData();", und von da an werden sie von aussen nicht mehr angefasst. Es finden also keine Quergriffe aus anderen Teilen des Projektes statt.


Wo könnte ich hier ansetzen? (Das sind so Momente, wo sich Sourcecode von Fremdkomponenten prima machen würde...)

Medium 2. Mär 2012 09:49

AW: TDBChart mit Queries in Thread füttern
 
*lupf* :angel2:

Furtbichler 2. Mär 2012 09:58

AW: TDBChart mit Queries in Thread füttern
 
Ich hatte vor Jahren mal so ein Problem und habe die Series dann per Hand befüllt. Auch sonst ist mir damals aufgefallen, das das 'AutoUpdate' vom TeeChart irgendwie nicht durchdacht ist. So wie eigentlich das ganze Teil nicht sonderlich gut programmiert ist.

Aber egal, es funktioniert ja, man muss nur hier und da mal gegentreten.

Also: Thread feuert ein Event (oder verschickt ne Message) und im Hauptprogramm wird dann die Series upgedated.

So als Optimierungspotential: Es ändern sich ja nur neue Werte, also reicht es, die Query so zu schreiben, nur die neusten Daten geliefert werden. Dann schmeißt Du in der Series die hinteren weg und hängst die paar neuen Daten einfach ran. Das sollte dann schnell genug sein.

Medium 2. Mär 2012 10:40

AW: TDBChart mit Queries in Thread füttern
 
Hm, schade, aber vielen Dank für den Erfahrungswert! Dann wird das wohl doch etwas umständlicher als gehofft, zumal ich nicht immer jedes Leseintervall immer genau einen neuen Wert pro Series dazu bekomme. Also muss ein kleines "Management" rein, dass erkennt, wie viele neue Werte da sind, und... naja, das ist jetzt mein Bier :)

Best Fishes!


Alle Zeitangaben in WEZ +1. Es ist jetzt 15:56 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