![]() |
AW: SQL-Abfrage im Thread und füllen eines ListView
Liste der Anhänge anzeigen (Anzahl: 2)
Ok, hier mal ein Thread, der Datensätze an eine TListView schickt.
Der passt da noch nicht ganz für Dich (kein ZEOS, keine Query) aber das Prinzip bleibt gleich. Daten lesen und dann immer blockweise an die ListView schicken. so lange wie der Thread noch läuft per Queue und der letzte Abgleich erfolgt per Synchronize. (Damit wird sichergestellt, dass alle Einträge auch die ListView ausgeliefert werden)
Delphi-Quellcode:
Im Anhang ScreenShot, Exe-Datei und der gesamte Source
unit thread.SqlDataToListView;
interface uses Classes, ComCtrls, // TListView DB, DBClient, // TClientDataSet Generics.Collections; // TList<T> type TSqlDataToListViewThread = class( TThread ) private FListView : TListView; FData : TDataSet; protected procedure SendDataToListView( AListView : TListView; AItems : TList<TStrings>; Sync : Boolean = False ); protected procedure Execute; override; public constructor Create( AListView : TListView; const AFileName : string; CreateSuspended : Boolean = False ); destructor Destroy; override; end; implementation uses Windows, SysUtils; { TGetSqlDataToListViewThread } constructor TSqlDataToListViewThread.Create( AListView : TListView; const AFileName : string; CreateSuspended : Boolean ); begin inherited Create( CreateSuspended ); FListView := AListView; FData := TClientDataSet.Create( nil ); with FData as TClientDataSet do begin FileName := AFileName; end; end; destructor TSqlDataToListViewThread.Destroy; begin FData.Free; inherited; end; procedure TSqlDataToListViewThread.Execute; var lItems : TList<TStrings>; lItem : TStrings; lField : TField; begin // Datenverbindung öffnen try FData.Open; except on E : Exception do end; if FData.Active then // Wenn die Datenverbindung gesichert hergestellt ist dann können wir ja ans Werk try lItems := TList<TStrings>.Create; try while not Terminated and not FData.Eof do // Wir machen hier so lange, bis ... // ... der Thread abgebrochen wird // ... oder alle Datensätze gelesen wurden begin // Daten in einen Puffer schieben lItem := TStringList.Create; for lField in FData.Fields do begin lItem.Add( lField.AsString ); end; lItem.Add( DateTimeToStr( now ) ); // Daten in die Sammelliste schreiben lItems.Add( lItem ); // Nächster Datensatz FData.Next; // Wir tun mal so, als ob das hier gaaaanz lange dauert Sleep( Random( 15 ) ); if ( lItems.Count >= 10 ) or Terminated or FData.Eof then // Wenn der Block voll ist, // oder der Thread abgebrochen wurde // oder keine Daten mehr zu lesen sind // dann die Daten an das ListView ausliefern SendDataToListView( FListView, lItems, Terminated or FData.Eof ); end; finally lItems.Free; end; finally FData.Close; end; end; procedure TSqlDataToListViewThread.SendDataToListView( AListView : TListView; AItems : TList<TStrings>; Sync : Boolean ); var lItems : TObjectList<TStrings>; // Mal hier schnell geändert, sonst haben wir da ein Speicherleck :o) lItem : TStrings; begin if MainThreadID = GetCurrentThreadId then // Ei jo, wenn wir uns jetzt im MainThread-Kontext befinden, // dann können wir ja wieder ganz gemütlich auf das VCL-Gedöns zugreifen begin if Assigned( AItems ) then begin AListView.Items.BeginUpdate; try for lItem in AItems do begin with AListView.Items.Add do begin Caption := lItem[0]; lItem.Delete( 0 ); SubItems.Assign( lItem ); SubItems.Add( DateTimeToStr( now ) ); SubItems.Add( BoolToStr( Sync, True ) ); end; end; finally AListView.Items.EndUpdate; end; AItems.Free; // ** Hier ist das Free, und ... end; end else begin // Kopieren der übergebenen Daten-Liste lItems := TObjectList<TStrings>.Create; // ** ... hier das Create ... verkehrte Welt :o) for lItem in AItems do begin lItems.Add( lItem ); end; // übergebene Daten-Liste leeren (da schreibt der Thread ja wieder neue Daten rein) AItems.Clear; // Jetzt rufen wir uns selber nochmal auf, aber ... // 1. mit der kopierten Liste // 2. im MainThread-Kontext (Synchronized oder Gequeued) if Sync then Synchronize( procedure begin SendDataToListView( AListView, lItems, Sync ); end ) else Queue( procedure begin SendDataToListView( AListView, lItems, Sync ); end ); end; end; end. |
AW: SQL-Abfrage im Thread und füllen eines ListView
Hmm.. Bisserl kompliziert (zu viel Rumkopiererei), wenn man bedenkt, das man die Daten beim virtual Mode gar nicht in die ListView schreiben muss. Ich würde die Daten im Hintergrund einfach in eine TObjectList<TStringList> füllen und immer dann, wenn ein weiterer Block befüllt wurde, einfach per Synchronize die ListView.Items.Count neu setzen.
Aber als allgemeingültiges Proof-Of-Concept natürlich ok. |
AW: SQL-Abfrage im Thread und füllen eines ListView
Also als erstes heut morgen möchte ich gleich mal eine Kniefall für Sir Rufo :hello: machen. Das ich im Create gleich das Zeilobject mit übergeben kann, da wär ich im Leben nicht drauf gekommen. Wenn ich das so richtig verstanden habe, dann kann in der Create-Procedure dann ein Connection und Query object erzeugen. Das SQL-Statement könnte ich anstelle von afilename übergeben.
Und das Execute wird nur einmal ausgeführt. Damit werd ich das jetzt mal versuchen. Was ich bisher aus den verschieden Beispielen zusammengebastelt hatte hatte schon eine gewisse ähnlichkeit mit dem was du hier gepostet hast. Nur hab ich versucht, die Paramter für die Abfrage per Property zu übergeben, aber das hat noch nicht funktioniert. Wenn ich das gleich im Create machen kann spar ich mir noch code. Okay, ich frage mich grad noch, wann und wie sich der Thread dann zerstört? Und ich müßte die Objekte, die ich im Thread erzeuge ja auch noch freigeben. Dafür würde ich den destructor Destroy nehmen, wenn das geht. Zur Tabelle hier das das Create
Code:
Und hier mal mein SQL-Statement
CREATE TABLE `main` (
`Lfdnr` int(10) unsigned NOT NULL AUTO_INCREMENT, `Datum` datetime NOT NULL, `Zeit` varchar(45) DEFAULT NULL, `Da` varchar(45) DEFAULT '0', `Erl` varchar(45) DEFAULT '0', `Name` varchar(45) DEFAULT NULL, `Wer` varchar(45) DEFAULT NULL, `Termin` varchar(45) DEFAULT NULL, `Typ` varchar(45) DEFAULT NULL, `KZ` varchar(45) DEFAULT NULL, `Arbeiten` varchar(250) DEFAULT NULL, `Telefon` varchar(45) DEFAULT NULL, `Bemerkung` longtext, `Farbe` varchar(45) DEFAULT 'clScrollBar', `ZT` varchar(45) DEFAULT '0', `TB` varchar(45) DEFAULT '0', `TD` varchar(45) DEFAULT '0', `MA` varchar(45) DEFAULT NULL, `EW` varchar(45) DEFAULT '0', `Erinnerung` varchar(45) DEFAULT NULL, `FormatArbeiten` blob, `ID` varchar(45) DEFAULT NULL, `Mondruck` int(10) unsigned DEFAULT '0', `Auftrnr` varchar(10) DEFAULT NULL, `InArbeit` varchar(45) DEFAULT NULL, `rg` varchar(45) DEFAULT '0', `kmstand` int(10) unsigned DEFAULT '0', `recall` int(11) DEFAULT '-1', `lkukon` datetime DEFAULT NULL, `nachrep` longtext, `FD` varchar(45) DEFAULT '0', `TBKomm` longtext, `TDKomm` longtext, `FDKomm` longtext, `Gestrichen` varchar(45) DEFAULT '0', `HBKomm` longtext, `first` int(11) DEFAULT '0', `prior` int(11) DEFAULT '0', `next` int(11) DEFAULT '0', `erster` int(11) DEFAULT '0', `voheriger` int(11) DEFAULT '0', `naechster` int(11) DEFAULT '0', `weitergabe` int(11) DEFAULT '-1', `Firmenname` varchar(50) DEFAULT NULL, `Bild` blob, `debitor` varchar(45) DEFAULT NULL, `gruppe` int(11) DEFAULT '-1', `zegeplant` int(11) unsigned DEFAULT NULL, `frueher` int(11) DEFAULT '-1', `EWKomm` longtext, `TerminKomm` longtext, PRIMARY KEY (`Lfdnr`), KEY `Datum` (`Datum`,`Zeit`) ) ENGINE=InnoDB AUTO_INCREMENT=587 DEFAULT CHARSET=latin1;
Code:
Das Replace ist leider notwendig, damit alle Schreibweise auch erfasst werden.
Select Datum, Zeit, Wer, Arbeiten from main where REPLACE(KZ," ","")=:kennzeichen and lfdnr=erster
|
AW: SQL-Abfrage im Thread und füllen eines ListView
Zitat:
Wieso achtest Du beim *schreiben* der Daten nicht auf die Schreibweise? :gruebel: Dann brauchst Du das komische 'REPLACE' bei der Abfrage nämlich nicht mehr. Auch immer wieder gerne gefragt und durchaus interessant: Wer scrollt eigentlich durch die 40.000 Datensätze? |
AW: SQL-Abfrage im Thread und füllen eines ListView
Zitat:
Zitat:
Der Thread selber wird dann wie folgt gestartet: (siehe auch den restlichen Quelltest in der zip)
Delphi-Quellcode:
MyThread := TMyThread.Create( {[Parameter],} True {CreateSuspended} );
MyThread.OnTerminated := ThreadIstAmEnde; { Eine Methode die beim Beenden des Threads aufgerufen wird } MyThread.FreeOnTerminate := True; { Ist der Thread am Ende, dann soll er sich auch gleich vom Acker machen } MyThread.Start; { Jetzt geht es los } Zitat:
Zitat:
Code:
Ja und am besten für das Feld KZ einen Trigger (Insekt und Update) erstellen, der die Leerzeichen schon direkt beim Eintrag in die DB entfernt.
Select Datum, Zeit, Wer, Arbeiten from main where lfdnr=erster AND REPLACE(KZ," ","")=:kennzeichen
|
AW: SQL-Abfrage im Thread und füllen eines ListView
Zitat:
Kein Scrollt durch 40000 Datensätze. Darum ja auch die Abfrage. Bleiben ja nur so 20 übrig. |
AW: SQL-Abfrage im Thread und füllen eines ListView
@Sir Rufo: Ich hab jetzt mal versucht, den Code von dir zu übernehmen. Leider stoße ich da mal wieder an meine Grenzen. Das Projekt ist ein D7 geschrieben. Und teile deines Codes funktionieren unter D7 noch nicht.
Im Grund sollte das kein Problem sein, aber durch meine begrenzte Erfahrung mit Threads bedeuten die Änderungen für mich schon ein Problem. Zum einen sind da Sachen wie:
Delphi-Quellcode:
-> Kennt D7 ja nicht.
Generics.Collections
ebenso dieses:
Delphi-Quellcode:
und
TList<TStrings>.Create
Delphi-Quellcode:
(ich denke for .. in kennt D7 auch nicht).
for lField in FData.Fields do
Aber das krieg ich schon hin. Aber hiermit hab ich wirklich probleme:
Delphi-Quellcode:
geht wohl so in D7 auch noch nicht.
Synchronize( procedure begin SendDataToListView( AListView, lItems, Sync ); end )
Ich kann jetzt aber auch nicht mit dem Projekt auf D2010 umziehen. |
AW: SQL-Abfrage im Thread und füllen eines ListView
Das ist doch nur eine Inline Definition. Schreib die zu synchronisierende procedure einfach normal und ruf sie dort auf. Also aus:
Delphi-Quellcode:
wird dann
Synchronize( procedure begin SendDataToListView( AListView, lItems, Sync ); end )
Delphi-Quellcode:
Das ganze hier ist aber aus meiner Sicht komplett überflüssig, da das Problem in der Datenbankdefinition und der Abfrage liegt. Du treibst hier einen affenartigen Aufwand um Kosmetik zu betreiben.
procedure TMeinThread.SyncProc
begin SendDataToListView( AListView, lItems, Sync ); end; ... Synchronize(SyncProc) Dein KennZeichen Feld sollte normalisiert werden. Wo kommt der Abfrage Parameter her? Und wie sieht die Mastertabelle aus? |
AW: SQL-Abfrage im Thread und füllen eines ListView
Ich kann mich nur union anschließen.
Statt die Ursache in der Datenhaltung zu beseitigen, wird das "Problem" in einem Thread versteckt. Wieviel Nutzer verwenden diese Maske? Wie oft am Tag? Sicher, es sind nur ein paar Sekunden pro Aufruf. Aber ich vermute, dass Euer Hardwarehandler vielleicht bald mit einem großen Paket vorbeikommt, wenn Du den Stil beibehälst. |
AW: SQL-Abfrage im Thread und füllen eines ListView
Ok, unter D7 ist das so tatsächlich nicht möglich ;)
Mal sehen ob ich da eine D7-taugliche Version dir bereitstellen kann. Allerdings solltest du tatsächlich mal das Kennzeichen in den Index aufnehmen und mit einem Trigger sicherstellen, dass dort keine Leerzeichen auftauchen. Dann geht diese Abfrage bestimmt wie der Teufel
Code:
EDIT: Da fällt mir doch gerade auf, dass du das Feld erster auch in den Index aufnehmen solltest ;)
Select Datum, Zeit, Wer, Arbeiten from main where lfdnr = erster AND KZ = :kennzeichen
|
Alle Zeitangaben in WEZ +1. Es ist jetzt 21:04 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz