Autor |
Beitrag |
vlad_tepesch
Hält's aus hier
Beiträge: 2
|
Verfasst: Mi 06.06.12 23:25
Hi
Ich hab eine (für Delphi-Kenner wahrscheinlich triviale) Frage:
Ich komme aus der C/C++ Ecke und wollte nach Jahren der Delphi-Abstinenz eine Gui für etwas bauen.
Hier mein Problem:
Ich habe ein Listview, an dass ich Elemente anhänge, verschiebe lösche.
An die Items möchte ich eine eigene Datenstruktur (Abgeleitet von TObjekt) hängen.
Genau Dafür gibt es ja das data member.
Irgendwie komme ich in Delphi noch nicht ganz klar mit Objekten/Pointern/Referenzen, bzw weiß nicht, wann ich womit arbeite.
ICh hänge im Hinzufügen-Button ein neues ELement mit Zusätzlichen Daten an den Listview.
Später im onSelect haben sich aber die Werte geändert.
meine Routine zum Hinzufügen von Items sieht so aus:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39:
| procedure TForm1.BtnListItemAddClick(Sender: TObject); var rawModeData: TRawModeData; li:TListItem; begin rawModeData := TRawModeData.Create; li := LstModes.Items.Add(); li.Data := @rawModeData; li.ImageIndex := -1; LstModes.ItemIndex := LstModes.Items.Count-1; BtnListItemChangeClick(sender); end;
procedure TForm1.BtnListItemChangeClick(Sender: TObject); var item : TListItem; rawModeData:PRawModeData; mode:integer; pulse:integer; begin item := LstModes.Selected; if item <> nil then begin rawModeData := PRawModeData(item.Data); rawModeData^:= createRawModeFromConfig(); item.Caption := 'test' end;
end;
procedure TForm1.LstModesSelectItem(Sender: TObject; Item: TListItem; Selected: Boolean); var modeData:TRawModeData; begin if Item<>nil then begin modeData := PRawModeData(item.Data)^; rawModeToConfig(modeData); end; end; |
ich vermute, dass das Problem in den ersten 3 Zeilen von BtnListItemAddClick.
das TRawModeData.Create sollte eine neue Instanz im Heap anlegen?
in C++ würde man hier
new TRawModeData();
schreiben
Ich benutze Delphi 6
Hoffe jemand kann mir einen Tip geben.
|
|
jaenicke
      
Beiträge: 19314
Erhaltene Danke: 1747
W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: Mi 06.06.12 23:36
Ich würde dir sehr raten die VirtualTreeView stattdessen zu benutzen:
www.delphi-gems.com/...ols/virtual-treeview
Die ist nämlich exakt dafür konzipiert, dass da Daten an die Einträge angehängt werden, sehr flexibel und extrem schnell.
Da schreibst du einfach so in der Art: AddNode(RootNode, MyData)
Wobei MyData dann einfach ein Objekt sein kann.
|
|
Martok
      
Beiträge: 3661
Erhaltene Danke: 604
Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
|
Verfasst: Do 07.06.12 05:54
Moin Dracula
Objekte in Delphi sind schon Zeiger, man muss also nicht nochmal eine Pointer auf den Pointer anlegen.
Delphi-Quelltext 1:
| li.Data := @rawModeData; |
Du speicherst einen Zeiger auf die Stack-Variable rawModeData. Dass der Zugriff danach überhaupt funktioniert ist ein absoluter Zufall. Den gleichen Denkfehler hat auch der Rest, aber für alles weitere müsste man jetzt wissen: willst du wirklich Objekte oder Records (also Structs) speichern? Und, wie sieht createRawModeFromConfig aus?
Außerdem hat Delphi keinen Garbage Collector (unmanaged C++ doch auch nicht), also baust du einige nette Speicherlecks, da du deine Objekte nie freigibst.
jaenicke hat folgendes geschrieben : | Die ist nämlich exakt dafür konzipiert, dass da Daten an die Einträge angehängt werden, sehr flexibel und extrem schnell. |
Und für jemanden, der an der Stelle stolpert, auch total sinnvoll. Ja ne is' klar.
_________________ "The phoenix's price isn't inevitable. It's not part of some deep balance built into the universe. It's just the parts of the game where you haven't figured out yet how to cheat."
|
|
jaenicke
      
Beiträge: 19314
Erhaltene Danke: 1747
W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: Do 07.06.12 06:24
Martok hat folgendes geschrieben : | jaenicke hat folgendes geschrieben : | Die ist nämlich exakt dafür konzipiert, dass da Daten an die Einträge angehängt werden, sehr flexibel und extrem schnell. | Und für jemanden, der an der Stelle stolpert, auch total sinnvoll. Ja ne is' klar. |
Ja, weil es einfacher ist... 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33:
| procedure TfrmVirtualTreesSmallExampleMain.FormCreate(Sender: TObject); begin FMyData := TObjectList<TExampleData>.Create(True); FMyData.Add(TExampleData.Create('Test', 'a')); FMyData.Add(TExampleData.Create('Test2', 'b')); FMyData.Add(TExampleData.Create('Test3', 'c')); vstExample.NodeDataSize := SizeOf(TExampleData); ReloadData; end;
procedure TfrmVirtualTreesSmallExampleMain.FormDestroy(Sender: TObject); begin FMyData.Free; end;
procedure TfrmVirtualTreesSmallExampleMain.ReloadData; var CurrentData: TExampleData; begin vstExample.ChildCount[vstExample.RootNode] := 0; for CurrentData in FMyData do vstExample.AddChild(vstExample.RootNode, CurrentData); end;
procedure TfrmVirtualTreesSmallExampleMain.vstExampleGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText: string); begin case Column of 0: CellText := PExampleData(vstExample.GetNodeData(Node)).ExampleData1; 1: CellText := PExampleData(vstExample.GetNodeData(Node)).ExampleData2; end; end; | Komplettes Beispiel im Anhang.
Und wenn sich die Daten im Hintergrund geändert haben, muss man nur Invalidate auf die Komponente aufrufen, damit sie neu gezeichnet wird. Man muss aber nicht groß irgendwo in der Komponente herumfummeln und die Überschrift aktualisieren wie bei der Standard-TListView.
Zudem gibt es auch einen generischen Wrapper, der in dem Paket schon dabei ist, wenn man das möchte.
Einloggen, um Attachments anzusehen!
|
|
vlad_tepesch 
Hält's aus hier
Beiträge: 2
|
Verfasst: Do 07.06.12 20:47
Danke erst mal für eure Antworten.
Martok hat folgendes geschrieben : | Moin Dracula
Objekte in Delphi sind schon Zeiger, man muss also nicht nochmal eine Pointer auf den Pointer anlegen.
|
wie denn dann?
wenn ich eine Variable TRawModeData anlege, dann kann ich die nicht an den Pointer zuweisen.
Zitat: |
Delphi-Quelltext 1:
| li.Data := @rawModeData; |
Du speicherst einen Zeiger auf die Stack-Variable rawModeData.
|
genau das habe ich mir gedacht.
wo ich keine prinzipiellen Verständisprobleme habe, ist was das generelle Konzept "Pointer" angeht.
Was mir fehlt, ist wann ich womit in Delphi zu tun habe.
Ich meine gelesen zu haben, dass in Delphi Objekte generell auf dem Heap angelegt werden.
Ich dachte, dass mit dem Typname.Create() ein Heapobjkekt angelegt wird. Das war wahrscheinlich falsch.
wenn ich ein Objekt auf dem Heap anlegen will, von dem ich den Zeiger haben will - was muss ich da machen?
Delphi-Quelltext 1: 2: 3:
| var rawModeData: PRawModeData;
new(rawModeData); |
so? wird da der Konstruktor mit aufgerufen, oder nur speicher alloziiert?
was ist das Delphi-Äquivalent zu folgendem C++-Code
Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9:
| TRawModeData* rawModeData = new RawModeData(); // heapobjekt mit defaultkonstruktor rawModeData->irgendwas = 6; //dereferenzierung und memberzugriff
TRawModeData localData(5); // stackobjekt mit speziellem konstruktor *rawModeData = localData; // kopieren des lokalen objektes an die Speicherstelle, auf die der Zeiger zeigt.
void* genericPointer = rawModeData; //pointer in typfreien Pointer speichern
rawModeData = (TRawModeData*)genericPointer ; // casten des generischen pointers auf Typbehafteten Pointer |
Zitat: |
Dass der Zugriff danach überhaupt funktioniert ist ein absoluter Zufall. Den gleichen Denkfehler hat auch der Rest, aber für alles weitere müsste man jetzt wissen: willst du wirklich Objekte oder Records (also Structs) speichern?
|
die werden unterschiedlich gehandlet?
vom Prinzip ist es egal. Das Objekt ist nur 4 Byte groß.
Zitat: |
Und, wie sieht createRawModeFromConfig aus?
|
in etwa wie folgt:
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8:
| function TForm1.createRawModeFromConfig():TRawModeData; var r:TRawModeData; begin r := TRawModeData.Create(); createRawModeFromConfig := r; end; |
Zitat: |
Außerdem hat Delphi keinen Garbage Collector (unmanaged C++ doch auch nicht), also baust du einige nette Speicherlecks, da du deine Objekte nie freigibst.
|
Ich dachte die Objekte liegen auf dem Stack?
dh sie müssten doch automatisch abgeräumt werden. (das passiert zumindest in C++).
Das Objekt an dem ListItem lösche ich auf jeden Fall vor dem entfernen des Listitems.
Quelltext 1: 2: 3: 4: 5:
| void test () { IrgendEineKlasse stackisntanz; // <- defaultkonstructur // .. } // <- beim verlassen wird Destructor aufgerufen |
zu der anderen Komponente:
Da das ding eigentlich fast fertig ist.
(es fehlt halt nur noch die Sache mit den Datenobjekten) würde ich ungern jetzt nochmal so viel umstellen.
|
|
Martok
      
Beiträge: 3661
Erhaltene Danke: 604
Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
|
Verfasst: Do 07.06.12 21:45
Und genau deswegen ist ein VT keine Lösung, sondern nur ein anderes Problem
Objekte im Wortsinn (also die Dinger mit object) liegen auf dem Stack, ja. Genau deswegen kann man sie aber nicht direkt in die Liste stecken, sie wären ja nach der Funktion wieder weg. Es geht (mit der gleiche Methode wie records [weil sie quasi-identisch sind], s.u.), aber wenn man die Verrenkungen macht kann man auch gleich eine Klasse nehmen und eine Instanz speichern.
(Ich muss wohl doch mal auf die Wortwahl achten, wegen Objekt<>Instanz<>Klasse<>Record)
Warum ich nach Record oder Instanz frage, ist eigentlich nur die Art, wie du sie erstellst und freigibst. Instanzen immer mit Create und Free (wie du das auch schon machst), Zeiger auf Records werden mit New()/Dispose() initialisiert/freigegeben.
Für Klassen würde das ungefähr so aussehen:
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22:
| var Obj: TMyClass; begin Obj:= TMyClass.Create; ListItem.Data:= Pointer(Obj); var Obj: TMyClass; begin Obj:= TMyClass(ListItem.Data); var Obj: TMyClass; begin Obj:= TMyClass(ListItem.Data); Obj.Free; Obj:= TMyClass.Create; ListItem.Data:= Pointer(Obj); |
Records dagegen liegen, wenn man sie als lokale Variable deklariert, immer auf dem Stack, also müssen wir die selber auf den Heap legen und alles zu Fuß machen.
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26:
| PMyRecord = ^TMyRecord; TMyRecord = record end;
var Rec: PMyRecord; begin New(Rec); ListItem.Data:= Rec;
var Rec: PMyRecord; begin Rec:= PMyRecord(ListItem.Data); Rec^.Attribut:= 42;
var Rec: PMyRecord; begin Rec:= PMyRecord(ListItem.Data); Dispose(Rec); New(Rec); ListItem.Data:= Rec; |
Alles klar soweit? Super, dann kommen jetzt die Tricks
vlad_tepesch hat folgendes geschrieben : | vom Prinzip ist es egal. Das Objekt ist nur 4 Byte groß. |
Wenn das immer so ist (es also nicht größer wird), und du keine Methoden brauchst, brauchst du eigentlich gar keinen eigenen Speicher.
Was ist (in 32bit-Compilaten) noch 4 Byte groß? Richtig, ein Pointer  Ein Pointer ist ja auch nur eine Zahl, wir können also direkt das Feld verwenden und brauchen keinen Speicher zu reservieren.
Nehmen wir mal an, deine 4 Byte sind ein DWORD:
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20:
| PMyRecord = ^TMyRecord; TMyRecord = record end;
var Flags: DWORD; begin ListItem.Data:= Pointer(Flags); var Flags: DWORD; begin Flags:= DWORD(ListItem.Data);
var newFlags: DWORD; begin ListItem.Data:= Pointer(newFlags); | Da das keine echten Zeiger sind, sondern wir nur ein Feld anders interpretieren, brauchen wir uns um nichts weiter zu kümmern.
_________________ "The phoenix's price isn't inevitable. It's not part of some deep balance built into the universe. It's just the parts of the game where you haven't figured out yet how to cheat."
|
|
jaenicke
      
Beiträge: 19314
Erhaltene Danke: 1747
W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: Do 07.06.12 22:02
Wobei man solche Casts von Pointer auf Integer/DWORD niemals mehr so machen sollte. Da nun bekannt ist, dass es den Typ NativeInt dafür in aktuellen Delphiversionen gibt, sollte man den grundsätzlich bei älteren Delphiversionen deklarieren und für solche Casts verwenden.
Sprich: Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9:
| {$if CompilerVersion < 21} type NativeInt = LongInt; {$ifend}
var Flags: NativeInt; begin Flags := NativeInt(ListItem.Data); | So funktioniert der Quelltext mit allen Delphiversionen. Die Definition von NativeInt kann man natürlich auch in eine eigene Unit packen und die immer einbinden.
|
|
Martok
      
Beiträge: 3661
Erhaltene Danke: 604
Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
|
Verfasst: Do 07.06.12 22:10
Oder PtrInt/PtrUInt in FPC.
Deswegen aber auch meine Einschränkung auf die Architektur.
_________________ "The phoenix's price isn't inevitable. It's not part of some deep balance built into the universe. It's just the parts of the game where you haven't figured out yet how to cheat."
|
|
|