Autor Beitrag
vlad_tepesch
Hält's aus hier
Beiträge: 2



BeitragVerfasst: 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:


ausblenden volle Höhe 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:
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;    // instanz eigener Struktur anlegen
  li := LstModes.Items.Add();            // neues lsitenelement anlegen
  li.Data := @rawModeData;               // pointer zur instanz speichern
  li.ImageIndex := -1;
  LstModes.ItemIndex := LstModes.Items.Count-1;  // neues element selektieren
  BtnListItemChangeClick(sender);                // und routine zum ändern aufrufen
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(); // befüllt struktur
    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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19314
Erhaltene Danke: 1747

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 3661
Erhaltene Danke: 604

Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
BeitragVerfasst: 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.

ausblenden Delphi-Quelltext
1:
  li.Data := @rawModeData;               // pointer zur instanz speichern					

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.



user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19314
Erhaltene Danke: 1747

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Do 07.06.12 06:24 
user profile iconMartok hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
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...
ausblenden volle Höhe 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:
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 Threadstarter
Hält's aus hier
Beiträge: 2



BeitragVerfasst: Do 07.06.12 20:47 
Danke erst mal für eure Antworten.

user profile iconMartok hat folgendes geschrieben Zum zitierten Posting springen:
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:

ausblenden Delphi-Quelltext
1:
  li.Data := @rawModeData;               // pointer zur instanz speichern					

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?


ausblenden 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

ausblenden 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:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
function TForm1.createRawModeFromConfig():TRawModeData;
var r:TRawModeData;
begin
 r := TRawModeData.Create();
 // ...
 createRawModeFromConfig := r;
 // muss ich hier r wieder Zerströren?
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.
ausblenden 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
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 3661
Erhaltene Danke: 604

Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
BeitragVerfasst: 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:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
// Eintragen:
var
  Obj: TMyClass;
begin
  Obj:= TMyClass.Create;
  ListItem.Data:= Pointer(Obj);  // Delphi will einen Untyped Pointer

// auslesen
var
  Obj: TMyClass;
begin
  Obj:= TMyClass(ListItem.Data);  // "Dieser Pointer zeigt auf eine Instanz"

// ersetzen
var
  Obj: TMyClass;
begin
  Obj:= TMyClass(ListItem.Data);  // "Dieser Pointer zeigt auf eine Instanz"
  Obj.Free;                        // altes freigeben (das würdest du auch beim Item löschen verwenden)

  Obj:= TMyClass.Create;          // neues erstellen, hier könnte die Funktion stehen
  ListItem.Data:= Pointer(Obj);    // und wieder eintragen


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.
ausblenden 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;

// Eintragen:
var
  Rec: PMyRecord;
begin
  New(Rec);
  ListItem.Data:= Rec;

// auslesen
var
  Rec: PMyRecord;
begin
  Rec:= PMyRecord(ListItem.Data);  // "Dieser Pointer zeigt auf diese Datenstruktur"
  Rec^.Attribut:= 42;

// ersetzen
var
  Rec: PMyRecord;
begin
  Rec:= PMyRecord(ListItem.Data);  // "Dieser Pointer zeigt auf diese Datenstruktur"
  Dispose(Rec);                    // altes freigeben (das würdest du auch beim Item löschen verwenden)

  New(Rec);                        // neues erstellen, hier könnte die Funktion stehen
  ListItem.Data:= Rec;    // und wieder eintragen



Alles klar soweit? Super, dann kommen jetzt die Tricks ;)
user profile iconvlad_tepesch hat folgendes geschrieben Zum zitierten Posting springen:
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:
ausblenden 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;

// Eintragen:
var
  Flags: DWORD;
begin
  ListItem.Data:= Pointer(Flags);   // gewöhnlicher cast

// auslesen
var
  Flags: DWORD;
begin
  Flags:= DWORD(ListItem.Data);

// ersetzen: einfach
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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19314
Erhaltene Danke: 1747

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: 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:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
{$if CompilerVersion < 21} // vor Delphi 2010, da NativeInt in 2007 und 2009 kaputt war
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
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 3661
Erhaltene Danke: 604

Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
BeitragVerfasst: 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."