PDA

Visualizza la versione completa : [DELPHI] TreeView e cancellazione nodi


MrCocò85
24-08-2009, 11:13
Salve a tutti.
ho un problema con l'eliminazione dei nodi di un TreeView: ogni volta che cancello un nodo
-nella modalità progettazione mi esegue il debug nella CPU Viewer
-avviando direttamente il file .exe ottengo una eccezione di 'Access Violation' nel modulo ntdll.dll.

Ecco la procedura che esguo per l'elimininazione dei nodi:




var
nd:TTreeNode;
i:integer;
begin
nd:=TreeView1.Items.GetNode(HTREEITEM(itemId));
for i:=0 to nd.count-1 do
begin
nd.Item[0].Data := nil;
TreeView1.Items.Delete(nd.Item[0]);
end;


Ogni nodo contiene un oggetto( utilizzandando .data)

alka
24-08-2009, 12:18
Mi pare di intravedere un errore certo e un errore quasi certo. :)

L'errore certo è nel ciclo di eliminazione: va fatto partendo dall'ultimo elemento e risalendo al primo, poiché in caso contrario il limite superiore dell'indice arriva ben presto a superare gli elementi disponibili. Se gli elementi sono 10, il ciclo arriverà fino a 10 - 1, ma ogni volta che elimini un nodo questi si riducono, quindi ben presto si arriva ad un'eccezione.

L'errore quasi certo, che dipende da come è strutturata l'applicazione in merito alla gestione degli oggetti assegnati nella proprietà Data, è dato dal fatto che questi oggetti non vengono apparentemente distrutti, quindi si verifica un "memory leak": occorre richiamare la Free per rilasciare la memoria allocata, prima di perderne il riferimento.

Ciao! :ciauz:

MrCocò85
24-08-2009, 12:48
Originariamente inviato da alka
Mi pare di intravedere un errore certo e un errore quasi certo. :)

L'errore certo è nel ciclo di eliminazione: va fatto partendo dall'ultimo elemento e risalendo al primo, poiché in caso contrario il limite superiore dell'indice arriva ben presto a superare gli elementi disponibili. Se gli elementi sono 10, il ciclo arriverà fino a 10 - 1, ma ogni volta che elimini un nodo questi si riducono, quindi ben presto si arriva ad un'eccezione.


Nel ciclo di eliminazione utilizzo sempre item[0], quindi non ci sono problemi per quello.




L'errore quasi certo, che dipende da come è strutturata l'applicazione in merito alla gestione degli oggetti assegnati nella proprietà Data, è dato dal fatto che questi oggetti non vengono apparentemente distrutti, quindi si verifica un "memory leak": occorre richiamare la Free per rilasciare la memoria allocata, prima di perderne il riferimento.

Ciao! :ciauz:

Come immaginavo il problema è proprio questo.
Solo che non è cosi semplice:
Gli oggetti puntati dal .data sono presenti in altre strutture, quindi facendo TObject(nd.Item[0].Data).free elimino anche gli oggetti che non devono essere eliminati.
Non sò come fare per eliminare i nodi lasciando gli oggetti puntati inalterati.

alka
24-08-2009, 18:19
Originariamente inviato da MrCocò85
Nel ciclo di eliminazione utilizzo sempre item[0], quindi non ci sono problemi per quello.

In effetti hai ragione, anche se bisogna ammettere che così il codice è un po' fuorviante.


Originariamente inviato da MrCocò85
Gli oggetti puntati dal .data sono presenti in altre strutture, quindi facendo TObject(nd.Item[0].Data).free elimino anche gli oggetti che non devono essere eliminati.
Non sò come fare per eliminare i nodi lasciando gli oggetti puntati inalterati.
Se la distruzione degli oggetti viene fatta in un oggetto esterno, allora non ci sono problemi: puoi tranquillamente eliminare il nodo senza fare la Free dell'oggetto referenziato dalla proprietà Data, purché qualcuno - prima o poi, da qualche parte - si ricordi di distruggere quegli oggetti quando non servono più.

Ciao! :ciauz:

MrCocò85
24-08-2009, 18:31
Originariamente inviato da alka
Se la distruzione degli oggetti viene fatta in un oggetto esterno, allora non ci sono problemi: puoi tranquillamente eliminare il nodo senza fare la Free dell'oggetto referenziato dalla proprietà Data, purché qualcuno - prima o poi, da qualche parte - si ricordi di distruggere quegli oggetti quando non servono più.

Ciao! :ciauz:

Il problema è che l'errore lo ricevo subito dopo aver eseguito la funzione delete del treeView.
Hai qualche idea al riguardo?

alka
24-08-2009, 18:43
Originariamente inviato da MrCocò85
Il problema è che l'errore lo ricevo subito dopo aver eseguito la funzione delete del treeView.
Hai qualche idea al riguardo?
Quello che mi appare strano è il modo in cui ottieni il riferimento al nodo TTreeNode.
Come mai usi il metodo GetNode?

MrCocò85
24-08-2009, 22:03
Originariamente inviato da alka
Quello che mi appare strano è il modo in cui ottieni il riferimento al nodo TTreeNode.
Come mai usi il metodo GetNode?

Utilizzo il treeview per rappresentare un xml.
ogni TreeNode punta tramite la proprietà Data ad un 'estensione' della interfaccia IxmlDomElement. Questa estensione ha la proprietà itemId che contiene l'identificativo del TreeNode: in questo modo posso accedere ad ogni elemento dell'xml tramite la proprietà Data, e accedere ad ogni nodo del treeview tramite l'itemId(utile quando si scatena un evento su un elemento).
Conosci qualcosa di più efficace?

Edit:Ho trovato l'errore anche se non capisco bene il perchè: Ti posto un esempio:
Aggiungere ad una nuova application 1 treeview e 2 Tbutton.



unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ComCtrls, StdCtrls;

type

TObj=class(TObject)
obj:Tobject;
itemId:string;
end;

TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
TreeView1: TTreeView;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
a:TTreeNode;
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
obj1,obj2,obj3:TObj;
begin
obj1:=Tobj.Create();
obj1.obj:=TObject.Create();
a:=treeView1.Items.AddChildObject(nil,'A',obj1);
obj1.itemId:=String(a.ItemId);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
self.TreeView1.Items.Delete(a);
end;

end.



Quando sul click button2 avviene l'errore da me descritto.
Ciò non avviene se ItemId della classe Tobj lo dichiaro di tipo HTREEITEM.
Capisco che è un errore di cast(anche se tramite Tobj.itemId riesco a recuperare tranquillamente il nodo), ma non capisco perchè si verifica sulla cancellazione del nodo.
Comunque grazie mille!!!

alka
25-08-2009, 10:03
Originariamente inviato da MrCocò85
Quando sul click button2 avviene l'errore da me descritto.
Ciò non avviene se ItemId della classe Tobj lo dichiaro di tipo HTREEITEM.
Capisco che è un errore di cast(anche se tramite Tobj.itemId riesco a recuperare tranquillamente il nodo), ma non capisco perchè si verifica sulla cancellazione del nodo.

Adesso la situazione è più chiara, o meglio non capisco perché stai facendo puntare la proprietà ItemId del nodo TTreeNode ad una stringa (!!!).

E' lecito e utile che tu memorizzi l'handle del nodo per risalire direttamente allo stesso attraverso un oggetto, ma ovviamente quell'handle viene assegnato dal TreeView per essere poi passato alle varie funzioni API che contribuiscono al funzionamento del controllo.

Se tu alteri la natura e il dato di quella proprietà, è naturale che l'applicazione vada in crash.

Il problema nasce dalla cancellazione poiché proprio in quel momento è probabile che il controllo abbia bisogno della proprietà ItemId di tipo HTreeItem per effettuare la cancellazione "pratica" tramite chiamata all'apposito metodo del controllo... se lo hai fatto puntare ad una stringa, perdendo il valore originale e sostituendolo con tutt'altra risorsa, di tutt'altro tipo, svolgi delle "operazioni illegali" e - in base a dove è stata collocata in memoria la stringa - i risultati sono impredicibili.

Questa è senz'altro l'origine del problema.

Poi, in merito a possibili "best practices", io in genere uso un approccio differente: creo una classe discendente da TTreeNode, ereditando da quest'ultima, e vi aggiungo campi, metodi e proprietà utili allo scopo (ad esempio, un riferimento al nodo XML che ti interessa) e intercetto l'evento CreateNodeClass per restituire al controllo la classe da creare per ciascun nodo.

Un esempio molto rapido:



TMyTreeNode = class (TTreeNode)
private
FText: string;
public
property Text: string read FText write FText;
end;

// ...

procedure TForm1.TreeView1CreateNodeClass(Sender: TCustomTreeView;
var NodeClass: TTreeNodeClass);
begin
NodeClass := TMyTreeNode;
end;



In questo modo, posso "castare" ogni TreeNode estratto dal controllo al tipo TMyTreeNode e accede alle sue proprietà, senza bisogno di utilizzare la generica proprietà Data di tipo TObject.

Ciao! :ciauz:

MrCocò85
25-08-2009, 10:23
Grazie, utilissimo come sempre.

Loading