Visualizzazione dei risultati da 1 a 9 su 9
  1. #1

    [DELPHI] TreeView e cancellazione nodi

    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:


    codice:
    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)
    La differenza fra un cammello e un uomo? Il cammello può lavorare una settimana senza bere. L'uomo può bere una settimana senza lavorare. (Julian Tuwim)

    A casa mia non si mangia mai a stomaco vuoto!!!

  2. #2
    Moderatore di Programmazione L'avatar di alka
    Registrato dal
    Oct 2001
    residenza
    Reggio Emilia
    Messaggi
    24,472
    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!
    MARCO BREVEGLIERI
    Software and Web Developer, Teacher and Consultant

    Home | Blog | Delphi Podcast | Twitch | Altro...

  3. #3
    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!
    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.
    La differenza fra un cammello e un uomo? Il cammello può lavorare una settimana senza bere. L'uomo può bere una settimana senza lavorare. (Julian Tuwim)

    A casa mia non si mangia mai a stomaco vuoto!!!

  4. #4
    Moderatore di Programmazione L'avatar di alka
    Registrato dal
    Oct 2001
    residenza
    Reggio Emilia
    Messaggi
    24,472
    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!
    MARCO BREVEGLIERI
    Software and Web Developer, Teacher and Consultant

    Home | Blog | Delphi Podcast | Twitch | Altro...

  5. #5
    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!
    Il problema è che l'errore lo ricevo subito dopo aver eseguito la funzione delete del treeView.
    Hai qualche idea al riguardo?
    La differenza fra un cammello e un uomo? Il cammello può lavorare una settimana senza bere. L'uomo può bere una settimana senza lavorare. (Julian Tuwim)

    A casa mia non si mangia mai a stomaco vuoto!!!

  6. #6
    Moderatore di Programmazione L'avatar di alka
    Registrato dal
    Oct 2001
    residenza
    Reggio Emilia
    Messaggi
    24,472
    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?
    MARCO BREVEGLIERI
    Software and Web Developer, Teacher and Consultant

    Home | Blog | Delphi Podcast | Twitch | Altro...

  7. #7
    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.

    codice:
    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!!!
    La differenza fra un cammello e un uomo? Il cammello può lavorare una settimana senza bere. L'uomo può bere una settimana senza lavorare. (Julian Tuwim)

    A casa mia non si mangia mai a stomaco vuoto!!!

  8. #8
    Moderatore di Programmazione L'avatar di alka
    Registrato dal
    Oct 2001
    residenza
    Reggio Emilia
    Messaggi
    24,472
    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:

    codice:
      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!
    MARCO BREVEGLIERI
    Software and Web Developer, Teacher and Consultant

    Home | Blog | Delphi Podcast | Twitch | Altro...

  9. #9
    Grazie, utilissimo come sempre.
    La differenza fra un cammello e un uomo? Il cammello può lavorare una settimana senza bere. L'uomo può bere una settimana senza lavorare. (Julian Tuwim)

    A casa mia non si mangia mai a stomaco vuoto!!!

Permessi di invio

  • Non puoi inserire discussioni
  • Non puoi inserire repliche
  • Non puoi inserire allegati
  • Non puoi modificare i tuoi messaggi
  •  
Powered by vBulletin® Version 4.2.1
Copyright © 2025 vBulletin Solutions, Inc. All rights reserved.