Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   GUI-Design mit VCL / FireMonkey / Common Controls (https://www.delphipraxis.net/18-gui-design-mit-vcl-firemonkey-common-controls/)
-   -   Delphi VirtualTreeView: Es klappt nicht ganz (https://www.delphipraxis.net/70508-virtualtreeview-es-klappt-nicht-ganz.html)

JPSelter 31. Mai 2006 11:06


VirtualTreeView: Es klappt nicht ganz
 
So, jetzt stosse ich an erste Probleme. Ich habe ein Objekt erzeugt und möchte es an den Baum anhängen. Da läuft er auch durch, aber am Ende (scheinbar bevor er den VTV anzeigen will) kommt eine EAccessViolation. Ich kann den Fehler leider nicht ganz zurückverfolgen, ich weiss nur, dass er aus der VTV Klasse kommt. Delphi bleibt in der Funktion TCustomVirtualStringTree.DoGetText stehen, aber auch nach 2-maligem Durchlauf von TBaseVirtualTree.WndProc. Hier erstmal mein bisheriger Code:

Objekt-Definition:
Delphi-Quellcode:
// Ebene 0: Sitzungsdaten
  TSessionObject = class
    private
      fdatum: integer;
      fsessiontyp: string;
      fprotokollant: string;
      fteilnehmer: string;
      fverteiler: string;
      fndatum: integer;
    published
      property datum: integer read fdatum write fdatum;
      property sessiontyp: string read fsessiontyp write fsessiontyp;
      property protokollant: string read fprotokollant write fprotokollant;
      property teilnehmer: string read fteilnehmer write fteilnehmer;
      property verteiler: string read fverteiler write fverteiler;
      property ndatum: integer read fndatum write fndatum;
  end;
Neues Objekt wird mit Daten versehen und an den Baum gehangen:
Delphi-Quellcode:
// insert new session
procedure insertSession();
var SessionObject: TSessionObject;
    datum, ndatum: integer;
begin
  Form1.VST1.NodeDataSize:=SizeOf(TTreeData);
  SessionObject:=TSessionObject.Create;
  try
    datum:=DateTimeToUnix(Form1.DateTimePicker1.Date);
    ndatum:=DateTimeToUnix(Form1.DateTimePicker2.Date);
    SessionObject.datum:=datum;
    SessionObject.sessiontyp:=Form1.ComboBox1.Text;
    SessionObject.protokollant:=Form1.Edit1.text;
    SessionObject.teilnehmer:=Form1.Edit2.Text;
    SessionObject.verteiler:=Form1.Edit3.Text;
    SessionObject.ndatum:=ndatum;
    AddVSTObject(Form1.VST1,nil,SessionObject);
  except
    SessionObject.Free;
  end;
end;
Die eigentliche Anhänge-Funktion:
Delphi-Quellcode:
// add new structure into VST
function AddVSTObject(AVST: TCustomVirtualStringTree; ANode: PVirtualNode;
  AObject: TObject): PVirtualNode;
var
  Data: PTreeData;
begin
  Result:=AVST.AddChild(ANode);
  AVST.ValidateNode(Result,False);
  Data:=AVST.GetNodeData(Result);
  Data^.FObject:=AObject;
end;
Er läuft da überall ohne Probleme durch, keine Fehlermeldungen, nur am Ende dieses EAccessViolation... vielleicht kennt sich da jemand genauer aus. Danke!

generic 31. Mai 2006 11:24

Re: VirtualTreeView: Es klappt nicht ganz
 
probier mal

Delphi-Quellcode:
vst.addchild(nil, meinobj);
später drauf zugreifen mit:

Delphi-Quellcode:
myobj:=TSessionObject(vst.getnodedate(node)^);

jbg 31. Mai 2006 11:30

Re: VirtualTreeView: Es klappt nicht ganz
 
Die NodeDataSize Eigenschaft hast du aber schon auf die richtige Größe (SizeOf(TTreeData)) gesetzt?

chaosben 31. Mai 2006 11:31

Re: VirtualTreeView: Es klappt nicht ganz
 
Zitat:

Zitat von JPSelter
Delphi-Quellcode:
// add new structure into VST
function AddVSTObject(AVST: TCustomVirtualStringTree; ANode: PVirtualNode;
  AObject: TObject): PVirtualNode;

Ich denke es liegt an der Übergabe des Objektes. Probier doch mal das Ganze mit einem "const" vor "AObject". Dann sollte er das Objekt selbst bzw. eine Referenz darauf übergeben. (ich hoffe, ich verwechsel nicht schon wieder alles)

JPSelter 31. Mai 2006 11:34

Re: VirtualTreeView: Es klappt nicht ganz
 
Argh, hab den Fehler gefunden... hatte im Projekt noch Überbleibsel aus dem Tutorial drin, also so Sachen wie vstFreeNode und VSTGetText, die alle ohne Objekte arbeiteten... ach menno :D Exception ist nun weg. Dann bis zur nächsten Frage ;)

JPSelter 31. Mai 2006 12:26

Re: VirtualTreeView: Es klappt nicht ganz
 
Weiter gehts ;)

Ich habe noch nicht viel mit Pointern und Objekten programmiert. Ich will nun die Spalte korrekt beschriften und das Tutorial hat dazu diesen Code:

Delphi-Quellcode:
procedure TForm1.VSTGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
  Column: Integer; TextType: TVSTTextType; var Text: WideString);
var
  Data: PTreeData;
begin
  Data:=VST.GetNodeData(Node);

  case Column of
    0: Text := Data.FCaption;
    1: Text := Data.FColumn1;
  end;
end;
Leider ist das noch die Geschichte ohne Objekte und ich frage mich nun, wie ich die Eigenschaften meines Objektes ansprechen soll. Irgendwas mit

Data^.FObject...

oder so?

himitsu 31. Mai 2006 12:42

Re: VirtualTreeView: Es klappt nicht ganz
 
jupp, du mußt halt nur den DatenTyp ändern Data: PTreeData; ist ja ein Pointer auf 'nen Record und du brauchst Data: TSessionObject; ... eine ObjektVariable ist ja schon ein Pointer auf ein Objekt, also kannst du auch gleich dieses nutzen und mußt es nicht nochmal über 'nen Pointer ansprechen ^^

Und dann halt nur noch Data.FObject...

JPSelter 31. Mai 2006 12:46

Re: VirtualTreeView: Es klappt nicht ganz
 
Jepp, das klappt :) Danke!

JPSelter 31. Mai 2006 13:16

Re: VirtualTreeView: Es klappt nicht ganz
 
So, diesmal finde ich aber gar nichts zu diesem Thema: Ich will meinen Baum nun speichern. Das Tutorial behandelt aber nur das Speichern von Strings und Integer, nicht das Speichern von ganzen Objekten. Wie speichere ich da?
Ein Node kann bei mir 3 verschiedene Objekte beinhalten, die ich aber leicht über die Spalten-Nummer identifizieren kann. Ein Node hat also ein FObject, ich kann doch nicht das ganze Objekt speichern oder? Ich muss doch sicher noch IN das Objekt rein und alle Paramter einzeln speichern, oder gehts doch einfacher?

himitsu 31. Mai 2006 13:32

Re: VirtualTreeView: Es klappt nicht ganz
 
Im Grunde muß immer nur der DatenTyp angepasst werden. (abgesehn von den CompilerMagicSachen)
Delphi-Quellcode:
var Data: PString;
var Data: PInteger;
...

Wobei der String nicht dem Pointer übergeben wird (Strings und dynamische Arrays sind halt 'ne CompilerMagicSache), sondern der Pointer zum String gemacht wird und diesem dann erst der String gegeben wird (also andersrum).
Ein AnsiRING) hat ja eine Interne referenzzähling und wenn nur der Pöinter übergeben wird, dann wird der Referenzzähler nicht erhöht und somit kann der String eventuell vorzeigt freigegeben werden.

Wenn dem String z.B. erst in der Prozedur der Inhalt erzeugt/zugewiesen wurde und dann der String auch noch eine Lokale Variable ist, dann wird er ja am Prozedurende freigegeben.

Zitat:

String zuweisen >> Referenzzähler = 1
Pointer übergeben >> Referenzzähler immernoch 1
Prozedurende >> Referenzzähler = Referenzzähler - 1
wenn Referenzzähler = 0 >> string wird freigegeben
Code:
function AddVSTObject(AVST: TCustomVirtualStringTree; ANode: PVirtualNode;
  [b]AString: String[/b]): PVirtualNode;
begin
  Result:=AVST.AddChild(ANode);
  AVST.ValidateNode(Result,False);
  Data:=AVST.GetNodeData(Result);
  [b]PString(Data^.FObject)^ := AString;[/b]
end;
Und der Integer ... für den brauchste nichtma Speicher reservieren, da dieser ja genausogroß wie der Pointer ist:
Code:
function AddVSTObject(AVST: TCustomVirtualStringTree; ANode: PVirtualNode;
  [b]AInteger: Integer[/b]): PVirtualNode;
begin
  Result:=AVST.AddChild(ANode);
  AVST.ValidateNode(Result,False);
  Data:=AVST.GetNodeData(Result);
  [b]Data^.FObject := Pointer(AInteger);[/b]
end;

JPSelter 31. Mai 2006 13:38

Re: VirtualTreeView: Es klappt nicht ganz
 
Das hab ich jetzt nicht so erwartet ;) Nochmal gaaaanz langsam:

Problem 1: Speichern. Wie speichere ich ein Objekt? Übernimmt das Programm die interne Verwaltung der internen Objekt-Daten oder muss ich manuell an die Daten ran? Im Moment speichere ich ganz einfach das Objekt in den Stream. Er macht das auch ohne zu murren und in der Datei auf der Festplatte sehe ich einen der internen Strings (aber nur diesen einen) und jede Menge anderes ASCII-Zeugs, das für mich als Betrachter sicher nicht wichtig ist.

Problem 2 stelle ich erst, wenn das Speichern funktioniert ;)

himitsu 31. Mai 2006 13:44

Re: VirtualTreeView: Es klappt nicht ganz
 
Du speicherst nur einen Pointer auf das Objekt in den Stream ... das Objekt selber ist nicht in der VTV ... genaus wird es mit dem String gemacht (halt nur der Pointer auf den String in der VTV), nur das man da halt auf den Referenzzähler achten muß.

Du kannst natürlich den String auch in einem ShortString (String[255], odeer so) speichern, wobei der hortString wieder nur ein Record wäre und somit selbst in der VTV landet.


PS: hatte oben noch einiges nachgetragen.

JPSelter 31. Mai 2006 13:54

Re: VirtualTreeView: Es klappt nicht ganz
 
Puh, oje, das ist mir im Moment etwas zu hoch... kannst Du mir vielleicht etwas konkreten Code zu meinem konkreten Objekt (hier ganz oben) geben? :|

generic 31. Mai 2006 14:04

Re: VirtualTreeView: Es klappt nicht ganz
 
warum macht ihr alle das so umständlich.

schaut euch bitte von mir nochmal folgenden beispielcode an:
http://www.delphipraxis.net/internal...=484809#484809

himitsu 31. Mai 2006 14:06

Re: VirtualTreeView: Es klappt nicht ganz
 
zu deinem Projekt steht's ja sozusagen schon dort oben (Beitrag #7)

Du muß halt bedenken, das dein Objekt ja nicht nur aus einem zusammenhängendem Speicherblock besteht.
Var irgendwas: TSessionObject; ist halt nur ein Pointer auf das Objekt und in dem Objekt sind ja auch wiederum Pointer auf andere Dinge (z.B. die Strings).

Also, will man jetzt ein Objekt "nur" mit der VTV verbinden, dann stimmt das schon, aber die gesamten Daten eines Objekts in einen Stream zu bekommen ist nicht so leicht ... man kann da eigentlich nur das Objekt zerlegen und eine Kopie der einzelnen ObjektDaten in dem Stream speichern und müßte dann daraus (beim Auslesen) dann wieder ein neues Objekt erstellen.

JPSelter 31. Mai 2006 14:09

Re: VirtualTreeView: Es klappt nicht ganz
 
@generic: Von OnLoad und OnSave steht da aber nichts drin ;)

Ich probiere nun, die Daten des Objekts einzeln in den Stream zu schreiben (erstmal nur der erste Parameter) mit

Delphi-Quellcode:
// save nodes to disk
procedure TForm1.VST1SaveNode(Sender: TBaseVirtualTree; Node: PVirtualNode;
  Stream: TStream);
var
  Data: PTreeData;
  Len: integer;
begin
  Data := VST1.GetNodeData(Node);
  Len := Length(TSessionObject(Data.FObject).name);
  Stream.Write(Len, SizeOf(Len));
  Stream.write(PChar(TSessionObject(Data.FObject).name)^, Len);
end;
Funktioniert soweit. Nur wie bekomme ich die Daten da wieder raus?

Delphi-Quellcode:
// load nodes from disk
procedure TForm1.VST1LoadNode(Sender: TBaseVirtualTree; Node: PVirtualNode;
  Stream: TStream);
var
  Data: PTreeData;
  Len: integer;
begin
  Data := VST1.GetNodeData(Node);
  Stream.read(Len, SizeOf(Len));
  SetLength(TSessionObject(Data.FObject).name, Len); //<---- Error: Ein Konstantenobjekt kann nicht als Var-Parameter übergeben werden
  Stream.read(PChar(TSessionObject(Data.FObject).name)^, Len);
end;
Das klappt irgendwie nicht, wegen der markierten Zeile. Wieso?

JPSelter 31. Mai 2006 14:13

Re: VirtualTreeView: Es klappt nicht ganz
 
Ah, jetzt verstehe ich Dich ;) Dann muss ich das Objekt beim Laden ja neu erstellen durch ein Create. Dann mach ich das mal...

JPSelter 31. Mai 2006 14:35

Re: VirtualTreeView: Es klappt nicht ganz
 
OK, jetzt habe ich folgenden Code und eine EAccessViolation:

Delphi-Quellcode:
// save nodes to disk
procedure TForm1.VST1SaveNode(Sender: TBaseVirtualTree; Node: PVirtualNode;
  Stream: TStream);
var
  Data: PTreeData;
  Len: integer;
begin
  Data := VST1.GetNodeData(Node);
  Stream.Write(TSessionObject(Data.FObject).datum, SizeOf(TSessionObject(Data.FObject).datum));
  Len := Length(TSessionObject(Data.FObject).name);
  Stream.Write(Len, SizeOf(Len));
  Stream.write(PChar(TSessionObject(Data.FObject).name)^, Len);
  //[... die restlichen Parameter]
end;
Delphi-Quellcode:
// load nodes from disk
procedure TForm1.VST1LoadNode(Sender: TBaseVirtualTree; Node: PVirtualNode;
  Stream: TStream);
var
  Data: PTreeData;
  Len,datum,ndatum: integer;
  SessionObject: TSessionObject;
  name,sessiontyp,protokollant,teilnehmer,verteiler: string;
begin
  Data := VST1.GetNodeData(Node);
  SessionObject:=TSessionObject.Create;

  Stream.Read(datum, SizeOf(datum));
  Stream.read(Len, SizeOf(Len));
  SetLength(name, Len);
  Stream.read(name, Len);
  // [... + der rest]

  SessionObject.datum:=datum;
  SessionObject.name:=name; // <--------------- hier kommt die Exception
  // [... + der rest]

  Data^.FObject:=SessionObject;
end;
Aber wieso die Exception? Irgendwo ein Pointer oder ein Objekt nicht richtig?

EDIT: Habe die Zeile markiert, wo die Exc geworfen wird. Muss aber jetzt leider für heute weg. Komme beim nächsten Arbeitstag wieder auf diesen Thread zurück. Danke soweit! :)

Hawkeye219 31. Mai 2006 15:13

Re: VirtualTreeView: Es klappt nicht ganz
 
Der Code zum Lesen der String-Var ist nicht ganz richtig. So ist es besser:

Delphi-Quellcode:
Stream.read(Len, SizeOf(Len));
SetLength(name, Len);
// Stream.read(name, Len);  // <-- name ist nur ein Zeiger!
if (Len > 0) then
  Stream.read(name[1], Len);
Gruß Hawkeye

himitsu 31. Mai 2006 15:16

Re: VirtualTreeView: Es klappt nicht ganz
 
Wo kommt den der Fehler und warum willst du unbedingt das gesamte Objekt din haben?
Es ist zumindestens Platzsparender, wenn nur der Pointer in der VTV steht und man bräuchte dann das Objekt nicht zu zerlegen...

Aber versuch es mal so:
Delphi-Quellcode:
Stream.Write(TSessionObject(Data.FObject).Name[1], Len);
Stream.Read(TSessionObject(Data.FObject).Name[1], Len);
oder
Delphi-Quellcode:
Stream.Write(@TSessionObject(Data.FObject).Name[1], Len);
Stream.Read(@TSessionObject(Data.FObject).Name[1], Len);
Ich weiß nicht, ob Write/Read 'nen VAR-Parameter (ersteres), oder 'nen Pointer (zweiteres) will.

[add]
OK, Markierung gesehn ^^

Dann lag es bestimmt an der Stringübergabe (siehe Code)
[/add]

PS: @generic
Na ja, eigentlichj ist es von der interen Speicherung in der VTV her egal wo man die Daten übergibt, aber wie man sie übergibt/ließt ist da schon wichtig.
Delphi-Quellcode:
vst.addchild(nil, [b]Data[/b]);

[i][b]Pointer[/b][/i] := [b]Data[/b];
vst.addchild(nil, [i][b]Pointer[/b][/i]);

N := vst.addchild(nil);
AVST.SetNodeData(N, [b]Data[/b]);

[i][b]Pointer[/b][/i] := [b]Data[/b];
n := vst.addchild(nil);
AVST.SetNodeData(N, [i][b]Pointer[/b][/i]);

... // vieles, vieles mehr
Hier ist ja mehr das Problem, wie die Daten in der VTV gespeichert/verwaltet sollen.






So, nun noch der wohl einfachere Weg ... das Objekt erzeigen und nur die referenz daruf übergeben:
Delphi-Quellcode:
procedure insertSession();
var SessionObject: TSessionObject;
begin
  SessionObject := TSessionObject.Create;
  SessionObject.datum:=DateTimeToUnix(Form1.DateTimePicker1.Date);
  SessionObject.sessiontyp:=Form1.ComboBox1.Text;
  SessionObject.protokollant:=Form1.Edit1.text;
  SessionObject.teilnehmer:=Form1.Edit2.Text;
  SessionObject.verteiler:=Form1.Edit3.Text;
  SessionObject.ndatum:=DateTimeToUnix(Form1.DateTimePicker2.Date);
  Result:=Form1.VST1.AddChild(nil, SessionObject);
end;

Diese kann auch ins OnCreate der Form (falls man es nicht auch direkt im OI ändern kann/will) ... einmal reicht ja, die Größe ändert sich ja nicht (PS: SizeOf(TTreeData) = Pointer = 4 Byte, also 4)
Delphi-Quellcode:
VST1.NodeDataSize:=SizeOf(TTreeData);
Delphi-Quellcode:
procedure deleteSession(Node: PVirtualNode);
var SessionObject: TSessionObject;
begin
  TSessionObject(Form1.VST1.GetNodeData(Node)).Free;
  Form1.VST1.Delete; // keine Ahnung ob das wirklich so heißt
end;

Folgendes kann auch ins OnCreate der Form (falls man es nicht direkt im OI ändern kann/will) ... einmal reicht ja, die Größe ändert sich schließlich nicht (PS: SizeOf(TTreeData) = Pointer = 4 Byte, also 4)
Delphi-Quellcode:
VST1.NodeDataSize:=SizeOf(TTreeData);

generic 1. Jun 2006 09:34

Re: VirtualTreeView: Es klappt nicht ganz
 
das sieht sehr komplizert aus.
die vcl hat doch noch die klasse tpersistent.
warum leitest du nicht von der ab und nutzt die methoden der vcl um objekte in streams zu speichern?
(ich muss zugeben mit dem thema habe ich mich noch nicht beschäftigt)

JPSelter 9. Jun 2006 13:36

Re: VirtualTreeView: Es klappt nicht ganz
 
So, habe heute endlich mal weitermachen können an dem Projekt. Laden und Speichern funktioniert jetzt einwandfrei. Nächstes Problem:

Ich rechtsklicke auf einen Node, um auf einem PopUpMenu "edit" auszuwählen. Dann erscheint ein neues Formblatt in das ich einen neuen Namen für den Knoten eintippe. Dann klicke ich auf Speichern und das Formblatt verschwindet. Der Name des Knotens wird nun im VST aktualisiert und angezeigt. Problem: Solange der Knoten immer noch blau selektiert ist bleibt diese "blaue Box" wie sie vorher war und passt sich nicht der neuen Wortlänge an. Ich muss erst woandershin klicken und zurückklicken, dann ist die "blaue Selektionsbox" so breit wie der Name des Knotens. Versteht Ihr was ich meine? Ich mache sonst maln Screenshot. Gibts irgendwie eine Repaint-Funktion, falls die überhaupt die Lösung wäre?

Dale 9. Jun 2006 13:43

Re: VirtualTreeView: Es klappt nicht ganz
 
da sollte folgendes helfen:
Delphi-Quellcode:
VirtualTreeView.InvalidateNode(Node);

Gruß Dale

JPSelter 9. Jun 2006 13:49

Re: VirtualTreeView: Es klappt nicht ganz
 
Wann genau soll ich das ausführen? Ich habs jetzt nach der Knotenaktualisierung, aber es passiert nichts.

Dale 9. Jun 2006 13:54

Re: VirtualTreeView: Es klappt nicht ganz
 
hm, wundert mich, dass es nicht funktioniert. Versuchs mal mit:

Delphi-Quellcode:
VirtualTreeView.BeginUpdate;
...
// Knoten aktualisieren;
...
VirtualTreeView.EndUpdate;
ansonsten solltest du mal den Quelltext posten.

Gruß Dale

JPSelter 9. Jun 2006 14:03

Re: VirtualTreeView: Es klappt nicht ganz
 
Hier etwas Code, es klappt immernoch nicht:

Delphi-Quellcode:
procedure updateSession;
var Data: PTreeData;
begin
  Form1.VST1.BeginUpdate();
  Data := Form1.VST1.GetNodeData(currentNode);
  TSessionObject(Data.FObject).datum:=datetimetounix(Form1.DateTimePicker1.date);
  [...]
  Form1.VST1.EndUpdate();
  Form1.VST1.InvalidateNode(currentNode);
end;
Delphi-Quellcode:
procedure TForm2.Button1Click(Sender: TObject);
begin
  if editmode then
    updatePoint
  else
    insertPoint;
  saveTree;
  editmode:=false;
  close;
end;
currentNode ist global und enthält die aktuell selektierte Node.

Dale 9. Jun 2006 14:53

Re: VirtualTreeView: Es klappt nicht ganz
 
Stimmt alles was du geschrieben hast,

es hilft nur folgendes: (ich habs getestet)

Delphi-Quellcode:
VirtualStringTree.BeginUpdate;
...
VirtualStringTree.ReinitNode(CurrentNode, False);
VirtualStringTree.EndUpdate;
Gruß Dale

JPSelter 14. Jun 2006 08:39

Re: VirtualTreeView: Es klappt nicht ganz
 
So, da bin ich wieder. Hab den Vorschlag mit dem ReInitNode getestet, aber es wirkt sich immernoch nicht positiv auf das Programm aus. Habe auch mal dieses RepaintNode ausprobiert, aber es klappt nicht. Vielleicht liegt der Fehler woanders, ich schau nochmal rein...


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