Einzelnen Beitrag anzeigen

Medium

Registriert seit: 23. Jan 2008
3.679 Beiträge
 
Delphi 2007 Enterprise
 
#1

TDBChart mit Queries in Thread füttern

  Alt 29. Feb 2012, 13:33
Aloah

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...)
"When one person suffers from a delusion, it is called insanity. When a million people suffer from a delusion, it is called religion." (Richard Dawkins)
  Mit Zitat antworten Zitat