Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Programmieren allgemein (https://www.delphipraxis.net/40-programmieren-allgemein/)
-   -   Delphi Fehler beim Programm beenden (https://www.delphipraxis.net/186999-fehler-beim-programm-beenden.html)

Jens Hartmann 19. Okt 2015 19:53

Fehler beim Programm beenden
 
Liste der Anhänge anzeigen (Anzahl: 2)
Hallo zusammen,

seid ein paar Tagen, bringt mein Programm beim schliessen der Anwendung einen seltsamen Fehler. Dieser tritt aber auch nicht immer auf. Siehe Bild im Anhang. Ich vermute, dass diese irgendwo damit zu tun hat, das ich beim beenden auf ein Objekt zugreifen will, welches es nicht mehr gibt. Das Problem allerdings ist, wie kann ich das finden.

Ich habe in der Projektdatein aktuell folgenden Code eingefügt und bekomme beim beenden die Fehlermeldung (siehe Bild 2)

Delphi-Quellcode:
   
{$IFDEF DEBUG}
ReportMemoryLeaksOnShutdown := True;
{$ENDIF}
Kann mir jemand eine Hilfestellung geben, wie ich das Problem auffinden kann?

geskill 19. Okt 2015 20:08

AW: Fehler beim Programm beenden
 
Bekommst du den MemoryLeak Report auch ohne die Zugriffsverletzung (AV)?

Wenn nein, dann folgt daraus, dass nach der AV eigentlich noch Objekte wären, die hätten freigegeben werden müssen. Die AV sorgt aber dafür, dass das Programm abbricht. Am besten kannst du den Fehler mit dem Debugger finden. Damit trotz AV Objekte freigegeben werden gibt es try .. finally .. end;

Wenn ja, dann haben die MemoryLeaks nichts mit der AV gemeinsam.


Bevor du mit dem Debugger arbeitest solltest du in den Projektoptionen Debug diverse Optionen wie Optimierung ausschalten. Dadurch wird dein Programm beim Debugger langsamer, aber du kannst dir noch Variablen ansehen, die der Compiler sonst schon wieder aus dem LX verworfen hätte.

Versuch einfach mal direkt wenn die AV kommt auf Anhalten zu klicken. Im IDE Fenster Call Stack bekommst eine Liste der Aufrufe, die zu diesem Fehler geführt haben (Ausführungsablauf). Daran kannst du dich nach oben entlanghangeln.

Vielleicht hilft dir dies schon weiter ;)

Luckie 19. Okt 2015 21:25

AW: Fehler beim Programm beenden
 
Andere Theorie: Du gibst Objekte frei die automatisch erzeugt werden und eigentlich auch automatisch wieder frei gegeben werden, aber da du sie schon frei gibst... Oder du gibts was frei, was schon freigegeben ist. Greifst auf schon freigegeben Objekte zu. Überpürf das alles mal.

Jens Hartmann 19. Okt 2015 21:41

AW: Fehler beim Programm beenden
 
Ich glaube, dass das ganze mit der Komponente VirtualStringTree zu tun hat. In der OnCloseQuery gebe ich das VST frei und irgendwie habe ich die Vermutung, das der Fehler dabei auftreten tut.
Nach der Fehlermeldung ist der VST nicht mehr sichtbar und die Anwendung ansonsten noch offen. Außerdem verwende ich in der VST Objekte.

Luckie 19. Okt 2015 22:08

AW: Fehler beim Programm beenden
 
Erstellt du ihn auch "von Hand" im Code?

Jens Hartmann 19. Okt 2015 22:52

AW: Fehler beim Programm beenden
 
Nein, liegt als Komponenten auf der Form...

Luckie 19. Okt 2015 23:28

AW: Fehler beim Programm beenden
 
Und warum gibst du ihn dann selbst frei?

Perlsau 20. Okt 2015 05:21

AW: Fehler beim Programm beenden
 
Zitat:

Zitat von Jens Hartmann (Beitrag 1319136)
Ich glaube, dass das ganze mit der Komponente VirtualStringTree zu tun hat. In der OnCloseQuery gebe ich das VST frei und irgendwie habe ich die Vermutung, das der Fehler dabei auftreten tut. Nach der Fehlermeldung ist der VST nicht mehr sichtbar und die Anwendung ansonsten noch offen. Außerdem verwende ich in der VST Objekte.

Wenn du den VirtualStringTree (VST) nicht selbst erzeugst, darfst du ihn auch nicht selbst freigeben. Legst du einen VST auf die Form, erhält dieser automatisch die Form als Eigentümer (Owner) und wird somit automatisch freigegeben, wenn die Form freigegeben wird. Mit irgendwelchen Objekten, die du dem VST zuweist, ist es ähnlich: Du mußt feststellen, ob diese Objekte beim Zerstören des VST ebenfalls zerstört werden und wenn nicht, diese Objekte zuerst freigeben.

Im Übrigen sollte man Freigaben niemals im OnCloseQuery machen, sondern immer im OnDestroy, weil erst im OnDestroy sichergestellt ist, daß die Zerstörung nun wirklich begonnen hat und auch durchgeführt wird. So könnte im OnClose, das nach OnCloseQuery aufgerufen wird (wenn CanClose = True), auch etwas anderes als das caFree vereinbart werden oder worden sein; der Standard-Wert ist nämlich caHide. Nur wenn die zu schließende Form auch die Mainform ist, wird bei OnClose automatisch caFree aufgerufen.

Um genau herauszufinden, wo diese ominösen Memory-Leaks entstehen, wäre es zielführend, das Programm-Ende ein- oder mehrmals sorgfältig durchzusteppen. Beim letzten meiner Kunden, der dieses Problem hatte, wurde in seiner vollkommen undurchschaubaren Anwendung mehrfach OnClose oder OnDestroy aufgerufen, was sich natürlich fatal auf die saubere Beendigung des Programms auswirkt. Oft werden bei älteren Anwendungen auch massenweise Hinweise und Warnungen mitgeschleppt und ignoriert (besagter Kunde erzeugte bei jedem Kompilierungsvorgang mehrere Hundert!). Da muß man sich einfach die Zeit nehmen, das einmal in aller Ruhe zu bereinigen. Häufig erledigen sich dadurch bereits zahlreiche Fehlerquellen, vor allem beim Beheben von Warnungen über nicht initialisierte Variablen.

Dejan Vu 20. Okt 2015 06:44

AW: Fehler beim Programm beenden
 
Die Memoryleaks kommen vom vorzeitigen Abbruch der Anwendung, jede Wette. Na ja. Fast jede. :mrgreen:

Jens Hartmann 20. Okt 2015 07:34

AW: Fehler beim Programm beenden
 
Danke schon mal zusammen, werde das heute Abend mal anpassen.

Das ich eine VST normal nicht freigeben muss (wenn es auf der Form liegt), war mir eigentlich klar, allerdings meine ich mich daran errinnern zu können, das ich das vor einiger Zeit mal eingebaut habe, weil ein ähnliches Problem vorhanden war und das Problem mit dem VST.free erledigt war.

Hat mich damals schon gewundert. Ich werde heute mal alle Warnungen versuchen zu beseitigen und schauen das alle Objekte zum passenden Zeitpunkt freigegeben werden. Die Freigabe vom VST nehme ich wieder raus.

Dann schaun war mal

Danke und Gruß Jens

Jens Hartmann 20. Okt 2015 07:38

AW: Fehler beim Programm beenden
 
Ach so, eins noch. Kann man die vielen Memoryleaks irgendwie den Variablen zuordnen? Weil außer die drei Objekte TOCustomer, TOBuilding, TOSystem sind diese ja nicht eindeutig.

Sir Rufo 20. Okt 2015 07:42

AW: Fehler beim Programm beenden
 
Wie geht man einen Sack voll Memleaks an?

Man beseitigt die bei den bekannten Typen und fängt mit dem an, der die meisten Leaks hat. Diese UnicodeString Leaks sind idR nur Folgefehler davon ;)

haentschman 20. Okt 2015 08:16

AW: Fehler beim Programm beenden
 
Hallöle...:P
Zitat:

Ach so, eins noch. Kann man die vielen Memoryleaks irgendwie den Variablen zuordnen?
Viel wichtiger ist es das ReportMemoryLeaks grundsätzlich im Debug Mode an zu haben. Da sieht man sofort an welcher Ecke man das gerade eingebaut hat und muß nicht ein halbes Jahr später rätseln... In der Regel sieht man sofort den Knackpunkt weil man gedanklich an der Stelle drinsteckt. :thumb:

TiGü 20. Okt 2015 08:56

AW: Fehler beim Programm beenden
 
Warum schlägt keiner vor den vollständigen FastMM4 zu installieren und anhand der viel ausführlicheren Fehlermeldungen das Problem einzukreisen?

Tutorials:
http://wiert.me/2009/07/29/delphi-fa...-introduction/
http://delphibistro.com/?p=186

Luckie 20. Okt 2015 11:11

AW: Fehler beim Programm beenden
 
Vielleicht nicht schön, aber MemoryLeaks beim Beenden sind eh egal. Wenn ich das Haus eh abbrenne, brauch eich die Küche vorher auch nicht feucht wischen. Ich hätte allerdings das Bestreben es ohne MemoryLeaks zu schaffen. Zumindest bei denen für die ich selbst verantwortlich bin.

Sherlock 20. Okt 2015 12:42

AW: Fehler beim Programm beenden
 
Wer sagt denn, daß die Leaks nicht schon längst im Lauf des Programms autreten? ODer kann das der FastMM so genau lokalisieren, wann man vergessen hat den Speicher freizugeben ;)?

Sherlock

Zacherl 20. Okt 2015 16:23

AW: Fehler beim Programm beenden
 
Zu VST und Memory Leaks fallen mir als allererstes immer drei Sachen ein:
  1. Delphi-Quellcode:
    VST.NodeDataSize
    initialisiert?
  2. OnFreeNode Event implementiert und deinen Record darin auch ordentlich finalisiert
    Delphi-Quellcode:
    Finalize(NodeData^)
    ?
  3. Eine kleine Eigenheit vom VST, durch welche OnFreeNode nur dann aufgerufen wird, wenn die Node vorher validated war. Validated wird eine Node nachdem sie zum ersten Mal tatsächlich sichtbar war oder nach manuellem Aufruf von
    Delphi-Quellcode:
    VST.ValidateNode
    .

Jens Hartmann 22. Okt 2015 20:51

AW: Fehler beim Programm beenden
 
Hallo zusammen,

so, die Fehlermeldung beim Programm beenden scheint erstmal weg zu sein. Allerdings sind die MemoryLeaks noch vorhanden. Ich habe jetzt den Tip von "Zacherl" befolgt und das Event "OnFreeNode" entsprechend eingebaut. Irgendwie glaube ich allerdings, dass das ganze nicht ganz sauber Programmiert ist. Ich möchte Euch gerne daher mal versuchen die jeweiligen Teilabschnitte aufzuführen und wäre Euch dankbar, wenn Ihr mir ein kurzes Feedback geben könntet.

So, ich versuch das mal Schrittweise darzustellen. Ich erzeuge ein VST mit ungefähr folgendem Aufbau:
Code:
+Hauptknoden (Zeigt den Datenbankpfad an)
   +Kunde_1
     +Objekt_1
       +System_1
         +Menü_1
         +Menü_2
         +Menü_3
       +System_2
         +Menü_1
         +Menü_2
         +Menü_3
     +Objekt_2
       +System_1
         +Menü_1
         +Menü_2
         +Menü_3
   +Kunde_2
     +Objekt_1
       +System_1
         +Menü_1
         +Menü_2
         +Menü_3
     +Objekt_2
       +System_1
         +Menü_1
         +Menü_2
         +Menü_3
Jeder Kunde kann mehrere Objekte haben. Jedes Objekt kann mehrere Systeme haben und Jedes System hat mehrere Menüpunkte.

Erzeugen tue ich das ganze dann in etwa so...

Ich habe je ein Objekt für die Kundendaten, Objektdaten und Systemdaten. Diese sind jeweils ähnlich und wie folgt aufgebaut...

Delphi-Quellcode:
unit Customers;

interface

uses
  Buildings;

{ TObject für die Kundendaten }
type
  TOCustomers = class(TObject)
    private
      FID               : integer;        //ID
      FESID             : integer;        //ESID
      FName             : string;         //Name des Kunden
      FOrt              : string;         //Ort des Kunden
      FStraße           : string;         //Straße des Kunden
      FAnsprechpartnerId : integer;        //Ansprechpartnernummer
      FAnsprechpartner  : string;         //Ansprechpartner
      FAnsPosition      : string;         //Position/Stellung
      FEMail            : string;         //EMail des Ansprechpartners
      FTel1              : string;         //Telefonnummer 1 des Ansprechpartners
      FTel2              : string;         //Telefonnummer 2 des Ansprechpartners
      FFax              : string;         //Faxnummer des Ansprechpartners
      FBemerkung        : string;         //Kunden Zusatzinformation
    public
      property Kunden_Kundennummer : integer read FID write FID;
      property Kunden_ESKundennummer : integer read FESID write FESID;
      property Kunden_Kundenname : string read FName write FName;
      property Kunden_Ort : string read FOrt write FOrt;
      property Kunden_Straße : string read FStraße write FStraße;
      property Kunden_Bemerkung : string read FBemerkung write FBemerkung;
      property Ansprechpartner_Id : integer read FAnsprechpartnerId write FAnsprechpartnerId;
      property Ansprechpartner_Name : string read FAnsprechpartner write FAnsprechpartner;
      property Ansprechpartner_Position : string read FAnsPosition write FAnsPosition;
      property Ansprechpartner_EMail : string read FEMail write FEMail;
      property Ansprechpartner_Telefon1 : string read FTel1 write FTel1;
      property Ansprechpartner_Telefon2 : string read FTel2 write FTel2;
      property Ansprechpartner_Fax : string read FFax write FFax;
  end;

implementation

end.
ähnlich sieht das Objekt für "Objekte" und "System" aus.

In meinem Hauptformular nutze ich die Objekte wie folgt:

Delphi-Quellcode:
...
interface

uses
...

  type
    PCustomersData = ^TCustomersData;
    TCustomersData = record
      FCustomer_Object : TObject;
    end;

  type
    PBuildingsData = ^TBuildingsData;
    TBuildingsData = record
      FBuilding_Object : TObject;
    end;

  type
    PSystemsData = ^TSystemsData;
    TSystemsData = record
      FSystem_Object : TObject;
    end;

...

  private
    { Private-Deklarationen }
    var

    ....
    //Diese Globalen Variablen nutze ich für das LoadData
    TMyKunden_Daten : TOCustomers;
    TMyObjekt_Daten : TOBuilding;
    TMySystem_Daten : TOSystems;

...
  public
    { Public-Deklarationen }
    procedure LoadData;
var
  fMyForm: TfMyForm;

implementation

//Das hinzufügen der Daten mache ich dann über folgende Methode:

procedure TfMyForm.LoadData;
var
  I,J,K : Integer;
  CustomerNode, BuildingNode, SystemNode : PVirtualNode;
begin
 try
    vstKunden.BeginUpdate;
    vstKunden.Clear;

    CustomerNode := vstKunden.AddChild(nil);
    vstKunden.InvalidateNode (CustomerNode);
    vstKunden.NodeDataSize:=SizeOf(TCustomersData);
    {Hier glaube ich liegt das erste Problem. Ich setze NodeDataSize auf TCustomersData.
     Was aber ist mit TBuildingData und TSystemData? Benötige ich die nicht,
     weil es nur um den ersten Node geht?}


    //Jetzt lade ich die Kundendaten aus der DB und lasse das ganze durch eine for-Schleife laufen

              TMyKunden_Daten := TOCustomers.Create;
              with TMyKunden_Daten do
                begin
                  //Daten zufügen
   
              //Inerhalb der Schleife folgen dann die tieferen Datenpunkte
              BuildingNode := AddVSTCustomer(vstKunden,CustomerNode,TMyKunden_Daten);

              //Objektedaten laden aus Objektdatenbank
              //Systemdaten
              //Menüpunkte

      end;
    vstKunden.EndUpdate;
    ExpandedRootNodes(vstKunden);
    vstKunden.SortTree(0, sdAscending, True);
 finally
   //Queries schließen etc.
 end;
Delphi-Quellcode:
//Hier die Funktion AddVSTCustomer, die weiteren Add-Funktionen sind ähnlich
  function TfReportClient.AddVSTCustomer(AVST: TCustomVirtualStringTree; ANode: PVirtualNode;
    AObject: TObject): PVirtualNode;
  var
    Data : PCustomersData;
  begin
    Result := AVST.AddChild(ANode);
    AVST.ValidateNode(Result,False);
    Data := AVST.GetNodeData(Result);
    Data^.FCustomer_Object := AObject;
  end;
Dann weiße ich über "OnGetText" die Bezeichnung zu und über "OnGetImageIndex" noch ein paar Icons

Das "OnFreeNode" sieht aktuell wie folgt aus...

Delphi-Quellcode:
procedure TfReportClient.vstKundenFreeNode(Sender: TBaseVirtualTree;
  Node: PVirtualNode);
var
  Kunden_Daten : PCustomersData;
  Objekt_Daten : PBuildingsData;
  System_Daten : PSystemsData;
begin
  case vstKunden.GetNodeLevel(Node) of
    0:
    begin
      Kunden_Daten := Sender.GetNodeData(Node);
      Finalize (Kunden_Daten^);
    end;
    1:
    begin
      Objekt_Daten := Sender.GetNodeData(Node);
      Finalize (Objekt_Daten^);
    end;
    2:
    begin
      System_Daten := Sender.GetNodeData(Node);
      Finalize (System_Daten^);
    end;
  end;
end;
Die Globalen Var. gebe ich im FormDestroy wieder frei...
Delphi-Quellcode:
procedure TfReportClient.FormDestroy(Sender: TObject);
begin
  TMyKunden_Daten.Free;
  TMyObjekt_Daten.Free;
  TMySystem_Daten.Free;
end;
Ich vermute, das einige von Euch jetzt schon die Hände über dem Kopf zusammengeschlagen haben :oops:

Aber vieleicht könnt Ihr mir ja mal ein paar Tips geben.

Danke schon mal und Gruß Jens

TiGü 23. Okt 2015 09:30

AW: Fehler beim Programm beenden
 
Zitat:

Zitat von Jens Hartmann (Beitrag 1319491)
Delphi-Quellcode:
//Jetzt lade ich die Kundendaten aus der DB und lasse das ganze durch eine for-Schleife laufen

Dir ist schon bewusst, das du an dieser Stelle immer wieder ein neues Objekt erzeugst, aber nur die letzte Instanz davon im FormDestroy freigibst?

Beispiel: Wenn du 10 Kunden erzeugst
Delphi-Quellcode:
TMyKunden_Daten := TOCustomers.Create;
, gibst du im FormDestroy nur Nummer 9 frei.
Die Kunden 0 bis 8 hängen noch im Speicher rum und das sind deine Speicherlecks.

Verwalte doch diese Instanzen von Kunden, Systemen und Gebäuden in seperaten Objektlisten.
Bei Freigabe der Objektlisten werden dann auch die darin gespeicherten Instanzen freigeben.

Beispiel:
Delphi-Quellcode:
var
  I, J, K: Integer;
  CustomerNode, BuildingNode, SystemNode: PVirtualNode;
  LCustomer: TOCustomers;
begin
  VstKunden.BeginUpdate;
  try
    VstKunden.Clear;

    CustomerNode := VstKunden.AddChild(nil);
    VstKunden.InvalidateNode(CustomerNode);
    VstKunden.NodeDataSize := SizeOf(TCustomersData);
    { Hier glaube ich liegt das erste Problem. Ich setze NodeDataSize auf TCustomersData.
      Was aber ist mit TBuildingData und TSystemData? Benötige ich die nicht,
      weil es nur um den ersten Node geht? }

    FCustomers := TObjectList.Create;
    // Jetzt lade ich die Kundendaten aus der DB und lasse das ganze durch eine for-Schleife laufen
    for I := 0 to 10 do
    begin
      LCustomer := TOCustomers.Create;
      FCustomers.Add(LCustomer);

      LCustomer.Kunden_Kundenname := 'Hallo';
      LCustomer.Ansprechpartner_Position := 'Hallo';
      LCustomer.Ansprechpartner_Telefon1 := 'Hallo';

      // Inerhalb der Schleife folgen dann die tieferen Datenpunkte
      BuildingNode := AddVSTCustomer(VstKunden, CustomerNode, LCustomer);

      // Objektedaten laden aus Objektdatenbank
      // Systemdaten
      // Menüpunkte
    end;

    // ExpandedRootNodes(VstKunden);
    VstKunden.SortTree(0, SdAscending, True);
  finally
    VstKunden.EndUpdate;
  end;
end;

bcvs 23. Okt 2015 09:40

AW: Fehler beim Programm beenden
 
Ergänzung:

Das OnFreeNode des VST brauchst du dann nicht mehr, da du die Freigabe selbst machst.

Ist doch auch irgendwie logischer: Du erzeugst die Objekte, übergibst sie dem VST zur Anzeige, und zerstörst sie selbst wieder.

Zacherl 23. Okt 2015 10:13

AW: Fehler beim Programm beenden
 
Du benutzt den VST (meiner Meinung nach) recht .. ungewöhnlich :D Ich schaue später daheim nochmal genauer drüber und poste ein paar Verbesserungsvorschläge.

Edit:
Also, ich bin immer sehr gut damit gefahren, wenn ich einen einzigen Record-Typ für alle Nodes verwendet habe. Das ist auch die einzig valide Anwendungsweise. In deinem Falle hast du nur "Glück", dass deine Records alle gleich groß sind.

Wenn ich verschiedene Node Typen habe, mache ich das immer so:
Delphi-Quellcode:
type
  TNodeType = (ntCustomer, ntBulding, ..);

  PNodeData = ^TNodeData;
  TNodeData = record
    NodeType: TNodeType;
    NodeObject: TObject;
  end;
Deine Methode über das NodeLevel zu differenzieren geht natürlich auch.

Die
Delphi-Quellcode:
NodeDataSize
ist aber in jedem Falle immer
Delphi-Quellcode:
SizeOf(TNodeData)
für alle Nodes.

Bezüglich der Objektverwaltung sehe ich zwei Möglichkeiten:
  1. Du behälst die einzelnen Objekte in einer gemeinsamen Liste (jeweils eine für Kunden, Gebäude, etc) und gibst dem NodeData Record jeweils nur einen Zeiger auf das Objekt mit. Die Freigabe der Objekte erfolgt dann gemeinsam beim Freigeben der Liste (Achtung: Je nachdem musst du manuell iterieren und
    Delphi-Quellcode:
    Free
    aufrufen.
    Diese Methode trennt ganz gut Daten von der Anzeige, allerdings hast du ein Problem, wenn zur Laufzeit dynamisch Einträge aus der Liste gelöscht oder hinzugefügt werden sollen, bzw. musst du dann doppelten Aufwand betreiben.
  2. Die zweite Möglichkeit wäre deshalb beim Hinzufügen der Nodes deine Objekte mit
    Delphi-Quellcode:
    Create
    zu konstruieren und den Zeiger wieder entsprechend zuzuweisen. Dann implementierst du noch das
    Delphi-Quellcode:
    OnFreeNode
    Event und rufst darin
    Delphi-Quellcode:
    NodeData^.NodeObject.Free
    auf.
    Beim Löschen einer Node wird dann automatisch auch das Objekt freigegeben.

Jens Hartmann 23. Okt 2015 19:27

AW: Fehler beim Programm beenden
 
EDIT: Videos doch gefunden...

Erstmal vielen Dank für die Tips. Ich werde das ganz jetzt mal durcharbeiten und versuchen zu verbessern. Ich werden das dann hier nochmal einstellen.

PS: Kann man irgendwo noch auf das "Stammtisch" Video zum VST zugreifen?

Gruß Jens

Jens Hartmann 23. Okt 2015 20:09

AW: Fehler beim Programm beenden
 
Groß gesagt gefunden und dann war es doch nur das vom Stammtisch 1...

Weiß jemand wo das Video vom Stammtisch 2 zu finden ist?

Jens Hartmann 24. Okt 2015 18:50

AW: Fehler beim Programm beenden
 
Hallo nochmal,

ich glaube das ich bei der Definition der Datenhaltung (Objekt, Record) schon was verbessern muss. Eventuell kann mit hier jemand einen Tip geben...

Aktuell habe ich 3 Klassen "Kunde", "Objekt" und "System"

Der Aufbau sieht wie folgt aus:

Delphi-Quellcode:
{ TObject für die Kundendaten }
type
  TOCustomers = class(TObject)
    private
      FID               : integer;        //ID
      FESID             : integer;        //ESID
      FName             : string;         //Name des Kunden
      FOrt              : string;         //Ort des Kunden
      FStraße           : string;         //Straße des Kunden
      FAnsprechpartnerId : integer;        //Ansprechpartnernummer
      FAnsprechpartner  : string;         //Ansprechpartner
      FAnsPosition      : string;         //Position/Stellung
      FEMail            : string;         //EMail des Ansprechpartners
      FTel1              : string;         //Telefonnummer 1 des Ansprechpartners
      FTel2              : string;         //Telefonnummer 2 des Ansprechpartners
      FFax              : string;         //Faxnummer des Ansprechpartners
      FBemerkung        : string;         //Kunden Zusatzinformation
    public
      property Kunden_Kundennummer : integer read FID write FID;
      property Kunden_ESKundennummer : integer read FESID write FESID;
      property Kunden_Kundenname : string read FName write FName;
      property Kunden_Ort : string read FOrt write FOrt;
      property Kunden_Straße : string read FStraße write FStraße;
      property Kunden_Bemerkung : string read FBemerkung write FBemerkung;
      property Ansprechpartner_Id : integer read FAnsprechpartnerId write FAnsprechpartnerId;
      property Ansprechpartner_Name : string read FAnsprechpartner write FAnsprechpartner;
      property Ansprechpartner_Position : string read FAnsPosition write FAnsPosition;
      property Ansprechpartner_EMail : string read FEMail write FEMail;
      property Ansprechpartner_Telefon1 : string read FTel1 write FTel1;
      property Ansprechpartner_Telefon2 : string read FTel2 write FTel2;
      property Ansprechpartner_Fax : string read FFax write FFax;
  end;

//Das Object für die Objektdaten und Systemdaten ist ähnlich. Teilweise andere Felder
Den Bezug zum VST stelle ich über das NodeLevel her.

Delphi-Quellcode:
  NodeLevel 0 = Datenbankpfad
  NodeLevel 1 = Kundendaten
  NodeLevel 2 = Objektdaten
  NodeLevel 3 = Systemdaten
  NodeLevel 4 = Menüpunkte zum System
Beim Click auf den jeweiligen Knoten (VST in Baumstruktur auf der Linken Programmseite), werden auf der rechten Seite in verschiedenen Frames die zugehörigen Datenangezeigt und zum editieren etc. zur Verfügung gestellt.

Da ich drei Objekte habe, stellt sich mir die Frage erneut nach dem
Delphi-Quellcode:
NodeDataSize
. Wie geh man sowas an.

Vorstellen könnte ich mir folgende alternativen.

1.

Das Object "TOCustomers" bekommt ein Feld
Delphi-Quellcode:
Objektdaten array of TOBuildings
und das Object TOBulidings bekommt ein Feld
Delphi-Quellcode:
Systemdaten array of TOSystems
2.

Die Objecte liegen ja je in einer eigenen Unit. Diese könnte man ja eventuell in einen Record einbinden:

Delphi-Quellcode:
type
  TRKundenDaten = record
    FKundedaten : TOCustomers;
    FObjektdaten : TOBuildings;
    FSystemdaten : TOSystems;
  end;

  PRKundenDaten = ^TRKundenDaten;
Vieleicht gibt es ja noch bessere/sinnvollere Wege. Die Variante 2 scheint mir aktuell am sinnvollsten. Dabei würde ich einen Record verwenden, welchen ich dann auch dem NodeDataSize zuweisen könnte.

Delphi-Quellcode:
vst.NodeDataSize:=sizeof(TRKundenDaten);
Weiterhin, wäre dann das OnFreeNode klar...

Delphi-Quellcode:
procedure TForm1.vstFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
var
  daten: PRMeineDaten;
begin
  daten := vst.GetNodeData(node);
  daten^.FKundendaten.free;
  daten^.FObjektdaten.free;
  daten^.FSystemdaten.free;
end;
Ich hoffe, das ich nicht ganz auf dem Holzweg bin...

TiGü 26. Okt 2015 08:38

AW: Fehler beim Programm beenden
 
Zitat:

Zitat von Jens Hartmann (Beitrag 1319675)
Eventuell kann mit hier jemand einen Tip geben...

Schaue dir den editierten Post von Zacherl an:
http://www.delphipraxis.net/1319525-post21.html

Wahrscheinlich hast du übersehen, das er ihn erweitert hat.
Da steht alles drin, was du wissen musst.
Punkt Nummer eins habe ich dir bspw. schon auf der vorigen Threadseite gezeigt.
Du musst die Vorschläge nur verstehen und umsetzen.

Jens Hartmann 26. Okt 2015 20:24

AW: Fehler beim Programm beenden
 
Zitat:

Zitat von TiGü
Wahrscheinlich hast du übersehen, das er ihn erweitert hat.
Da steht alles drin, was du wissen musst.
Punkt Nummer eins habe ich dir bspw. schon auf der vorigen Threadseite gezeigt.
Du musst die Vorschläge nur verstehen und umsetzen.

Hallo TiGü,

ja zu erstens, ich habe das übersehen. Ich glaube auch das da alles drin steht, aber der letze Punkt trifft leider auch zu. Ich habe das ganze jetzt versucht mal umzubauen. Als erstes hier mal mein neuer "record"

Delphi-Quellcode:
  type
  TRKundenDaten = record
    FKundedaten : TOCustomers;
    FObjektdaten : TOBuilding;
    FSystemdaten : TOSystems;
  end;

  PRKundenDaten = ^TRKundenDaten;
Aber der erste Crash kommt direkt beim einlesen...

Delphi-Quellcode:
var
  Node, CustomerNode, BuildingNode, SystemNode, ReportNode : PVirtualNode;
  Daten : PRKundenDaten;
begin
 try
    ...
    CustomerNode := vstKunden.AddChild(nil); //in diese Knoten wir nach nur der BD-Pfad geschrieben...

    //Kundendaten laden aus Kundendatenbank
        ...
        //Dann durch alle Datensätze mit einer Schleife...

        for i := 0 to DMMasterData.qryCustomerData.RecordCount -1 do
          begin
            try
              BuildingNode := vstKunden.AddChild(nil);
              Daten := vstKunden.GetNodeData(CustomerNode);
              //Mit Create und ohne versucht... Aber hier kracht es
              {Daten^.FKundedaten := TOCustomers.create;
              Daten^.FObjektdaten := TOBuilding.create;
              Daten^.FSystemdaten := TOSystems.create;}

              with Daten^ do
                begin
                  FKundedaten.Kunden_Kundennummer := DMMasterData.qryCustomerData.FieldByName('Kunden_Kundennummer').AsInteger;
                  ...
                end;

TiGü 27. Okt 2015 08:56

AW: Fehler beim Programm beenden
 
Ich würde den Weg von Zacherl gehen, also ein Record mit einen Enum-Typen zur Erkennung und einen allgemeinen TObject-Platzhalter, anstatt alle drei Objekte da rein zuverwursten.
Irgendwann kommt nämlich noch ein weiteres Objekt dazu und dann noch eins und noch eins...

Es wäre auch super mega klasse hilfreich, wenn du ein kleines Beispielprojekt zusammenstellen könntest.
Oft zeigt sich dann, dass das Problem an ganz anderen Stellen liegt.

Wenn man nämlich als hilfsbereiter Threadleser sich selber sowas ähnliches zusammenzimmert - siehe im folgenden - dann kracht es nicht!
So weiß man natürlich nicht, woran es genau in deinen Quelltext scheitert.

Delphi-Quellcode:
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, VirtualTrees;

type
  TOCustomers = class(TObject)
  private
    FID: Integer; // ID
    FESID: Integer; // ESID
    FName: string; // Name des Kunden
    FOrt: string; // Ort des Kunden
    FStraße: string; // Straße des Kunden
    FAnsprechpartnerId: Integer; // Ansprechpartnernummer
    FAnsprechpartner: string; // Ansprechpartner
    FAnsPosition: string; // Position/Stellung
    FEMail: string; // EMail des Ansprechpartners
    FTel1: string; // Telefonnummer 1 des Ansprechpartners
    FTel2: string; // Telefonnummer 2 des Ansprechpartners
    FFax: string; // Faxnummer des Ansprechpartners
    FBemerkung: string; // Kunden Zusatzinformation
  public
    property Kunden_Kundennummer: Integer read FID write FID;
    property Kunden_ESKundennummer: Integer read FESID write FESID;
    property Kunden_Kundenname: string read FName write FName;
    property Kunden_Ort: string read FOrt write FOrt;
    property Kunden_Straße: string read FStraße write FStraße;
    property Kunden_Bemerkung: string read FBemerkung write FBemerkung;
    property Ansprechpartner_Id: Integer read FAnsprechpartnerId write FAnsprechpartnerId;
    property Ansprechpartner_Name: string read FAnsprechpartner write FAnsprechpartner;
    property Ansprechpartner_Position: string read FAnsPosition write FAnsPosition;
    property Ansprechpartner_EMail: string read FEMail write FEMail;
    property Ansprechpartner_Telefon1: string read FTel1 write FTel1;
    property Ansprechpartner_Telefon2: string read FTel2 write FTel2;
    property Ansprechpartner_Fax: string read FFax write FFax;

  end;

  TOBuilding = class
  end;

  TOSystems = class

  end;

  TRKundenDaten = record
    FKundedaten: TOCustomers;
    FObjektdaten: TOBuilding;
    FSystemdaten: TOSystems;
  end;

  PRKundenDaten = ^TRKundenDaten;

type
  TForm1 = class(TForm)
    vstKunden: TVirtualStringTree;
    procedure FormCreate(Sender: TObject);
    procedure VstKundenFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
  private
  public
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
var
  I : Integer;
  CustomerNode, BuildingNode, SystemNode: PVirtualNode;
  Daten: PRKundenDaten;
begin
  vstKunden.BeginUpdate;
  try
    vstKunden.Clear;

    CustomerNode := vstKunden.AddChild(nil);
    vstKunden.InvalidateNode(CustomerNode);
    vstKunden.NodeDataSize := SizeOf(TRKundenDaten);

    for I := 0 to 10 do
    begin
      BuildingNode := vstKunden.AddChild(nil);
      Daten := vstKunden.GetNodeData(CustomerNode);
      // das folgende geht anstandslos
      Daten^.FKundedaten := TOCustomers.Create;
      Daten^.FObjektdaten := TOBuilding.Create;
      Daten^.FSystemdaten := TOSystems.Create;
    end;

    vstKunden.SortTree(0, sdAscending, True);
  finally
    vstKunden.EndUpdate;
  end;
end;

procedure TForm1.VstKundenFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
var
  Kunden_Daten: PRKundenDaten;
begin
  case vstKunden.GetNodeLevel(Node) of
    0:
    begin
      Kunden_Daten := Sender.GetNodeData(Node);
      Finalize(Kunden_Daten^);
    end;
  end;
end;

end.

Jens Hartmann 27. Okt 2015 19:49

AW: Fehler beim Programm beenden
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hallo TiGü,

als Anlage mal das Musterprojekt genau nach Deinen vorgaben. Trotzallem immer noch der gleiche Fehler. Ich habe auch mal mit FastMM eingebunden. Das EventLog liegt dem Musterprojekt bei. Selbst in diesem kleinen Projekt entsteht der gleiche Fehler. Ich habe auch nochmal mein aktuelles Projekt geprüft und bearbeitet. Ich habe diese genau nach Deinem Vorschlag zusammengebaut und wie im Musterprojekt kommt die Fehlermeldung.

Grundlegend habe ich noch Fragen zu Zacherls vorschlagen und der Umsetzung. Das aber vieleicht später. Erstmal stellt sich mir die Frage, ob die Komponenten eventuell verbogen oder falsch konfiguriert ist. 2 Alternative, kann es mit Windows 10 zusammenhängen?

Gruß Jens

bcvs 28. Okt 2015 07:50

AW: Fehler beim Programm beenden
 
Zwei Sachen sind mir aufgefallen:

1.
Delphi-Quellcode:
vstKunden.NodeDataSize := SizeOf(TRKundenDaten);
muss vor dem ersten AddChild stehen, sonst weiß der VST ja nicht, wie groß der Node sein soll und nachfolgende Speicheroperationen laufen ins (n)irgendwo.

2.
Wie schon gesagt, solltest du die Objekte in einer separaten Liste speichern und diese selbst wieder freigeben. Dann ist die Zuständigkeit sauber geregelt und du kannst auf das OnFreeNode komplett verzichten. Der VST arbeitet nur mit den Daten, der braucht sich nicht darum zu kümmern wo die herkommen und ob die freigegeben werden müssen.

Delphi-Quellcode:
procedure TForm1.FormCreate(Sender: TObject);
var
  I : Integer;
  CustomerNode, BuildingNode, SystemNode: PVirtualNode;
  Daten: PRKundenDaten;
  fk:TOCustomers;
  fo:TOBuilding;
  fs:TOSystems;
begin
  vstKunden.NodeDataSize := SizeOf(TRKundenDaten);

  KundenList:=TObjectList.create; // separate Liste
  vstKunden.BeginUpdate;
  try
    vstKunden.Clear;

    CustomerNode := vstKunden.AddChild(nil);
    vstKunden.InvalidateNode(CustomerNode);

    for I := 0 to 10 do
    begin
      BuildingNode := vstKunden.AddChild(nil);
      Daten := vstKunden.GetNodeData(CustomerNode);
      // das folgende geht anstandslos
      fk := TOCustomers.Create;
      fo := TOBuilding.Create;
      fs := TOSystems.Create;

      Daten^.FKundedaten := fk;
      Daten^.FObjektdaten := fo;
      Daten^.FSystemdaten := fs;
      KundenList.Add(fk); // wahrscheinlich reicht hier aber auch ein Object pro Node
      KundenList.Add(fo);
      KundenList.Add(fs);
    end;

    vstKunden.SortTree(0, sdAscending, True);
  finally
    vstKunden.EndUpdate;
  end;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  KundenList.free;
end;

TiGü 28. Okt 2015 09:03

AW: Fehler beim Programm beenden
 
Zitat:

Zitat von Jens Hartmann (Beitrag 1319874)
als Anlage mal das Musterprojekt genau nach Deinen vorgaben. Trotzallem immer noch der gleiche Fehler.

Ändere mal in der for-Schleife diese Zeilen:
Delphi-Quellcode:
   
 for I := 0 to 10 do
    begin
      BuildingNode := vstKunden.AddChild(nil);
      Daten := vstKunden.GetNodeData(CustomerNode);
...
in folgende Zeilen:

Delphi-Quellcode:
   
 for I := 0 to 10 do
    begin
      BuildingNode := vstKunden.AddChild(nil);
      Daten := vstKunden.GetNodeData(BuildingNode);
...
Außerdem bitte noch das OnFreeNode ergänzen:
Delphi-Quellcode:
procedure TForm1.vstKundenFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
var
  Kunden_Daten: PRKundenDaten;
begin
  case vstKunden.GetNodeLevel(Node) of
    0:
    begin
      Kunden_Daten := Sender.GetNodeData(Node);
      Kunden_Daten^.FKundedaten.Free;
      Kunden_Daten^.FObjektdaten.Free;
      Kunden_Daten^.FSystemdaten.Free;
      Finalize(Kunden_Daten^);
    end;
  end;
end;
Und jetzt darfst du gerne mit den Kopf auf den Tisch schlagen. :wall: :mrgreen:

PS: Trotzdem wäre der Weg mit externer Datenhaltung über eine Objektliste langfristig besser!
Bitte beschäftige dich damit.

Jens Hartmann 29. Okt 2015 21:21

AW: Fehler beim Programm beenden
 
Also ich glaube ich bin zu doof dazu. Ich krieg das irgendwie nicht umgesetzt.

Ich will daher nochmal freundlichst die Frage stellen. Um mir das ganze nochmal in Ruhe ansehen zu können, würde ich gerne das Stammtisch Video 2 nochmal sehen. Aber wie ja bereits festgestellt, gibt es das nicht mehr. Hat den nicht einer der Administratoren das Video noch?

Zusätzlich wäre es schon, wenn mir mal jemand den Vorschlag mit der Objektliste erklären könnte.

Danke schon mal. Gruß Jens

Perlsau 29. Okt 2015 23:24

AW: Fehler beim Programm beenden
 
Liste der Anhänge anzeigen (Anzahl: 3)
Hab mir dein Test-Projekt heruntergeladen, ausprobiert und untersucht. Dabei ist mir aufgefallen, daß ich den VirtualStringTree (VST) ganz anders verwende als du:

Als erstes lege ich einen Record für NodeData fest, damit ich dort die Daten des Nodes eintragen kann, die am Ende in den diversen Spalten des VST angezeigt werden sollen. Das fehlt bei dir. Wenn ich Objekte erzeuge, die angezeigt werden sollen, ob nun mit oder ohne VST, verwende ich die TObjectList, und zwar nicht die aus der Unit Contnrs, sondern die aus den Generics, die es, soweit ich weiß, erst seit Delphi 2009 gibt:

Delphi-Quellcode:
BList : Generics.Collections.TObjectList<TStartBild>;
Ich versuch dir das mal an einem meiner etwas älteren Projekte zu erklären:

In einer Unit habe ich eine Klasse für die Bilder, die angezeigt werden sollen. Eine Klasse deshalb, weil zum Bild auch ein TLabel gehört:
Delphi-Quellcode:
UNIT Startbilder;

INTERFACE

USES
  ExtCtrls, StdCtrls, Classes, Graphics;

TYPE
  TStartBild = Class

    PRIVATE { Private-Deklarationen }
      Var
        fModulId : Integer;
        fBild   : TImage;
        fTitel  : TLabel;

      Function GetfModulId            : Integer;
      Procedure SetfModulId(Const Value : Integer);
      Function GetfBild               : TImage;
      Procedure SetfBild(Const Value   : TImage);
      Function GetfTitel              : TLabel;
      Procedure SetfTitel(Const Value  : TLabel);

    PUBLIC { Public-Deklarationen }

      Constructor Create();
      Destructor Destroy; override;

      Property ModulId : Integer read GetfModulId write SetfModulId;
      Property Bild   : TImage read GetfBild   write SetfBild;
      Property Titel  : TLabel read GetfTitel  write SetfTitel;

  END;

IMPLEMENTATION
{ TStartBild }

// ----- Modul-Id zurückliefern -------------------------------------------------------------------------------------------------- Privat
Function TStartBild.GetfModulId: Integer;
begin
  Result := fModulId;
end;

// ----- Modul-Id setzen --------------------------------------------------------------------------------------------------------- Privat
Procedure TStartBild.SetfModulId(Const Value: Integer);
begin
  fModulId := Value;
end;

// ----- Bild zurückliefern ------------------------------------------------------------------------------------------------------ Privat
Function TStartBild.GetfBild: TImage;
begin
  Result := fBild;
end;

// ----- Bild setzen ------------------------------------------------------------------------------------------------------------- Privat
Procedure TStartBild.SetfBild(Const Value: TImage);
begin
  fBild.Assign(Value);
end;

// ----- Label zurückliefern ----------------------------------------------------------------------------------------------------- Privat
Function TStartBild.GetfTitel: TLabel;
begin
  Result := fTitel;
end;

// ----- Label setzen ------------------------------------------------------------------------------------------------------------ Privat
Procedure TStartBild.SetfTitel(Const Value: TLabel);
begin
  fTitel.Assign(Value);
end;

// ########## PUBLIC METHODEN ########################################################################################################

// ----- Constructor Create ------------------------------------------------------------------------------------------------------ Privat
Constructor TStartBild.Create;
begin
  inherited;

  fBild                      := TImage.Create(nil);
  fBild.Visible              := False;
  fBild.Constraints.MaxWidth := 500;
  fBild.Constraints.MaxHeight := 500;
  fBild.Constraints.MinWidth := 100;
  fBild.Constraints.MinHeight := 100;
  fBild.AutoSize             := False;
  fBild.Stretch              := True;
  fBild.Proportional         := True;
  fBild.Center               := True;

  fBild.Picture.Bitmap.Canvas.Brush.Style := bsClear;
  fBild.Picture.Bitmap.Canvas.Pen.Color := clRed;
  fBild.Picture.Bitmap.Canvas.Pen.Style := psSolid;
  fBild.Picture.Bitmap.Canvas.Pen.Width := 5;

  fTitel                     := TLabel.Create(nil);
  fTitel.Visible             := False;
  fTitel.AutoSize            := False;
  fTitel.Layout              := tlCenter;
  fTitel.Alignment           := taCenter;
  fTitel.WordWrap            := True;
  fTitel.ParentFont          := True;
end;

// ----- Destructor Destroy ------------------------------------------------------------------------------------------------------ Privat
Destructor TStartBild.Destroy;
begin
  If Assigned(fBild) Then
     fBild.Free;
  If Assigned(fTitel) Then
     fTitel.Free;

  inherited;
end;

end.
Die einzelnen Module dieser Anwendung befinden sich in Frames. Beim Programmstart wird der Startframe geladen und bereitgestellt. Das ist der Frame, der in den beiden Beispiel-Grafiken unten angezeigt wird. In diesem Frame kann ich nun zwischen Icon- und Baumdarstellung wählen; Grundlage für beide Darstellungsformen ist die Objektliste. In der Function ObjektNeu erzeuge ich die Objektliste und weise ihr die in der Datenbank gespeicherten Icons, Texte und Beschreibungen zu. Die Variable Objekt vom Typ TStartBild darf nicht freigegeben werden, da der darin enthaltene Pointer direkt dem jeweiligen Item der Objektliste übergeben wird:
Delphi-Quellcode:
Function TFrame_Main.ObjektNeu(Const PId : Integer) : Integer;
Const
  BildPreString = 'Img_';
  LabelPreString = 'Lbl_';
Var
  Objekt : TStartBild;
  LblName : String;
  Bild   : TBitMap;
  R      : TRect;

begin
  Result := -1;
  Objekt := TStartBild.Create;
  Bild  := TBitMap.Create;

  Try
    Bild.PixelFormat := pf24bit;
    Bild.Width      := ImgList.Width;
    Bild.Height     := ImgList.Height;
    R.Left          := 0;
    R.Top           := 0;
    R.Right         := Bild.Width -1;
    R.Bottom        := Bild.Height -1;

      Try
        LblName                 := RemoveUnwantetChars(Titel);
        Objekt.ModulId          := fProgModus;
        Objekt.Bild.Parent      := Panel_Start;
        Objekt.Bild.Name        := BildPreString + LblName;
        Objekt.Bild.Width       := Laenge;
        Objekt.Bild.Height      := Laenge;
        Objekt.Bild.Canvas.Font := Panel_Start.Font;
        Objekt.Bild.OnClick     := BildGeklickt;

        Objekt.Titel.Parent     := Panel_Start;
        Objekt.Titel.Name       := LabelPreString + LblName;
        Objekt.Titel.Caption    := Titel;
        Objekt.Titel.OnClick    := TitelGeklickt;
        Objekt.Titel.OnMouseMove := TitelMausBewegt;
        Objekt.Titel.Hint       := HintText;
        Objekt.Titel.ShowHint   := HintsZeigen;

        If Not DatMod.BlobToImage(Feld,Objekt.Bild.Picture.Bitmap) Then
               Raise Exception.Create('Fehler beim Einlesen eines Blobfelds in ein Bitmap ')
                     at @TFrame_Main.ObjektNeu;

        If PId = 0 Then RahmenZeichnen(Objekt.Bild.Picture.Bitmap.Canvas);

        Result          := BList.Add(Objekt);
        Objekt.Bild.Tag := fProgModus;
        Objekt.Titel.Tag := Result;

        Bild.Canvas.StretchDraw(R,Objekt.Bild.Picture.Graphic);
        ImgList.Add(Bild,Nil);

      Except
        On e:Exception Do
        Begin
          If Assigned(Objekt)
             Then Objekt.Free;
          fInitOkay := e.Message;
        End;
      End;
  Finally
    Bild.Free;
  End;
end;
Je nachdem, welche Darstellungsform der Anwender nun wählt (Baum oder Icons), wird der Baum gezeichnet oder die Icons:
Delphi-Quellcode:
// ----- Zeichnet die Bilder auf das Panel in der Scrollbox --------------------------------------------------------------------- Privat
Procedure TFrame_Main.Zeichnen;
Var
  i,z,
  Titel_Breite,
  Titel_Hoehe,
  GesamtHoehe,
  GesamtBreite,
  HoehenMulti,
  AnzahlX,
  AnzahlY,
  X,Y         : Integer;
  Obj         : TStartBild;

// Panel an Scrollbox-Breite anpassen
Procedure PanelBreiteAnScrollBox;
Begin
  If ScrollBox_Start.VertScrollBar.Visible                 Then
     Panel_Start.Width := ScrollBox_Start.ClientWidth - 4 Else
     Panel_Start.Width := ScrollBox_Start.ClientWidth + 15;
End;

// Code innerhalb der For-Schleife
Procedure ObjekteZeigen;
Begin
  Obj              := BList[i];
  Obj.Bild.Width   := Laenge;
  Obj.Bild.Height  := Laenge;
  Obj.Titel.Width  := Laenge;
  Obj.Titel.Height := TitelHoehe;
  Obj.Bild.Left    := X;
  Obj.Bild.Top     := Y;
  Obj.Titel.Left   := X;
  Obj.Titel.Top    := Obj.Bild.Top + Laenge;
  Obj.Titel.Color  := TitelFarbe;
  Obj.Titel.Font   := Panel_Start.Font;
  Obj.Bild.Visible := True;
  Obj.Titel.Visible := True;

//  Application.ProcessMessages;

  X := X + GesamtBreite;
  If X + GesamtBreite > Panel_Start.ClientWidth Then
  Begin
    X := Abstand;
    Y := Y + GesamtHoehe;
  End;
End;

// ********** HAUPTPROCEDURE **********
begin
  If Not Self.Visible Then Exit;

  z := BList.Count;

  If z > 0 Then
  Begin
    Label_Schriftart.Color := TitelFarbe;
    PanelBreiteAnScrollBox;

    TitelHoehe := 0;
    For i := 0 To z-1 Do
    Begin
      Obj                 := BList[i];
      Obj.Bild.Visible    := False;
      Obj.Titel.Visible   := False;
      Obj.Bild.Canvas.Font := Panel_Start.Font;
      Titel_Breite        := Obj.Bild.Canvas.TextWidth(Obj.Titel.Caption) + 10;
      If Titel_Breite > Laenge Then
         HoehenMulti := 2      Else
         HoehenMulti := 1;
      Titel_Hoehe         := (Obj.Bild.Canvas.TextHeight(Obj.Titel.Caption) + (HoehenMulti * 6)) * HoehenMulti;
      If Titel_Hoehe > TitelHoehe Then
         TitelHoehe       := Titel_Hoehe;
    End;

    GesamtBreite := Laenge + Abstand;
    GesamtHoehe := Laenge + Abstand + TitelHoehe;
    X           := Abstand;
    Y           := Abstand;

    AnzahlX := (Panel_Start.Width - Abstand) Div GesamtBreite;
    If AnzahlX >= z Then
    Begin
      Panel_Start.Height := GesamtHoehe + Abstand;
      PanelBreiteAnScrollBox;
    End Else
    Begin
      AnzahlY := z Div AnzahlX;
      If z Mod AnzahlX > 0 Then Inc(AnzahlY);
      Panel_Start.Height := (AnzahlY * GesamtHoehe) + Abstand;
      PanelBreiteAnScrollBox;
    End;

    AnzahlX := (Panel_Start.Width - Abstand) Div GesamtBreite;
    If AnzahlX >= z Then
    Begin
      Panel_Start.Height := GesamtHoehe + Abstand;
      PanelBreiteAnScrollBox;
    End Else
    Begin
      AnzahlY := z Div AnzahlX;
      If z Mod AnzahlX > 0 Then Inc(AnzahlY);
      Panel_Start.Height := (AnzahlY * GesamtHoehe) + Abstand;
      PanelBreiteAnScrollBox;
    End;

    For i := 0 To z-1 Do ObjekteZeigen;
  End;
end;

// ----- Baumdarstelung initialisieren ------------------------------------------------------------------------------------------ Privat
Function TFrame_Main.BaumInit(Sender: TBaseVirtualTree) : Boolean;
Var
  Data  : PNodeData;
  Node  : PVirtualNode;
  PId   : Integer;

begin
  Try
    If Not DatMod.Qset_Modulix.Active Then
           DatMod.Qset_Modulix.Open;

    DatMod.Qset_Modulix.Filter  := 'ID_PARENT=0 and BILD<>null';
    DatMod.Qset_Modulix.Filtered := True;

    If DatMod.Qset_Modulix.RecordCount > 0 Then
    Begin
      DatMod.Qset_Modulix.First;

      VST.Clear;
      VST.BeginUpdate;

// Haupteinträge (Parent = 0)
      While Not DatMod.Qset_Modulix.Eof Do
      Begin
        Node        := Sender.AddChild(Sender.RootNode);
        Data        := Sender.GetNodeData(Node);
        Data.ImgId  := DatMod.Qset_Modulix.FieldByName('REIHENFOLGE').AsInteger;
        Data.Id     := DatMod.Qset_Modulix.FieldByName('ID_MODULIX').AsInteger;
        Data.PId    := DatMod.Qset_Modulix.FieldByName('ID_PARENT').AsInteger;
        Data.Titel  := DatMod.Qset_Modulix.FieldByName('TITEL').AsString;
        Data.Caption := DatMod.Qset_Modulix.FieldByName('CAPTION').AsString;

        DatMod.Qset_Modulix.Next;
      End;

// Untereinträge (Parent > 0)
      DatMod.Qset_Modulix.Filter  := 'ID_PARENT>0 and BILD<>null';
      If DatMod.Qset_Modulix.RecordCount > 0 Then
      Begin
        DatMod.Qset_Modulix.First;
        While Not DatMod.Qset_Modulix.Eof Do
        Begin
          PId := DatMod.Qset_Modulix.FieldByName('ID_PARENT').AsInteger;
          Node := GetNodeBy_Id(PId);

          If Node <> Nil Then
          Begin
            Node := VST.AddChild(Node);
            Data        := Sender.GetNodeData(Node);
            Data.ImgId  := DatMod.Qset_Modulix.FieldByName('REIHENFOLGE').AsInteger;
            Data.Id     := DatMod.Qset_Modulix.FieldByName('ID_MODULIX').AsInteger;
            Data.PId    := PId;
            Data.Titel  := DatMod.Qset_Modulix.FieldByName('TITEL').AsString;
            Data.Caption := DatMod.Qset_Modulix.FieldByName('CAPTION').AsString;
          End;

          DatMod.Qset_Modulix.Next;
        End;

      End;

      VST.EndUpdate;

      DatMod.Qset_Modulix.Filtered := False;
      Result := True;

    End Else Raise Exception.Create('keine Parent=0 Einträge in der Datenbank') at @TFrame_Main.BaumInit;
  Except
    on e:exception Do
    Begin
      Result   := False;
      fInitOkay := e.Message;
    End;
  End;
end;
Die komplette UnitFrameMain incl. DFM-Datei hab ich angehängt.

bcvs 30. Okt 2015 07:04

AW: Fehler beim Programm beenden
 
Zitat:

Zitat von Jens Hartmann (Beitrag 1320159)
Zusätzlich wäre es schon, wenn mir mal jemand den Vorschlag mit der Objektliste erklären könnte.

Danke schon mal. Gruß Jens

Ich mache es mal anhand von deinem Beispiel. Ist doch ganz einfach:

Du definiert die eine ObjectList. Da ich Delphi 2007 habe, nehme ich zwangsläufig die einfache TOjectList aus der Unit Contnrs;

Delphi-Quellcode:
type
  TForm1 = class(TForm)
    vstKunden: TVirtualStringTree;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private-Deklarationen }
    KundenList:TObjectList; // <-----
  public
    { Public-Deklarationen }
  end;
Beim Einlesen des Trees passiert folgendes:

Delphi-Quellcode:
procedure TForm1.FormCreate(Sender: TObject);
var
  I : Integer;
  CustomerNode, BuildingNode, SystemNode: PVirtualNode;
  Daten: PRKundenDaten;
  fk:TOCustomers;
  fo:TOBuilding;
  fs:TOSystems;
begin
  vstKunden.NodeDataSize := SizeOf(TRKundenDaten);

  KundenList:=TObjectList.create; // <--- Instanz der ObjectList erzeugen;

  // Besser wäre es, alles ab hier in eine eigene Methode auszulagern,
  // damit man das Einlesen des Trees vom Erzeugen des Liste trennen kann.
  // Vielleicht soll der Tree ja wärend der Lebenszeit des Form nochmal neu eingelesen werden.
  vstKunden.BeginUpdate;
  try
    vstKunden.Clear;

    //CustomerNode := vstKunden.AddChild(nil); wozu soll das gut sein?
    //vstKunden.InvalidateNode(CustomerNode);

    for I := 0 to 10 do
    begin
      BuildingNode := vstKunden.AddChild(nil);
      Daten := vstKunden.GetNodeData(BuildingNode);

      fk := TOCustomers.Create; // <---- Datenobjekte erzeugen
      fo := TOBuilding.Create; // Wahrscheinlich brauchst du nur ein Objekt pro Node,
      fs := TOSystems.Create;  // das dann die verschiedenen Node-Typen verwalten kann.
                                // Das ist aber ein anderes Thema

      Daten^.FKundedaten := fk; // <---- Datenobjekte dem VST übergeben
      Daten^.FObjektdaten := fo;
      Daten^.FSystemdaten := fs;

      KundenList.Add(fk); // <---- Datenobjekte der Liste hinzufügen
      KundenList.Add(fo); // Die Objekte gehören jetzt der ObjectList
      KundenList.Add(fs);
    end;

    vstKunden.SortTree(0, sdAscending, True);
  finally
    vstKunden.EndUpdate;
  end;
end;
Zum Schluss musst du nur die ObjectList wieder freigeben. Damit werden automatisch auch die darin enthaltenen Objekte freigegeben. Der VST muss sich um nichts mehr kümmern.

Delphi-Quellcode:
procedure TForm1.FormDestroy(Sender: TObject);
begin
  KundenList.free;
end;

TiGü 30. Okt 2015 08:41

AW: Fehler beim Programm beenden
 
Zitat:

Zitat von Jens Hartmann (Beitrag 1320159)
Also ich glaube ich bin zu doof dazu. Ich krieg das irgendwie nicht umgesetzt.

Was ist daran so schwierig, zwei/dreizeilige Codeschnipsel zu kopieren und auszutauschen?

Zitat:

Zitat von Jens Hartmann (Beitrag 1320159)
Ich will daher nochmal freundlichst die Frage stellen. Um mir das ganze nochmal in Ruhe ansehen zu können, würde ich gerne das Stammtisch Video 2 nochmal sehen. Aber wie ja bereits festgestellt, gibt es das nicht mehr. Hat den nicht einer der Administratoren das Video noch?

Wenn dir die ganzen Ratschläge und Codeschnipsel nicht ausreichen, mal ganz abgesehen von den vorhandenen und mit Google auffindbaren Tutorials (http://www.delphi-treff.de/tutorials...ualtreeview/6/), wie sollte dir da ein Video helfen?
Auch das wird schwerlich genau auf deinen Fall eingehen, sondern nur allgemeine Vorgehensweisen beschreiben.
Die Transferleistung musst du selber erbringen.

Zitat:

Zitat von Jens Hartmann (Beitrag 1320159)
Zusätzlich wäre es schon, wenn mir mal jemand den Vorschlag mit der Objektliste erklären könnte.

Stelle mal bitte konkrete Fragen dazu!
Was verstehst du genau nicht?
Inzwischen wurde dir diese Variante vier bis fünf Mal angeraten und erklärt.

Jens Hartmann 6. Nov 2015 20:35

AW: Fehler beim Programm beenden
 
So, nicht das noch einer glaubt, der Beitrag interresiert mich nicht mehr. Ich bin schon die ganze Woche dabei, das VST mit verschiedenen Tutorials mir in den Kopf zu arbeiten.

Das ganze ist auch mittlerweile Verständlicher für mich. Ich mache das ganze aktuell mit einem Record und auch ohne Fehlermeldung. Mein Verständnisproblem liegt allerdings aktuell trotzallem noch in der Datenhaltung.

Folgenden Aufbau möchte ich kurz darstellen:

Delphi-Quellcode:
//So sieht jetzt mein neuer Datenrecord aus
  type
    pVSTNodeData = ^rVSTNodeData;
    rVSTNodeData = record
      KundenName, ObjektName, AnlagenTyp, AnlagenBezeichnung : WideString;
      KundenNr, ObjektNr, AnlagenNr : Integer;
    end;

//Über folgende Funktion lade ich jetzt mein VST (Allerdings nur den ersten Root mit Daten und diesen setze ich im OnInit auf vsHASChildren)

procedure TfReportClient.LoadVST;
var
  pNode : PVirtualNode;
  i : Integer;
begin
  try
    try
      vstKunden.BeginUpdate;
      vstKunden.Clear;
      //Datenpfad Knoten erstellen
      pNode := vstKunden.AddChild(nil);
      //Kundendaten laden und zugehörige Knoten erstellen
      LoadCustomersData(pNode);
      //vst Fertigstellen
      vstKunden.EndUpdate;
      ExpandedRootNodes(vstKunden);
      vstKunden.SortTree(0,sdAscending,True);
    except
      //...
    end;
  finally
    //...
  end;
end;

procedure TfReportClient.LoadCustomersData(ANode : PVirtualNode);
var
  pNode : PVirtualNode;
  Daten : pVSTNodeData;
  i : Integer;
begin
  try
    try
      //Kundentabelle öffnen
      if DMMasterData.OpenCustomerQuery then
      begin
        //Ersten Datensatz wählen
        DMMasterData.qryCustomerData.First;
        //Schleife um alle Kunden zu durchlaufen und die Knoten für die Kunden zu erstellen.
        for i := 0 to DMMasterData.qryCustomerData.RecordCount -1 do
          begin

            //Kunden Knoten erstellen
            pNode := vstKunden.AddChild(ANode);

            //Daten zuweisen
            Daten := vstKunden.GetNodeData(pNode);
            Daten^.KundenName := DMMasterData.qryCustomerData.FieldByName('Kunden_Kundenname').AsString;
            Daten^.KundenNr := DMMasterData.qryCustomerData.FieldByName('Kunden_Kundennummer').AsInteger;

            //Nächsten Datensatz anwählen
            DMMasterData.qryCustomerData.Next;
          end;
      end;
    except

    end;
  finally
    //Datenbank wieder schliessen
  end;
end;

//Die einzelnen Nodes darauf vorbereiten, das Sie Childs haben
procedure TfReportClient.vstKundenInitNode(Sender: TBaseVirtualTree; ParentNode,
  Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
begin
  case vstKunden.GetNodeLevel(Node) of
    1:
     begin
       vstKunden.CheckType[Node] := ctCheckBox;
       vstKunden.CheckState[Node] := csCheckedNormal;
       vstKunden.HasChildren[Node] := True;
     end;
    2:
     begin
       vstKunden.HasChildren[Node] := True;
     end;
    3:
     begin
       vstKunden.HasChildren[Node] := True;
     end;
  end;
end;

//Den Node für das Objekt und die zugehörigen Daten erstelle ich erst mit dem aufklappen (OnExpanding) des jeweiligen Node

procedure TfReportClient.vstKundenExpanding(Sender: TBaseVirtualTree;
  Node: PVirtualNode; var Allowed: Boolean);
var
  Daten : pVSTNodeData;
begin
  case vstKunden.GetNodeLevel(Node) of
    1:
     begin
       if Node.ChildCount = 0 then
         begin
           //Objektdaten laden und zugehörige Knoten erstellen
           Daten := vstKunden.GetNodeData(Node);
           LoadObjektData(Node, Daten^.KundenNr);
         end;
        //...
     end;
  end;
end;

//Objektnode erstellen und Daten laden
procedure TfReportClient.LoadObjektData(ANode : PVirtualNode; AInt : integer);
var
  i : Integer;
  pNode : PVirtualNode;
  Daten : pVSTNodeData;
begin
  try
    try
      //Objektedaten laden aus Objektdatenbank
      DMMasterData.qryObjectData.Parameters.ParamByName('Kundennummer').Value := AInt;
      if DMMasterData.OpenObjectQuery then
        begin
          //Ersten Datensatz wählen
          DMMasterData.qryObjectData.First;
          //Schleife um alle Objekte zu durchlaufen und die Knoten für die Objekte zu erstellen.
          for i := 0 to DMMasterData.qryObjectData.RecordCount -1 do
            begin
              //Objekt Knoten erstellen
              pNode := vstKunden.AddChild(ANode);
              //Daten zuweisen
              Daten := vstKunden.GetNodeData(pNode);
              Daten^.ObjektName := DMMasterData.qryObjectData.FieldByName('Objekt_Objektname').AsString;
              Daten^.ObjektNr := DMMasterData.qryObjectData.FieldByName('Objekt_Objektnummer').AsInteger;
              //Nächsten Datensatz anwählen
              DMMasterData.qryObjectData.Next;
            end;
        end;
    except

    end;
  finally
    DMMasterData.CloseObjectQuery;
  end;
end;
So, und jetzt stell ich mir die Frage. Das ganze funktioniert auch, allerdings, wenn ich ja die Daten des Objekts zuweisen, dann mache ich das ja wie folgt..

Delphi-Quellcode:
   Daten := vstKunden.GetNodeData(pNode);
   Daten^.ObjektName := DMMasterData.qryObjectData.FieldByName('Objekt_Objektname').AsString;
Würde ich jetzt diesen Record zu diesem Node abfragen...

Delphi-Quellcode:
  Daten := vstKunden.GetNodeData(pNode);
  ShowMessage(Daten^.Kundenname);
  ShowMeddage(Daten^.Objektname);
wird das Ergebniss immer

Kundenname = '';
Objektname = 'Ich bin der Objektname von pNode'

sein. Ist ja auch klar, weil der zeiger zum Kundennamen bzw. zum Datenrecord nicht vorhanden ist. Wie löst man sowas. Ist das so vorgesehen. Müsste ich hier wirklich mit...

Delphi-Quellcode:
  Daten := vstKunden.GetNodeData(pNode.Parent);
  ShowMessage(Daten^.Kundenname);
arbeiten?

Jens Hartmann 10. Nov 2015 10:37

AW: Fehler beim Programm beenden
 
*push*

TiGü 10. Nov 2015 10:47

AW: Fehler beim Programm beenden
 
Deine Frage ist unklar.
Baue ein kurzes (!!!) Beispielprojekt, zippe es und hänge es an.
Im Quelltext per Kommentar deutlich machen wie der Soll- und Ist-Zustand sich darstellt.

Jens Hartmann 10. Nov 2015 19:35

AW: Fehler beim Programm beenden
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hallo TiGü,
Zitat:

Zitat von TiGü
Deine Frage ist unklar.

]

Sie Anhang.

Ich hoffe, das erklärt meine Frage etwas...

Ich versuche es aber auch nochmal wie folgt darzustellen...

Meine VST Struktur sieht ja in etwa so aus...

Code:
-Kunde1
  -Objekt1
  -Objekt2
  -Objekt3
-Kunde2
  -Objekt1
  -Objekt2
...
Wenn ich jetzt im VST auf Kunde1 "klicke", möchte ich natürlich die Daten des zugehörigen Kunden abfragen... Da ich ja nach aktueller Erklärung versuche das Ziel zu verfolgen, das ganze mit einem Record zu erschlagen, habe ich diesen aktuell als ersten Entwurf wie folgt aufgebaut...

Delphi-Quellcode:
  type
    pVSTNodeData = ^rVSTNodeData;
    rVSTNodeData = record
      KundenName, ObjektName, AnlagenTyp, AnlagenBezeichnung : WideString;
      KundenNr, ObjektNr, AnlagenNr : Integer;
    end;
Das heißt, in diesem Record sind alle notwendigen Daten. Dies ist aber falsch, da ja der Zusammenhang zwischen den verschiedenen Kunden und Objekten (Anlagen) nicht hergestellt werden kann. Dies wäre ja nur dann möglich, wenn z.B.

Delphi-Quellcode:
  Objektname : array of WideString
wäre.

In der Datenzuweisung mache ich ja immer

Delphi-Quellcode:
  //for-Schleife Kunden
  pNode1 := vstKunden.AddChild(pNode);
  Daten := vstKunden.GetNodeDate(pNode1);
  Daten^.KundenName := 'Kunde';

  //for-Schleife Objekte innerhalb der for-Schleife Kunden
  pNode2 := vstKunden.AddChild(pNode1);
  Daten := vstKunden.GetNodeDate(pNode2); //Hier ist ja der Zeiger auf einen ganz anderen record (logisch, anderer Node)
  Daten^.KundenName := 'Kunde';
Dies würde aber ja bedeuten, das in der Datenebene "Kunde" die Felder "Objekt" etc. überflüssig sind (da nie genutz) und in der Datenebene "Objekt" das Felde "Kunde" überflüssig ist. Dies würde mich wieder in die Richtung meiner ehemaligen Lösung bewegen "3 Objekt/Records" zu erstellen. Je einen für die entsprechende "Node-Ebene"

Wenn ich Euch aber richtig verstanden habe, kann man das irgendwie mit dem NodeType oder ähnlich lösen. Und da Blick ich nicht richtig durch...

Ich hoffe, dass man das jetzt verstehen konnte...

Danke schon mal und Gruß Jens

bcvs 11. Nov 2015 08:01

AW: Fehler beim Programm beenden
 
Vergiss diesen Record!!
Das vermischt Datenhaltung mit visueller Darstellung, da deine Daten im Control gespeichert werden. Nicht gut.

Ich würde mir eine Datenstruktur überlegen, die zuerst einmal komplett außerhab dem VST stattfindet.

z.B.
Delphi-Quellcode:
TKunde = class
  Name : String;
  Nr : String;
  Objects: TObjectList;
  constructor Create; // hier Objects erzeugen
  destructor Destroy; Override; // hier Objects freigeben
end;

TKundenObjekt = class
  Name : String;
  WasAuchImmer: String;
end;
Dann braucht du noch eine ObjectList für die Kunden:

Delphi-Quellcode:
KundenListe : TObjectList;
Vor dem Einlesen der Daten in den VST wird jetzt erstmal diese Datenstruktur gefüllt.

Dein Record für den VST sieht dann so aus:

Delphi-Quellcode:
  type
    pVSTNodeData = ^rVSTNodeData;
    rVSTNodeData = record
      Data : TObject;
    end;
Dann gehst du die zuvor befüllte KundenListe durch und baust den Inhalt des VST auf:


Delphi-Quellcode:
  //for-Schleife Kunden (i)
  pNode1 := vstKunden.AddChild(pNode);
  Daten := vstKunden.GetNodeDate(pNode1);
  Daten^.Data := KundenListe[i];

  //for-Schleife Objekte (j) innerhalb der for-Schleife Kunden (i)
  pNode2 := vstKunden.AddChild(pNode1);
  Daten := vstKunden.GetNodeDate(pNode2);
  Daten^.Data:=TKunde(KundenListe[i]).Objects[j]);
Jetzt kennt jeder Node des VST seine relevanten Daten und nur diese. Im OnGetText kannst du dann herausfinden, ob es sich um einen Kudnen oder ein Objekt handelt:

Delphi-Quellcode:
  Daten := vstKunden.GetNodeDate(Node);
  if Daten^.Data is TKunde then
    // Kunde
  else
    // Objekt

Jens Hartmann 15. Nov 2015 18:34

AW: Fehler beim Programm beenden
 
Hallo zusammen,

ich habe das Problem gelöst. Und ich glaube sogar, das ganze zu verstehen. Abschließen nochmal vielen Dank für Eure Hilfe und folgend mal die Lösung des Problems...

Delphi-Quellcode:
//Der Record mit dem integrierten Objekt
  type
    PTreeData = ^TTreeData;
    TTreeData = record
      FObject : TObject;
    end;

//Im OnCreate der Form
  ...
  vstKunden.NodeDataSize := SizeOf(TTreeData);
  LoadVST(true);
  ...

//Das Laden des ersten Knoten
procedure TfReportClient.LoadVST(IsStarted: Boolean);
var
  pNode : PVirtualNode;
begin
  try
    vstKunden.BeginUpdate;
    vstKunden.Clear;
    ....
      pNode := vstKunden.AddChild(nil);
      //Kundendaten laden
      LoadCustomersData(pNode,IsStarted);

      vstKunden.EndUpdate;
      ExpandedRootNodes(vstKunden);
      vstKunden.SortTree(0, sdAscending, True);
...
end;

//Das Laden der Kunden
procedure TfReportClient.LoadCustomersData(ANode : PVirtualNode; IsStarted : Boolean);
var
  i : Integer;
  Daten_Kunde : TOCustomers;
begin
 try
   try
    //Kundendaten laden aus Kundendatenbank

    if DMMasterData.OpenCustomerQuery then
      begin
        DMMasterData.qryCustomerData.First;

        for i := 0 to DMMasterData.qryCustomerData.RecordCount -1 do
          begin
              Daten_Kunde := TOCustomers.Create;
              with Daten_Kunde do
                begin
                  Kunden_Kundennummer := DMMasterData.qryCustomerData.FieldByName('Kunden_Kundennummer').AsInteger;
                  Kunden_ESKundennummer := DMMasterData.qryCustomerData.FieldByName('Kunden_ESKundennummer').AsInteger;
                  Kunden_Kundenname := DMMasterData.qryCustomerData.FieldByName('Kunden_Kundenname').AsString;
                  Kunden_Ort := DMMasterData.qryCustomerData.FieldByName('Kunden_Ort').AsString;
                  Kunden_Straße := DMMasterData.qryCustomerData.FieldByName('Kunden_Straße').AsString;
                  Kunden_Bemerkung := DMMasterData.qryCustomerData.FieldByName('Kunden_Bemerkung').AsString;
                  Ansprechpartner_Id := DMMasterData.qryCustomerData.FieldByName('Ansprechpartner_Id').AsInteger;
                  Ansprechpartner_Name := DMMasterData.qryCustomerData.FieldByName('Ansprechpartner_Name').AsString;
                end;

                vstKunden.AddChild(ANode,Daten_Kunde);

            DMMasterData.qryCustomerData.Next;
          end;
      end;
    DMMasterData.CloseCustomerQuery;
   except
     Daten_Kunde.Free;
   end;
 finally
   DMMasterData.CloseCustomerQuery;
 end;
end;
//Festlegung, das die Knoten Kunden Kinder haben im OnInit des VST
procedure TfReportClient.vstKundenInitNode(Sender: TBaseVirtualTree; ParentNode,
  Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
begin
  case vstKunden.GetNodeLevel(Node) of
    1:
     begin
       vstKunden.CheckType[Node] := ctCheckBox;
       vstKunden.CheckState[Node] := csCheckedNormal;
       vstKunden.HasChildren[Node] := True;
     end;
    2:
     begin
       vstKunden.HasChildren[Node] := True;
     end;
    3:
     begin
       vstKunden.HasChildren[Node] := True;
     end;
  end;
end;

//Die Objekte und Anlagen werden erst nach dem sie aufgeklappt werden erstellt
procedure TfReportClient.vstKundenExpanding(Sender: TBaseVirtualTree;
  Node: PVirtualNode; var Allowed: Boolean);
var
  NodeLevel : Integer;
  Daten : PTreeData;
begin
  NodeLevel := vstKunden.GetNodeLevel(Node);
  if vstKunden.ChildCount[Node] = 0 then
    begin
      case NodeLevel of
        1: //Objektdaten beim aufklappen des Objekt Knoten erstellen/laden
         begin
           Daten := vstKunden.GetNodeData(Node);
           LoadObjektData(Node, TOCustomers(Daten.FObject).Kunden_Kundennummer);
         end;
        2: //Anlagendaten beim aufklappen des Anlagen Knoten erstellen/laden
         begin
           Daten := vstKunden.GetNodeData(Node);
           LoadSystemData(Node, TOBuilding(Daten.FObject).Objekt_Objektnummer);
         end;
      end;
    end;
end;

//Das Freigeben der Daten
procedure TfReportClient.vstKundenFreeNode(Sender: TBaseVirtualTree;
  Node: PVirtualNode);
var
  Daten : PTreeData;
begin
  Daten := vstKunden.GetNodeData(Node);
  if not Assigned(Daten) then
    exit;
  Daten.FObject.Free;
end;


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