# TreeModelEvents auslösen



## AlArenal (3. Feb 2005)

Salut!

Ich hänge hier gerade mal wieder in einem Projekt. Und zwar habe ich mir mein eigenes Datenmodell (implements TreeModel) gestrickt. Eine Testroutine schiebt da Testdaten rein und es wird auch alles richtig angezeigt.

Spannend wird es nun beim interaktiven hinzufügen und entfernen von Daten. Mein Model beherbergt eine HashMap in der alle meine Objekte mit ihren IDs abgelegt sind. Es handelt sich um Instanzen eigener Klassen, die nicht von irgendwas abgeleitet sind.

Entferne ich nun ein Objekt (erst lösche ich die Refenzen, dann das Objekt in der HashMap) tut sich im Tree natürlich mal gar nichts, weil ich noch den passenden Event auslösen muss. Problem ist, dass ich mit den Parametern im Konstruktor nicht viel anfangen kann.  Ich habe z.B. ad hoc keine Ahnung wie ich von einem meiner Objewkte den TreePath bestimmen sollte. Ich finde auch keine passenden Beispiele, weil alle immer DefaultMutableTreeNodes verwenden und da der Fall dann gleich anders aussieht..

Bin hier gerade etwas gefangen in Ideenlosigkeit...

Weiß einer ein Tutorial oder ein gutes Beispiel?


----------



## Roar (3. Feb 2005)

du musst an alle deine hinzugefügtn TreeModelListener events feuern.
wenn also ein node gelöscht wurde musst du von allen listenern treeNodesRemoved aufrufen, mit einem TreeModelEvent als parameter. dem TreeModelEvent übergibst du die parameter wie sie ausführlich in der dokumentations beschrieben sind.


----------



## AlArenal (3. Feb 2005)

Diese Methoden haben ich schon (z.B. fireTreeNodesRemoved(TreeModelEvent e) ) , aber die Aufrufparameter des Events machen mir Kopfzerbrechen. Aus den Beschreibungen der Konstruktoren geht für mich nicht hervor wann welcher Konstruktor zu verwenden ist. Was ist der Unterschied zwischen TreeModelEvent(Object source, TreePath path, int[] childIndices, Object[] children) und TreeModelEvent(Object source, Object[] path, int[] childIndices, Object[] children) ? Und wie komme ich an den TreePath eines meiner Objekte? Und was ist the magic behind all diesen Parametern?

Mir fehlen da Beispiele zum Nachvollziehen.  Mein Swing-Buch von Sun schweigt sich da auch aus und meine neuen Bücher sind noch unterwegs


----------



## AlArenal (16. Feb 2005)

Ich sitze immernoch an diesem Scheiß-Problem, surfe die einschlägigen Foren bei Sun rauf und runter und google mich wund, aber komme nicht wirklich weiter. Viele lösen das Problem einfach, indem sie ein DefaultTreeModel und DefaultMutableTreeNode benutzen, aber das fällt für mich flach, weil es meine Anwendung ohne ENde verkomplizieren würde.

Problem ist einfach eine vernünftige Vorlage zu finden, wie man diese verdammten TreeModelEvents auslöst. Wenn ich mir den Quellcode vom DefaultTreeModel ansehe, bekomme ich Fragezeichen in die Augen und übernehme ich testeshalber Code, funzt der natürlich nicht (Anzeige ändert sich nicht; StackOverflowErrors, ...)...

Und dem Buchhändler wo mein Chef bestellt hat trete ich auch noch in den Arsch, weil der seit Wochen nen feuchten Dreck liefert.. grrrr.....


----------



## Beni (16. Feb 2005)

Am besten verwendest du den _public TreeModelEvent(Object source, Object[] path, int[] childIndices, Object[] children) _-Konstruktor.

So, ich hoffe mal ich labbere keinen quatsch zusammen...

Egal ob Childs hinzugefügt, entfernt oder nur verändert werden, der Konstruktor des TreeEvents bleibt derselbe, nur die Methode die bei den TreeModelListenern aufgerufen wird, wird anders gewhält (treeNodesRemove, Changed, ...).

Dieser Konstruktor hat 4 Parameter:
*source* Das "Objekt" das das Event abfeuert. Du kannst hier dasjenige Objekt einsetzen, in welchem die TreeModelListener registriert sind -> also nichts anderes als dein TreeModel

*path*
Das ist der Pfad zum Parent (!) der Knoten welche eingefügt oder gelöscht wurden.
Dieser Pfad geht "von oben nach unten", also an erster Stelle ist die Wurzel des Baumes, an zweiter Stelle ein Knoten aus der 2. Ebene des Baumes, ..., an letzter Stelle der Parent:

path[0] = root;
path[1] = Knoten aus Ebene 2;
path[2] = Knoten aus Ebene 3;
path[3] = Knoten aus Ebene 4;
path[4] = Der Parent derjenigen Knoten, welche hinzugefügt, entfernt oder verändert wurden. Im weiteren nenn ich diesen Knoten mal _Parent_.

*childIndices*
Angenommen beim Parent wurden zwei Kinder entfernt. Jeder Knoten in einem JTree verhält sich ja wie eine Liste (die Childs werden mit Indices angesprochen), von dem her hatten diese zwei Kinder bevor sie entfernt wurden einen eindeutigen Index.
Genau diese beiden Indices müssen hier übergeben werden.
_Wichtig:_ beim Entfernen werden hier die Indices der Kinder _vor_ der Entfernen-Aktion genommen
beim Einfügen werden hier die Indices der Kinder _nach_ der Einfügen-Aktion genommen

*children*
Tja, und das sind die Knoten, welche entfernt, eingefügt oder verändert wurden.


Beispiel: Du hast folgenden Baum


```
'root'
 +- 'alpha'
 |   L '123'
 L- 'beta'
     + 'a'
     + 'b'
     L 'c'
```

Nun werden b und c entfernt.
Dann wird dein TreeModelEvent folgendermassen aufgebaut:

```
path = new Object[2];
path[0] = root;
path[1] = beta;

childIndices = new int[2];
childIndices[0] = 1; // "b"
childIndices[1] = 2; // "c"

childs = new Object[2];
childs[0] = b;
childs[1] = c;

event = new TreeMovelEvent( this, path, childIndices, childs );
```

Wie immer: ohne Gewähr :wink:


----------



## AlArenal (16. Feb 2005)

@beni:

Danke für deine Hilfe! Jetzt ist schon einiges klarer..  Melde mich in Kürze weider....


----------



## AlArenal (16. Feb 2005)

Also zumindest das Löschen von Nodes funzt nun, alles andere habe ich noch nicht testen können. Nun habe ich aber eine weitere Denksportaufgabe:

Ich nutze ein eigenes TreeModel als zentrale Datenquelle. Um dieses Model herum gibt es mehrere weitere Models, die das zentrale Model als Datenquelle nutzen. So kann ich in unterschiedlichen JTrees unterschiedliche Sichtweisen (Filter) auf dieselben Daten reaisieren.

Für sich genommen funzt beides, nur in der Kombination funktioniert es nicht. Da fällt mir auch gerade nichts schlaues ein. *grübel*


----------



## Beni (16. Feb 2005)

Ich würde das so machen: Die Filter-Modelle registrieren TreeModelListener's beim zentralen TreeModel, und rechnen die Events dann auf ihre Indices (=angezeigte Teile) um.


----------



## AlArenal (16. Feb 2005)

Sowas hatte ich mir auf dem Heimweg auch überlegt. Muss ich mir mal Gedanken machen, wenn ich wieder nüchtern bin....


----------



## AlArenal (17. Feb 2005)

@beni:

Ich bin gerade noch bei der Planung und auf folgendes Problem gestoßen. Sagen wir mal wir haben neben dem zentralen TreeModel noch zwei FilterModels. Gehen wir weiter davon aus, dass die JTrees nie direkt das zentrale Model benutzen, sondern über die Filter gehen.

Nun nehmen wir an über Filter1 würde ein Node gelöscht. Dann müsste Filter1 an seine registrierten TreeModelEventListener entsprechende Events abgeben und das tatsächliche Löschen über das zentrale Model durchführen.

Daraufhin wirft das zentrale Model ja ebenfalls an seine Listener (also auch Filter2) entsprechende Events. Wie soll nun aber Filter2 an dessen registrierte Listener passend umgerechnete Events weiterleiten? Im zentralen Model ist ein Node gelöscht worden und Filter2 hat doch nun keine Möglichkeit mehr zu bestimmen welchen Index der Node in diesem Filter hatte?

*grübel*


----------



## Beni (17. Feb 2005)

AlArenal hat gesagt.:
			
		

> Nun nehmen wir an über Filter1 würde ein Node gelöscht. Dann müsste Filter1 an seine registrierten TreeModelEventListener entsprechende Events abgeben und das tatsächliche Löschen über das zentrale Model durchführen.


Wenn Filter1 da zentrale Model abhört, muss er nicht selbst noch ein Event abschicken (das wird sonst irgendwie doppelt: Filter1 verschickt Event, zentrales Model verschickt Event woraufhin Filter1 nocheinmal ein Event verschickt). ???:L 
(Oke, das kommt draufan wie du das schlussendlich genau implementierst, aber ich glaub es ist am einfachsten, wenn Filter1 erst ein Event verschickt, wenn es von der Zentrale ein Event bekommt).



> Filter2 hat doch nun keine Möglichkeit mehr zu bestimmen welchen Index der Node in diesem Filter hatte?


Doch, hat er: wenn das zentrale Model die Events so aufbaut wie oben beschrieben, hat es die Indices in dem Event gespeichert. Filter2 kann nun diese Indices abrufen, und so den Aufbau des Baumes (ohne den Filter) rekonstruieren. Wenn aber der originale Baum rekonstruiert ist, ist es keine Kunst mehr die Indices umzurechnen.

Ein bisschen Pseudocode, wie man das Original rekonstruieren könnte:

```
public void nodesRemoved( TreeModelEvent e ){
  int[] indices = e.getChildIndices();
  Object[] children = e.getChildren();

  List nodes = ... // Liste der Knoten, welche jetzt (nach dem Entfernen) noch beim Parent registriert sind.

  // und jetzt die entfernten Children an der alten Position wieder einfügen
  for( int i = indices.length-1; i >= 0; i-- ){
    nodes.add( indices[i], children[i] );
  }

  // jetzt beschreibt nodes die Kinder des Parents, bevor ein paar entfernt wurden.

  // das Umrechnen funktioniert jetzt wie früher: Knoten abrufen, und index hochzählen, sollte ein Knoten angezeigt werden.
}
```


----------



## AlArenal (17. Feb 2005)

Es funzt!

Meine Filter implementieren TreeModelListener und registrieren sich im ZentralModel. Die Löschanforderung für einen Node wird zum ZentralModel durchgereicht, welches den passenden Event auslöst. Diesen fangen die FilterModels ab und erzeugen den Path und den Index neu, entsprechend ihrer Sicht auf die Daten und generieren dann den TreeModelEvent für ihre Listener (JTrees).

Nun muss ich erstmal ne Runde Doku schreiben, damit das später auch mal wer anders rafft...


----------



## AlArenal (24. Feb 2005)

Bin gerade beim nachträglichen Einfügen von Nodes:

Ich füge über den Filter ein, der das Ganze nach unten an das zentrale Model weiterreicht. Dazu ruft der Filter auch die  fireTreeNodesInserted() vom zentralen Model auf.

Soweit funzt das, allerdings klappt der JTree bei seinem Refresh alle Äste ein, wenn er dann den neuen Node darstellt. Eigentlich hätte ich erwartet, dass die Anzeige halt nur um den neuen zusätzlichen Node erweitert wird.

Gibts da nen Trick?


----------



## Beni (24. Feb 2005)

Und das Filtermodel ruft dann auch treeNodesInserted auf? Die Angaben (Parent des neuen Childs etc) stimmen?


----------



## AlArenal (24. Feb 2005)

Hatte gerade noch nen Dnekfehler, aber der Effekt ist noch da. Ich habe folgendes:

Pseudocode:


```
JTree.insertNode(node) {
  rootModel.fireTreeNodesInserted(convertForRootModel(TreeModelEvent));
}

RootModel.fireTreeNodesInserted(TreeModelEvent) {
  foreach (TreeModelEventListener) {
    treeModelListener.treeNodesIserted(TreeModelEvent);
  }
}

Filter.treeNodesInserted(TreeModelEvent) {
  fireTreeNodesInserted(convertForFilter(TreeModelEvent));
}
```

Was der Tree anzeigt ist grundsätzlich richtig, nur dampft er den Tree bis auf alles unterhalb des RootNodes ein und verliert auch die Selection. Als hätte ich ein neues Model geladen, was ich aber nicht tue.

Im Grunde benutze ich diegleiche Methode als wenn ein Node removed worden wäre, was die Umrechnung zwischen den Models angeht, nur das ich eben die passenden Methoden beim Listener aufrufe. Im Filter siehts am Ende so aus:


```
// geänderten Event an eigene Listener schicken:
        fireTreeNodesInserted(
                new TreeModelEvent(
                        this,
                        getPathToRoot((FMEAInterface) getRoot(), (FMEAInterface) parent),
                        new int[] {indexHere},
                        new Object[] {insertedChild}
                )
        );
```


----------

