![]() |
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:
Kann mir jemand eine Hilfestellung geben, wie ich das Problem auffinden kann?{$IFDEF DEBUG} ReportMemoryLeaksOnShutdown := True; {$ENDIF} |
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 ;) |
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.
|
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. |
AW: Fehler beim Programm beenden
Erstellt du ihn auch "von Hand" im Code?
|
AW: Fehler beim Programm beenden
Nein, liegt als Komponenten auf der Form...
|
AW: Fehler beim Programm beenden
Und warum gibst du ihn dann selbst frei?
|
AW: Fehler beim Programm beenden
Zitat:
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 ![]() 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. |
AW: Fehler beim Programm beenden
Die Memoryleaks kommen vom vorzeitigen Abbruch der Anwendung, jede Wette. Na ja. Fast jede. :mrgreen:
|
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 |
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.
|
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 ;) |
AW: Fehler beim Programm beenden
Hallöle...:P
Zitat:
|
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: ![]() ![]() |
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.
|
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 |
AW: Fehler beim Programm beenden
Zu VST und Memory Leaks fallen mir als allererstes immer drei Sachen ein:
|
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:
Jeder Kunde kann mehrere Objekte haben. Jedes Objekt kann mehrere Systeme haben und Jedes System hat mehrere Menüpunkte.
+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 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:
ähnlich sieht das Objekt für "Objekte" und "System" aus.
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. 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:
Dann weiße ich über "OnGetText" die Bezeichnung zu und über "OnGetImageIndex" noch ein paar Icons
//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; Das "OnFreeNode" sieht aktuell wie folgt aus...
Delphi-Quellcode:
Die Globalen Var. gebe ich im FormDestroy wieder frei...
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;
Delphi-Quellcode:
Ich vermute, das einige von Euch jetzt schon die Hände über dem Kopf zusammengeschlagen haben :oops:
procedure TfReportClient.FormDestroy(Sender: TObject);
begin TMyKunden_Daten.Free; TMyObjekt_Daten.Free; TMySystem_Daten.Free; end; Aber vieleicht könnt Ihr mir ja mal ein paar Tips geben. Danke schon mal und Gruß Jens |
AW: Fehler beim Programm beenden
Zitat:
Beispiel: Wenn du 10 Kunden erzeugst
Delphi-Quellcode:
, gibst du im FormDestroy nur Nummer 9 frei.
TMyKunden_Daten := TOCustomers.Create;
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; |
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. |
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:
Deine Methode über das NodeLevel zu differenzieren geht natürlich auch.
type
TNodeType = (ntCustomer, ntBulding, ..); PNodeData = ^TNodeData; TNodeData = record NodeType: TNodeType; NodeObject: TObject; end; Die
Delphi-Quellcode:
ist aber in jedem Falle immer
NodeDataSize
Delphi-Quellcode:
für alle Nodes.
SizeOf(TNodeData)
Bezüglich der Objektverwaltung sehe ich zwei Möglichkeiten:
|
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 |
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? |
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:
Den Bezug zum VST stelle ich über das NodeLevel her.
{ 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
Delphi-Quellcode:
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.
NodeLevel 0 = Datenbankpfad
NodeLevel 1 = Kundendaten NodeLevel 2 = Objektdaten NodeLevel 3 = Systemdaten NodeLevel 4 = Menüpunkte zum System Da ich drei Objekte habe, stellt sich mir die Frage erneut nach dem
Delphi-Quellcode:
. Wie geh man sowas an.
NodeDataSize
Vorstellen könnte ich mir folgende alternativen. 1. Das Object "TOCustomers" bekommt ein Feld
Delphi-Quellcode:
und das Object TOBulidings bekommt ein Feld
Objektdaten array of TOBuildings
Delphi-Quellcode:
2.
Systemdaten array of TOSystems
Die Objecte liegen ja je in einer eigenen Unit. Diese könnte man ja eventuell in einen Record einbinden:
Delphi-Quellcode:
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.
type
TRKundenDaten = record FKundedaten : TOCustomers; FObjektdaten : TOBuildings; FSystemdaten : TOSystems; end; PRKundenDaten = ^TRKundenDaten;
Delphi-Quellcode:
Weiterhin, wäre dann das OnFreeNode klar...
vst.NodeDataSize:=sizeof(TRKundenDaten);
Delphi-Quellcode:
Ich hoffe, das ich nicht ganz auf dem Holzweg bin...
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; |
AW: Fehler beim Programm beenden
Zitat:
![]() 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. |
AW: Fehler beim Programm beenden
Zitat:
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:
Aber der erste Crash kommt direkt beim einlesen...
type
TRKundenDaten = record FKundedaten : TOCustomers; FObjektdaten : TOBuilding; FSystemdaten : TOSystems; end; PRKundenDaten = ^TRKundenDaten;
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; |
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. |
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 |
AW: Fehler beim Programm beenden
Zwei Sachen sind mir aufgefallen:
1.
Delphi-Quellcode:
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.
vstKunden.NodeDataSize := SizeOf(TRKundenDaten);
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; |
AW: Fehler beim Programm beenden
Zitat:
Delphi-Quellcode:
in folgende Zeilen:for I := 0 to 10 do begin BuildingNode := vstKunden.AddChild(nil); Daten := vstKunden.GetNodeData(CustomerNode); ...
Delphi-Quellcode:
Außerdem bitte noch das OnFreeNode ergänzen:for I := 0 to 10 do begin BuildingNode := vstKunden.AddChild(nil); Daten := vstKunden.GetNodeData(BuildingNode); ...
Delphi-Quellcode:
Und jetzt darfst du gerne mit den Kopf auf den Tisch schlagen. :wall: :mrgreen:
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; PS: Trotzdem wäre der Weg mit externer Datenhaltung über eine Objektliste langfristig besser! Bitte beschäftige dich damit. |
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 |
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:
Ich versuch dir das mal an einem meiner etwas älteren Projekte zu erklären:
BList : Generics.Collections.TObjectList<TStartBild>;
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:
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:
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.
Delphi-Quellcode:
Je nachdem, welche Darstellungsform der Anwender nun wählt (Baum oder Icons), wird der Baum gezeichnet oder die Icons:
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;
Delphi-Quellcode:
Die komplette UnitFrameMain incl. DFM-Datei hab ich angehängt.
// ----- 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; |
AW: Fehler beim Programm beenden
Zitat:
Du definiert die eine ObjectList. Da ich Delphi 2007 habe, nehme ich zwangsläufig die einfache TOjectList aus der Unit Contnrs;
Delphi-Quellcode:
Beim Einlesen des Trees passiert folgendes:
type
TForm1 = class(TForm) vstKunden: TVirtualStringTree; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); private { Private-Deklarationen } KundenList:TObjectList; // <----- public { Public-Deklarationen } end;
Delphi-Quellcode:
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.
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;
Delphi-Quellcode:
procedure TForm1.FormDestroy(Sender: TObject);
begin KundenList.free; end; |
AW: Fehler beim Programm beenden
Zitat:
Zitat:
![]() Auch das wird schwerlich genau auf deinen Fall eingehen, sondern nur allgemeine Vorgehensweisen beschreiben. Die Transferleistung musst du selber erbringen. Zitat:
Was verstehst du genau nicht? Inzwischen wurde dir diese Variante vier bis fünf Mal angeraten und erklärt. |
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, 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..
//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;
Delphi-Quellcode:
Würde ich jetzt diesen Record zu diesem Node abfragen...
Daten := vstKunden.GetNodeData(pNode);
Daten^.ObjektName := DMMasterData.qryObjectData.FieldByName('Objekt_Objektname').AsString;
Delphi-Quellcode:
wird das Ergebniss immer
Daten := vstKunden.GetNodeData(pNode);
ShowMessage(Daten^.Kundenname); ShowMeddage(Daten^.Objektname); 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:
arbeiten?
Daten := vstKunden.GetNodeData(pNode.Parent);
ShowMessage(Daten^.Kundenname); |
AW: Fehler beim Programm beenden
*push*
|
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. |
AW: Fehler beim Programm beenden
Liste der Anhänge anzeigen (Anzahl: 1)
Hallo TiGü,
Zitat:
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:
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...
-Kunde1
-Objekt1 -Objekt2 -Objekt3 -Kunde2 -Objekt1 -Objekt2 ...
Delphi-Quellcode:
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.
type
pVSTNodeData = ^rVSTNodeData; rVSTNodeData = record KundenName, ObjektName, AnlagenTyp, AnlagenBezeichnung : WideString; KundenNr, ObjektNr, AnlagenNr : Integer; end;
Delphi-Quellcode:
wäre.
Objektname : array of WideString
In der Datenzuweisung mache ich ja immer
Delphi-Quellcode:
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"
//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'; 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 |
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:
Dann braucht du noch eine ObjectList für die Kunden:
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;
Delphi-Quellcode:
Vor dem Einlesen der Daten in den VST wird jetzt erstmal diese Datenstruktur gefüllt.
KundenListe : TObjectList;
Dein Record für den VST sieht dann so aus:
Delphi-Quellcode:
Dann gehst du die zuvor befüllte KundenListe durch und baust den Inhalt des VST auf:
type
pVSTNodeData = ^rVSTNodeData; rVSTNodeData = record Data : TObject; end;
Delphi-Quellcode:
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:
//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]);
Delphi-Quellcode:
Daten := vstKunden.GetNodeDate(Node);
if Daten^.Data is TKunde then // Kunde else // Objekt |
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 01:46 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