# Problem mit Threads



## mysticado (20. Okt 2011)

Hallo Leute,
ich habe folgendes Problem - schaut euch bitte mal den angehängten Screenshot an! Ich möchte, dass wenn ich auf den button "simulate" drücke, verschiedene Befehle durchlaufen werden und der derzeitige Status des Durchlaufs per _output.append("text");_ in der Output-Textarea angezeigt wird.

Nun habe ich aber in der Mitte meines Durchlaufs folgenden Code:


```
// Starting the VM-Utilize
                Thread t = new Thread();
                     output.append("\n >> Utilizing");
                     t.sleep(1000);
                     t.interrupt();
                     output.append(".");
                     t.sleep(1000);
                     t.interrupt();
                     output.append(".");
                     t.sleep(1000);
                     t.interrupt();
                     output.append(".");
                     utilizeVM();
```

Die Idee ist also die, im Outputfeld den Text "Utilize" anzuzeigen und nach jeder Sekunde jeweils einen Punkt dranhängen, so dass ich dadurch einen Fortschritt vortäuschen kann und am Ende nach 3 Sekunden den Text "Utilizing..." stehen habe.
Mein Problem ist aber, dass nachdem ich auf den Button Simulate gedrückt habe, erstmal meine ganze GUI für 3 Sekunden einfriert und dann auf einmal den kompletten Text "Utilizing..." anzeigt, ohne eben den oben genannten gewollten Effekt.
Ausserdem habe ich bemerkt, dass mein Simulate-Button in den 3 Sekunden auch einfriert, d.h. gedrückt bleibt.


Was kann ich also machen, um diese Animation der Punkte korrekt darzustellen und den Simulate-Button zu "entfrieren"? Wo mache ich den Fehler??

Danke schon mal für die Antworten


----------



## hdi (20. Okt 2011)

Den gesamten Artikeln lesen (Achtung, sind 5 Seiten, nicht übersehen!):

Swing threading and the event-dispatch thread - JavaWorld

Dann wirst du verstehen, warum du die sleep-Anweisungen in einem neuen Thread starten musst.

edit: Wobei du nicht einen SwingWorker einsetzen musst. new Thread() und invokeLater() an geeigneten Stellen geht genauso.


----------



## mysticado (20. Okt 2011)

Wow geile Sache!
Ich habs nun mit dem SwingWorker probiert und habe zumindest schon mal nicht mehr das Problem das der Button einfriert.
Leider funktioniert es aber trotzdem noch nicht so wie ich es will. Es bleibt leider nach dem ersten Punkt stehen (oder bleibt in einer Endlosschleife, oder wasweissich), d.h. ich erhalte als Ausgabe nur "Utilizing."

Hier mein neuer Code:


```
// Starting the VM-Utilize
                output.append("\n >> Utilizing");
                SwingWorker worker = new SwingWorker() {
          protected String doInBackground() throws InterruptedException {
            Thread.sleep(1000);
            return null;
          }
          protected void done() {
            output.append(".");
          }
        };

        worker.execute();
        worker.execute();
        worker.execute();
```

Ausserdem meldet mir mein Debbuger das drei Threads erzeugt werden - also anscheinend einer für jedes execute(). Sie hätten aber nach einander laufen sollen, und nicht gleichzeitig - wo ist denn nun mein Fehler???


----------



## hdi (20. Okt 2011)

Aus der API-Doc:


> public final void execute()
> 
> Schedules this SwingWorker for execution on a worker thread. There are a number of worker threads available. In the event all worker threads are busy handling other SwingWorkers this SwingWorker is placed in a waiting queue.
> 
> *Note: SwingWorker is only designed to be executed once. Executing a SwingWorker more than once will not result in invoking the doInBackground method twice.*




Der _gesamte _Prozess muss in den Worker, und der wird dann nur einmal gestartet. In deinem Fall reicht es wohl nicht, nur doInBackground() und done() zu implementieren. Du brauchst wohl noch process() und publish().

Lies dir die Dokumentation durch, da ist ein Beispiel:
SwingWorker (Java Platform SE 7 )

Ich hoffe, du hast die API-Doc gebookmarked. Ist heiliger als die Bibel, gerade am Anfang!


----------



## hdi (20. Okt 2011)

...und als Nachtrag:

Bevor du demnächst in das nächste Problem mit Threading läufst (in meiner Kristallkugel sehe ich eine ConcurrentModificationException ), lies dir am besten auch mal die ersten paar Artikel zu "Concurrent Programming" auf dieser Seite durch. Du weißt ja jetzt nach dem Lesen des o.g. Artikels, dass du bei Programmen mit einer GUI immer Multi-Threading hast, oder?


----------



## mysticado (20. Okt 2011)

Das ist echt nett von dir, dass du mir so gründlich hilfst. Ich hatte es natürlich auch zuerst selber versucht, doch google wollte mir einfach nicht das anzeigen, was ich eigentlich gebräucht hätte 

Ich muss aber leider zugeben, die ganze SwingWorker Geschichte übersteigt so langsam schon mein (recht überschaubares) Wissen. Das Beispiel zu publish() versteh ich ja nicht mal, haha!

Könntest du also evtl. eine Ausnahme machen, und mit konkret sagen wie ich denn process() und publish() für meinen Fall einbauen könnte? Okay, publish() muss ins doInBackground()....doch wie genau?! Irgendwie versteh ich nicht wirklich wo mein Thread.sleep() Befehl hin muss? Puh...

Danke! 


Edit: Ja, ich habe nun gelernt, dass es bei einer GUI immer um's Multithreading geht und ob du's glaubst oder nicht, ich habe durch deine Hilfe bisher tatsächlich was dazugelernt  Danke !!!


----------



## hdi (20. Okt 2011)

Über publish() schiebst du "Chunks", d.h. Zwischenergebnisse deiner Berechnung auf den EDT. Über publish() wird process() aufgerufen, und dieser Methode werden alle bisher noch nicht verarbeiteten Chunks mitgegeben. In deinem Fall sind die Zwischenergebnisse die Strings, die du appendest. D.h. in deiner doInBackground hast du eine Schleife, mit:

publish(text);
sleep(1000);

Und in der process fügst du diese Chunks in deine TextArea ein per Append. 

Das ganze ist generisch. Davon schon mal was gehört? Das ist dieses <V,K> in der Doc, das du in deinem Code so schön ignoriert hast  Damit kannst du den Datentyp deiner Chunks festlegen. In deinem Falle String. Ob du das für V oder K einsetzen musst, kannst du nachlesen in der Doc. Ist in diesem Beispiel zwar nicht unbedingt nötig, da du auf deinen Chunks auch die toString()-Methode aufrufen kannst.. Aber du solltest es dir gleich angewöhnen, das generisch zu machen.

Versuch's noch mal. Und zeig mir dein Ergebnis. Dann bekommst du von mir die Lösung, sofern du sie nicht gefunden hast. Aber beeil dich, in 10 Minuten läuft Quatsch Comedy Club xD


----------



## hdi (21. Okt 2011)

So ich poste das jetzt mal, weil QCC fängt an. Wenn du die API-Doc aufmerksam gelesen hättest, wäre dir aufgefallen dass das Beispiel dort eigentlich 1:1 das ist, was du brauchst...


```
SwingWorker<Void, String> job = new SwingWorker<Void, String>(){

     @Override
     public Void doInBackground() {
         for(int i = 0; i<10; i++){
             publish(".");
             try{
                 Thread.sleep(1000);
             } catch(InterruptedException e){}
         }
         return null;
     }

     @Override
     protected void process(List<String> chunks) {
         for (String s : chunks){
             output.append(s);
         }
     }

     @Override
     public void done(){
         utilizeVM(); 
     }
 }

job.execute();
```

Wenn du dazu noch Fragen hast, nur zu. Ich werd sie aber vllt erst morgen beantworten (wenn mir kein anderer zuvor kommt)

Gute Nacht!


----------



## mysticado (21. Okt 2011)

Mist, ich bin doch schon soooo nah! Es geht aber net anders, ich brauch deine Hilfe :-/


```
// Starting the VM-Utilize
                output.append("\n >> Utilizing");
                SwingWorker worker = new SwingWorker() {
          protected String doInBackground() throws InterruptedException {
              for(int i=0;i<3;i++){
                  Thread.sleep(1000);
                  publish(".");
              }
            return null;
          }

          protected void process(String a) {
          output.append(a);
                    }
          protected void done() {
            
          }
          
        };
```

Das mit dem Generalisieren habe ich verstanden und normalerweise mache ich das auch, doch es geht mir hierbei nur um 3 Punkte, und eigtl. wollte ich das ohne größere Komplikationen bewerkstelligen, doch am Ende hat es mich die meisten Nerven gekostet, haha.
Wie auch immer, ich hoffe ich kriege nun die Lösung von dir und dann wünsche ich dir auch viel Spaß beim QQC ! Ich denke, ich geh ihn mir nun auch reinziehen, hahaha....
Schönen Abend noch! Ciao!


----------



## mysticado (21. Okt 2011)

Hat geklappt! Danke für die Hilfe! 
Fragen habe ich vorerst nicht...ich werd mir nochmal die javadoc angucken und dazu noch diesen Beispielcode und dann erstmal versuchen selber draus schlau zu werden. Falls nicht, weiss ich ja wo ich hilfsbereite Menschen finden kann 

Danke für die tolle Unterstützung so spät am Abend und gute Nacht!


----------



## mysticado (21. Okt 2011)

Okay, da wäre ich wieder 
Keine Sorge, das Problem von oben haben wir ja gestern erfolgreich gelöst, doch nun habe ich ein weiteres Problem und da es sich (meiner Meinung nach) wieder um ein Thread-Problem handelt, dachte ich hänge ich es hier hinten dran.
Um mein Problem aber zu erklären werde ich ein bisschen weiter ausholen müssen...

Ich muss für ein Praktikum ein Programm schreiben, welches sich alle Rechner im internen Netz anguckt und protokolliert wie viele und welche virtuellen Maschinen auf ihnen laufen. Die Funktionsweise ist in diesem Moment nicht wichtig - es ist nur wichtig zu wissen, dass es klappt an diese Infos ranzukommen  Diese einzelnen virtuellen Maschinen sollen von mir nun unterschiedlich ausgelastet werden, z.B. 
- VM1= 99% cpu , 100% RAM
- VM2= 10% cpu , 50% RAM
- usw.
Wenn man sich den neu angehängten Screenshot anschaut, sollte es vielleicht etwas klarer sein - links ist der Baum aller Rechner und den darauf laufenden VMs. 

Das Problem ist nun die rechte Seite meiner GUI...

Ich soll es so programmieren, dass ich gleichzeitig mehrere VMs auslasten kann, sprich ich soll es möglich machen links im Baum eine VM auszuwählen, rechts individuelle Werte eingeben und die Simulation starten. Wenn ich nun eine andere VM auswähle, soll ich wieder die gleichen Möglichkeiten haben.
Leider bin ich halt absoluter Noob was Swing und GUI betrifft und weiss in diesem Moment nicht mal von wo aus ich starten soll. Ich habe z.Z. noch die Funktionalitäten (die rechte Seite der GUI) und den Baum komplett in einer Klasse - doch das ist wahrscheinlich falsch, oder? Die rechte Seite wird wahrscheinlich eine eigenständige Klasse sein müssen (bzw. ein JFrame?) damit sie jedes Mal neu instanziiert werden kann...
Und wie instanziiere ich sie dann? Jeweils ein Thread? Auch mit SwingWorker???

Puh, viel Text, viele Fragen...viel Spaß damit


----------



## hdi (21. Okt 2011)

Hello again,

Was mir jetzt noch nicht ganz klar ist: Wie funktioniert das Auslasten der VM's? Ist das eine pseudo-mäßige Simulation, in der du einfach irgendwelche Auslastungen in deiner GUI anzeigen lassen sollst (ohne dass da was dahintersteckt), oder geht es wirklich darum, den VM's über's Netzwerk irgendwelche Datenpakete zu schicken, sodass es am betroffenen Rechner tatsächlich zu einer echten Auslastung kommt?

Ich frage deshalb, da Multi-Threading in dem Fall, dass das alles nur gefaked ist, gar nicht nötig ist. Es sei denn, du hast die explizite Vorgabe, auch in diesem Fall trotzdem Multi-Threading zu betreiben.

Falls da wirklich etwas sinnvolles passiert, dann wäre es gut zu wissen was da genau passiert, und wie es passiert. D.h. was machen die Threads? Inwiefern gibt es eine Verbindung zwischen den internen Daten und der GUI?


----------



## mysticado (21. Okt 2011)

Also, um mich kurz zu fassen - das alles ist kein Fake sondern erzeugt tatsächlich Last. Um da jetzt jedochnicht zu tief reinzugehen lass mich nur soviel sagen - die GUI versendet die Befehle eigentlich nur, sie selbst muss aber nichts loggen, auf irgendwelche Antworten warten oder was auch immer.
Die Befehle werden per ssh gesendet und dann übernimmt die VM selbst die ganze Auslastungsgeschichte. Die Textfields dienen in meinem Programm daher nur, dass ich weiss welche Werte ich der VM rüberschicken muss.

Ich denke aber trotzdem das multi-threading nötig sein wird (falls ich das umgehen kann, dann würde ich das sehr gerne machen - die vorgabe wurde mir nicht gestellt  ), da es noch diese lästige Timer-Funktion gibt - Duration of Simulation - welche nach der vorgegebenen Zeitspanne den VM's die kill-Befehle zuschicken soll.
Ich weiss deswegen nicht wirklich wie ich denn einfach zwischen den ganzen VM's im Baum hin und her switchen kann und dabei all die eingegebenen Werte UND die verbliebene Zeit des Timers jeder VM behalten kann?

Puh! Schwere Sache!


----------



## hdi (21. Okt 2011)

Okay.. Also es würde durchaus _ohne_ Multi-Threading gehen. Aber es ist in diesem Fall wirklich besser (heißt auch: leichter) das über mehrere Threads zu machen.

Wegen der GUI: Du brauchst das alles, d.h. die Textfelder usw., nur ein mal. Immerhin werden ja immer nur die Daten von einer VM angezeigt, aber niemals Daten von mehreren VM's gleichzeitig, oder? 

Die eigentlichen Daten, d.h. Auslastung, Timer usw sind in einer internen Struktur gespeichert, zB Klasse "VMData". Und *nicht *direkt in deinen Textfeldern. Deinem Tree verpasst du ein entsprechendes Model mit diesen internen VMData-Daten. Wenn man jetzt auf eine Node klickt, dann befüllst du deine rechte Seite der GUI mit den Daten aus der VMData-Instanz. Und wenn man auf Simulation starten klickt, startest du einen neuen Thread, dem diese VMData-Instanz übergeben wird. Damit hat er alle Infos, die er braucht. Und ja, wenn man jetzt während der Simulation Werte ändern können soll, geht's los mit Synchronisierung.

Aber - fangen wir am besten mal bei dieser VMData an. Hast du denn im Moment irgendeine Klasse, die alle Infos über eine VM und ihre Simulationsdaten speichert?


----------



## mysticado (21. Okt 2011)

Also, ich merke, du bist so nett und möchtest mir tatsächlich helfen und deswegen hier nun auch mein Quellcode - es ist denke ich auch viel einfacher mit einander zu kommunizieren wenn du auch wirklich verstehst, was ich meine, bzw. du es vor dir liegen hast 
Doch keine Sorge ich erwarte jetzt nicht, dass du komplett alles für mich machst. Ich bin dir schon dafür dankbar, dass ich mit dir ein bisschen Brainstormen kann 

Zurück zu meinem Problem - das was mich noch ein bisschen stutzig macht ist die Tatsache, dass nachdem ich auf "Simulate" klicke, der Simulate-Button selbst erstmal ausgegraut wird, in Hintergrund wird dann auch der Timer gestartet, der nach "Duration of Simulation"-Zeiteinheiten die kill-Befehle an die VM schickt (also die Funktion des Stop-Buttons übernimmt, nur eben nach einer vorgegebenen Zeit).
Wenn ich da nun für jede VM einen eigenen Thread erstelle, wie mache ich es denn dann mit dem Ausgrauen des Simulate-Buttons? Geht das genauso mit einer internen Struktur?

Apropos: Eine interne Struktur habe ich bisher noch nicht. Ich habe die übergebenen Werte immer nur in die dementsprechenden Globalen Variablen gespeichert. Mein ganzes Programm ist ja, wie du sehen kannst, in einer einzigen Klasse untergebracht 

Also, erster Schritt...eine neue Klasse für die Werte? Eine Methode reicht dafür nicht?


----------



## hdi (22. Okt 2011)

Wenn du schon sagst, dass alles in einer einzigen Klasse steckt.. Ne du, das tu ich mir nicht an, sorry. Würd eh darauf hinauslaufen dass ich sage "Wegschmeißen, neu machen".

Vergiss erstmal die GUI.. Du brauchst eine Datenstruktur zur Verwaltung deiner Daten. Wenn ich mir deinen Screenshot ansehe würde ich sagen:


```
class VirtualMachine{

   private String ip;
   private long stressDuration;
   private int stressCpuLast;
   private int stressRamLast;
}
```

und


```
class NetworkEntity{

   private String ip;
   private List<VirtualMachine> virtualMachines;
}
```

Solche VirutalMachines bzw NetworkEntities baust du dir zusammen im Zuge der Durchsuchung des Netzwerks. Und die ganzen NetworkEntity-Instanzen (die ja die VM's enthalten) sind dann dein interner Datenbestand. Dafür machste dir am besten nochmal ne eigene Klasse. Die kann der Einfachheit halber meinetwegen die Daten auch statisch anbieten:


```
class Database{

   public static List<NetworkEntity> networkEntities = new ArrayList<NetworkEntitiy>();
}
```

Diese Database zu befüllen ist das erste, was du tust. in deiner main-Methode, da ist mit GUI oder Threads etc. noch gar nix!

So, wenn du das hast, dann sag Bescheid.

(Das ganze wird noch etwas länger dauern.. Aber dafür ist es am Ende sauber, du verstehst es, und kriegst ne 1)


----------



## mysticado (22. Okt 2011)

Ohje, ich trau mich das hier gar nicht zu schreiben, sonst verscheuche ich dich womöglich noch...
Ich verstehe deinen Ansatz, habe die Klassen auch so erstellt wie du es mir gesagt hast - die Idee ist also jeweils eine Database-Instanz zu erzeugen, bzw. die Variable networkEntities zu befüllen, nachdem die Werte ausgelesen wurden.
In der main-Methode habe ich die Auslesefunktion schon drin, sprich ich hab Zugriff auf die Daten, doch.......ich komme einfach nicht drauf, wie ich die Daten nun intern speichern soll 
Geht das einfach so mit:

```
Database db = new Database();
db.networkEntities("192.168.0.1", ...);
```

Ich muss ehrlich zugeben, ich habe noch nie mit solch verschachtelten Klassen rumhantiert und bin deswegen etwas durcheinander  Bitte verlier nicht die Geduld mit mir - es wäre toll, wenn du mir das in 3 Sätzen erklären könntest 
Ich bin bereit zu lernen und willl unbedingt diese 1 

P.S. Das ist falsch so, oder? 

_VMDatabase.networkEntities.add(new NetworkEntity("192.168.0.1",new List<VirtualMachine>()))_


----------



## hdi (22. Okt 2011)

```
VMDatabase.networkEntities.add(new NetworkEntity("192.168.0.1",new List<VirtualMachine>()))
```

Fast 

```
List
```
ist ein Interface, du kannst davon keine Instanz erzeugen. Du musst eine Klasse wählen, die das Interface implementiert. z.B. ArrayList:


```
VMDatabase.networkEntities.add(new NetworkEntity("192.168.0.1",new ArrayList<VirtualMachine>()))
```

Das wäre schon mal korrekt, sofern du natürlich einen entsprechenden Konstruktor definiert hast. Wenn du eine NetworkEntitiy so erzeugst, brauchst du halt eine Methode á la addVirtualMachine. Alternativ liest du erst alle VM's aus, erstellst eine Liste davon, und erzeugst dann die NetworkEntitiy, und übergibst diese Liste (an statt eine leere Liste).


----------



## mysticado (22. Okt 2011)

Also, als erstes - NEIN, es klappt noch net 
Ich muss schon sagen, Du hast mich ganz schön durcheinander gebracht mit all den Entitys und VirtualMachines  Aber ich seh schon ein Licht am Horizont...am Ende des Tages werde ich um einiges schlauer sein (was Java anbelangt zumindest  ).
Also, ich bin so weit gekommen:


```
public class VMDatabase {
    public static List<NetworkEntity> networkEntities = new ArrayList<NetworkEntity>();


}

public class NetworkEntity {
	   private String ip;
	   private List<VirtualMachine> virtualMachines;
	   
	   public void addVM(VirtualMachine vm){
		   virtualMachines.add(vm);
	   }

	}

public class VirtualMachine {
	private String ip;
	private long stressDuration;
	private int stressCpuLast;
	private int stressRamLast;
	private boolean webTraffic;
	private int activeUsers;
	private String templatePath;

	public VirtualMachine(String ip, long stressDuration, int stressCpuLast,
			int stressRamLast, boolean webTraffic, int activeUsers,
			String templatePath) {
		this.ip = ip;
		this.stressDuration = stressDuration;
		this.stressCpuLast = stressCpuLast;
		this.stressRamLast = stressRamLast;
		this.webTraffic = webTraffic;
		this.activeUsers = activeUsers;
		this.templatePath = templatePath;
	}
}
```

Meine Klasse mit main-Methode versucht nun folgendes auszuführen:


```
VMDatabase.networkEntities.add(new NetworkEntity("192.168.0.1",new ArrayList<VirtualMachine>().addVM(new VirtualMachine("192.168.0.5", (long) 1000, 10, 128, true, 5))));
```
 was natürlich wieder falsch ist. Mensch, da hab ich mir aber was eingehandelt


----------



## hdi (22. Okt 2011)

In deiner Network-Entitiy Klasse hast du die Liste noch nicht instantiiert. Das gibt eine NullPointerException wenn du da was per add() einfügen willst. Du musst der Variablen für die Liste natürlich auch eine Liste zuweisen. Das geht so wie auch bei der Database. Außerdem fehlt noch ein Konstruktor, um zB die ip festzulegen. Am besten so:


```
public class NetworkEntity {
       private String ip;
       private List<VirtualMachine> virtualMachines = new ArrayList<VirtualMachine>();

       public NetworkEntity(String ip){
           this.ip = ip;
       }
       
       public void addVM(VirtualMachine vm){
           virtualMachines.add(vm);
       }
}
```

Hier die Reihenfolge wie das ablaufen muss:

1) Du suchst die IP eines Rechner im Netzwerk, und erstellt eine NetworkEntity
2) Du suchst alle VM's auf diesem Rechner mit deren IP usw, und erstellst jeweils eine VirtualMachine, die du mit addVM() der obigen NetworkEntitiy hinzufügst
3) Wenn alle VM's zu der NetworkEntitiy geaddet wurden, fügst du die NetworkEntity in die Datbase ein. Das sieht dann nur so aus:


```
VMDatabase.networkEntities.add(entity);
```

Übrigens musst du ein Zahlen-Literal im Code nicht explizit nach long casten. Das Casting von int nach long kann implizit vorgenommen werden. Wenn du betonen willst, dass es sich um ein long handelt, hänge ein kleines oder großes "L" hinter die Zahl: 
	
	
	
	





```
1000L
```

PS: Gewöhn dir an, Fehlermeldungen zu lesen! Egal ob Compile- oder Laufzeitfehler, die sind meist recht klar ausgedrückt.


----------



## mysticado (22. Okt 2011)

Aaah, nun ist es mir viel klarer. Ich dachte ich muss komplett die Methoden so übernehmen wie du sie oben beschrieben hattest. Natürlich weiss ich, dass man Konstruktoren usw. braucht, doch da du hier definitiv der bessere Java-Checker bist wollte ich einfach nichts sagen und nahm alles so hin wie es da stand  Sorry, für meine Beschränktheit!

Alles klar, es hat geklappt - alle Rechner konnten so in die Database eingespeichert werden. Mein nächster Schritt ist dann das Erzeugen des JTrees, oder?
Eine weitere Frage - die Database ist ja nun in der Memory gespeichert - bleibt sie denn so drin so lange die GUI läuft? Sprich, werde ich dann mit den jeweiligen Threads immer die selben Werte aus der DB rauslesen können?


----------



## hdi (22. Okt 2011)

Alles, was irgendwie referenziert ist, bleibt auch für die gesamte Laufzeit des Programms im Speicher, ja.



> Mein nächster Schritt ist dann das Erzeugen des JTrees, oder?



Genau:


```
public class NetworkEntitiyTree extends JTree{

    public NetworkEntitiyTree(){
        setModel(new MyModel());
    }

    private static class MyModel extends DefaultTreeModel{

        // ...
    }  

}
```

Weißt du, wie man ein TreeModel implementiert? Du musst in der inneren Klasse diverse Methoden überschreiben (aus der Klasse DefaultTreeModel bzw dem Interface TreeModel), und dabei auf deine Database zugreifen, sodass der JTree weiß, wie er aussehen muss.


----------



## mysticado (22. Okt 2011)

Ist vielleicht ein wenig unschön, aber es klappt:


```
javax.swing.tree.DefaultMutableTreeNode treeNode1 = new javax.swing.tree.DefaultMutableTreeNode("Cluster");
        javax.swing.tree.DefaultMutableTreeNode treeNode2;
        javax.swing.tree.DefaultMutableTreeNode treeNode3;

        for(int i=0;i<VMDatabase.networkEntities.size();i++){
            treeNode2 = new javax.swing.tree.DefaultMutableTreeNode("Node #"+(i+1)+" ["+VMDatabase.networkEntities.get(i).getIP()+"]");
            treeNode1.add(treeNode2);
                for (int j=0;j<VMDatabase.networkEntities.get(i).virtualMachines.size();j++) {
                    if(VMDatabase.networkEntities.get(i).virtualMachines.get(j).getHostIP().equals(VMDatabase.networkEntities.get(i).getIP())){
                              treeNode3 = new javax.swing.tree.DefaultMutableTreeNode("VM ["+VMDatabase.networkEntities.get(i).virtualMachines.get(j).getIP()+"]");
                              treeNode2.add(treeNode3);
                    }
                }
        }
        tree.setModel(new javax.swing.tree.DefaultTreeModel(treeNode1));

        expandAll(tree); // Tree wird expandiert!

        tree.addTreeSelectionListener(new TreeSelectionListener() {
                 public void valueChanged(TreeSelectionEvent evt) {
                     String tempIp = evt.getPath().toString().split("VM")[1];
                     String endIp = tempIp.substring(tempIp.indexOf("[")+1, tempIp.length()-2);
                     ipAddr = endIp;
                     ueberschrift.setText("// VM: "+endIp);
                  }
          });
```

 Da ich meine GUI mit Netbeans mache, wurde mir solch eine Vorgabe für das JTree erstellt und daher habe ich es diesmal nicht so gemacht wie du es mir im letzten Kommentar geschrieben hattest...aber wie gesagt, es läuft ja und das ist erstmal das Wichtigste, hihi.
Was sagst du zum SelectionListener? Ist es so wie du es machen würdest, oder es dir vorgestellt hast? Mein nächster Schritt ist ja nun, dass ich per Selection jeweils einen neuen Thread starte, oder?

p.s. mehr als die 3 treenodes werde ich ja nicht brauchen, da ich ja nur rechner und deren vms anzeigen lassen muss.


----------



## hdi (22. Okt 2011)

Nein, das ist Quatsch!

Dein Tree hat jetzt wieder keine Verknüpfung zu den eigentlichen Daten. Deine Nodes sind nur Strings (IP-Adresse), das bringt dir nix. Deine Nodes müssen die tatsächlichen NetworkEntity bzw VirtualMachines kapseln, damit du ordentlich damit arbeiten kannst.

Meinetwegen benutze ein DefaultTreeModel, aber steck in die Nodes nicht nur die IP, sondern die gesamte NetworkEntity bzw die VM's. Und nimm diese Variablen "treeNode1,2,3" usw raus! Sonst funktioniert das nur wenn genau 3 Rechner gefunden wurden. Was machts du, wenn 4 gefunden werden? Lösch die Variablen, du brauchst nur einen Root-Knoten, und in den fügst du dynamisch über eine Schleife alle NetworkEntities aus denier Datenbank ein:


```
new DefaultMutableTreeNode(VMDatabase.networkEntities.get(i));
```

Dann überschreibst du in der NI- und VM-Klasse jeweils die to-String Methode, damit er dir die IP anzeigt:


```
@Override
public String toString(){
    return "[" + getClass().getSimpleName() + "]" + " " + ip;
}
```

Die Anzeige ist dann fast gleich, aber der Unterschied ist dass du über die Nodes tatsächlich an die internen Daten kommst.


----------



## mysticado (22. Okt 2011)

ja warte mal, ich dachte meine Treenodes1,2,3 seien nur variablen für die Tiefenlevels, sprich treenode1 = root, treenode2 = rechner, treenode3 = vms....so dass ich dann dadurch die hierarchie erhalten würde. ich wusste nicht, dass sich die treenode variablen in meinem fall auf konkret einen rechner beziehen?

ja und wie mach ich das dann mit den VMs die jeweils den networkentitys zugeordnet werden sollen? oben meintest du ja ich solle eine schleife machen und alles an root hängen - dann hätte ich aber diese hierarchie nicht, oder?


----------



## hdi (22. Okt 2011)

Die Hierarchie musst du halt zusammenbauen:


```
DefaultMutableTreeNode root = new DefaultMutableTreeNode();

for(NetworkEntity networkEntitiy : VMDatabase.getNetworkEntitites()){
   DefaultMutableTreeNode entityNode = new DefaultMutableTreeNode (networkEntitiy);
   for(VirtualMachine vm : networkEntitiy.getVirtualMachines()){
      entityNode.add(new DefaultMutalbeTreeNode(vm));
   }
   root.add(entityNode);
}
```

Zu den Variablen: Das ist falsch, du brauchst wie gesagt nur eine für den root-Knoten. An alles andere kommst du ja über diesen Root, da ja da alles drin steckt. Wenn du für einzelne Knoten im Tree nochmal Variablen abspeicherst ist das redundant, d.h. umständlich und fehleranfällig.

... und in deinem SelectionListener prüfst du nun mit 
	
	
	
	





```
instanceof
```
, ob der ausgewählte Knoten eine NetworkEntitiy ist oder eine VirtualMachine. Wenn ersteres, musst/kannst du ja rechts in der GUI gar nix anzeigen, außer ne Meldung "Bitte eine VM auswählen". Und wenn es eine VM ist, dann kannst du den Node casten, alle Infos da raus holen und deine Textfelder usw mit den Werten befüllen:


```
Object data = node.getUserObject();
if(data instanceof NetworkEntitiy){
   // ...
}
else{
   // DAtena nzeigen
}
```


----------



## mysticado (22. Okt 2011)

Du wirst es nicht glauben, aber die Aha-Effekte dauern bei mir schon seit gestern an! Ich wäre nieeee auf die Idee gekommen einige Sachen so zu machen wie Du es mir hier beschrieben hattest. Das Überschreiben der toString(), oder das instanceOf() lassen mich immer noch über all' die verschiedenen Möglichkeiten in Java staunen 

Eine Frage hätte ich jedoch noch zum Listener, dann können wir eeeendlich auf die eigentliche Frage (Threads!) übergehen: da mir das node.getUserObject() nicht wirklich klar war (ich bekomme ja einen Klickevent zurück und keinen node) und ich ja theoretisch nur wissen muss welches der Knoten angeklickt wurde, um da draus dann die Infos rauszuholen ob es ein NE, oder eine VM war, könnte ich es doch auch so machen oder:


```
tree.addTreeSelectionListener(new TreeSelectionListener() {
                 public void valueChanged(TreeSelectionEvent evt) {
                     Object data = evt.getSource();
                            if(data instanceof NetworkEntity){
                                 ueberschrift.setText("Bitte VM auswählen");
                            }
                            else{
                                ueberschrift.setText("Korrekt");
                            }
                  }
          });
```

Leider bekomme ich hierbei aber immer die Antwort "Korrekt" zurück, was natürlich nicht richtig ist. Ist es denn falsch es mit .getSource() zu machen?! 
Ich würde es auch gerne anhand deiner Methode lösen, nur musst du mir wieder einen klitzekleinen Stoß geben, damit ich auch verstehe aus welchem Knoten du deinen UserObject rausholen möchtest


----------



## hdi (22. Okt 2011)

*API-Doc lesen!*



> source - The object on which the Event initially occurred.



Das ist nicht der Knoten, sondern der eigentliche JTree. Und in der API-Doc von TreeSelectionEvent sind auch einige Methoden gelistet, u.a.: 
	
	
	
	





```
getNewLeadSelectionPath()
```
 Darüber musst du dir den angeklickten Node holen. Und wenn du den Node hast, dann machst du das was ich gepostet hab: über getUserData() die internen Daten rausholen, casten, und schon hast du deine VirtualMachine.

Es ist klar, dass du die API nicht ausnwendig kannst. Aber dazu gibt es die Doc. Wenn du mit einer Klasse arbeitest, dann lies dir die Doc durch, schau dir alleMEthoden an usw. Genau dann kommst du nämlich auch darauf, wie du es am besten machen kannst.


----------



## mysticado (22. Okt 2011)

Okay, passt! Die Daten bekomme ich daher nun, und es ist auch nicht wirklich schwer sie nun in die TextFields einzulesen...
Das Problem, das ich nun habe ist eben für jede VM einen Thread zu starten.

Wie muss der Thread aufgebaut werden? Genauso wie oben, per SwingWorker?

Edit:
Okay, nach all' den Schönheitskorrekturen hab ich mir meinen Code nochmals angeguckt und bin jetzt völlig durcheinander. Okay, ich kann jetzt alle im Netz befindlichen Rechner und VMs auslesen und sie zu einem Baum zusammenfügen - die jeweiligen VMs haben in dem Moment aber noch keine Werte.
Das ist ja klar, denn die Werte kommen ja erst mit der Veränderung der JTextfields!

Jetzt stellt sich mir die Frage - wie kann ich das denn machen das eventuelle Änderungen in den TextFields in der VM gespeichert werden beim Klicken auf einen anderen Node im Tree???? Und wie kombiniere ich das alles mit der Thread-Geschichte?

Jetzt erst kommt wohl der Hammer, oder? Denn alleine komm ich hier echt nicht mehr weiter, jetzt wo meine (damals noch laufende) GUI komplett zerschossen ist


----------



## hdi (22. Okt 2011)

> wie kann ich das denn machen das eventuelle Änderungen in den TextFields in der VM gespeichert werden beim Klicken auf einen anderen Node im Tree????


Du weißt ja, wie du auf das Anwählen von Nodes im Tree reagieren kannst: Mit einem SelectionListener. Also wo genau ist nun das Problem? Deine Frame-Klasse enthält eine Referenz auf den Tree, sowie auf die Textfelder, oder? Also fügst du dort einen neuen SelectionListener an den Tree, in dem du die Werte der Felder ausliest, und in der Node änderst. Welche Node ausgewählt ist kannst du ja auch über den Tree abfragen.


----------



## mysticado (22. Okt 2011)

Tja, manchmal sieht man den Wald vor lauter Bäumen nicht. Ich hab jetzt einfach einen weiteren SelectionListener implementiert, der die Werte aus den Feldern holt und....so, jetzt brauch ich wieder Hilfe 
Der ActionListener reagiert ja nur auf einen Klick, nachdem man auf einen anderen Knoten geklickt hat. Wie aber behalte ich am Besten die Info welcher Knoten als letzter aktiv war (ich hätte jetzt sonst eine globale Variable "String letzteAuswahl" erstellt und dort immer den aktuellen Knoten reingespeichert)?
Oder vielleicht so: _evt.getOldLeadSelectionPath().toString()_ ?

Und ausserdem hab ich noch ein kleines Problem mit dem Abspeichern der neuen Werte der VirtualMachine. Ich weiss leider nicht mal genau wie ich jetzt an die VirtualMachine selbst rankomme? DefaultMutableTree oder Object liefern mir nicht wirklich Methoden mit welchen ich konkret die VM-Werte ansprechen und abändern könnte, oder? getLastSelectedPathComponent sagt mir wohl welche VM ausgewählt war...doch wie ich konkret die Werte nun ändern kann weiss ich net 
Ich habe schon Setter-Methoden in die VM Klasse reingeschrieben, jetzt muss ich nur noch wissen wie ich da drauf komme


----------



## hdi (23. Okt 2011)

> Oder vielleicht so: _evt.getOldLeadSelectionPath().toString()_ ?


Das mit dem getOldLeadSelectionPath() stimmt, steht ja unmissverständlich in der API-Doc. Das mit dem toString() ist allerdings Blödsinn. Was willst du mit der String-Repräsentation dieses Objekts anfangen? Über den Path kommst du an die eigentliche Node. Siehe Methoden von TreePath. 



> Ich weiss leider nicht mal genau wie ich jetzt an die VirtualMachine selbst rankomme? DefaultMutableTree oder Object liefern mir nicht wirklich Methoden mit welchen ich konkret die VM-Werte ansprechen und abändern könnte, oder?


Richtig. getUserData() liefert dir zwar durchaus die VM, allerdings weiß das der Compiler nicht. Für ihn ist es lediglich Object. Liiegt daran, dass du dich ja gegen meinen Ratschlag für ein eigenes TreeModel entschieden hast. Ist halt jetzt etwas umständlicher.. Wie schon gesagt musst du nun casten. Dabei sagst du dem Compiler explizit, dass dieses "Object" in Wahrheit eine VM ist, und er seine "Sicht" auf dieses Objekt entsprechend umwandeln soll, sodass dir die Methoden aus der Klasse VM verfügbar gemacht werden. Casten geht so:


```
VirtualMachine vm = (VirtualMachine) object;
```

Und nun hast du über die Variable vm Zugriff auf deine ganzen Setter und Getter.


----------



## mysticado (23. Okt 2011)

Ach wie schön, nun habe ich mein Programm wieder auf den gleichen Punkt gebracht wie zu Anfang dieses Beitrages, nur dass meine Werte nun viel besser gespeichert sind und der Zugriff auf sie effizienter verläuft!
Ich muss echt noch einmal an dieser Stelle meinen Dank aussprechen für die bisherige Mühe - wir sind auch schon fast am Ziel 

Es bleibt nur noch die Frage der Threads, bzw. wie ich zu jedem Node aus dem Tree jeweils einen Thread starten kann, nachdem auf "Simulate" geklickt wurde.

Ich habe eine [c]startButtonActionPerformed()[/c] Methode in meiner GUI, welche zuerst prüft ob bei "WebTraffic" das Häkchen dran gemacht wurde und ob in der Browse-Zeile auch eine Datei reingeladen ist.
Danach wird die Methode [c]utilizeVM()[/c] aufgerufen, welche eine VM-bezogene Prozedur durchführt. 
Am Ende läuft dann noch der [c]javax.swing.Timer[/c], welcher den ganzen Thread abbrechen sollte entweder nachdem die Zeitspanne abgelaufen ist, oder der User selbst auf "Stop" geklickt hat.

Theoretisch müssten ja alle drei Befehle aus dem jeweiligen Thread gestartet werden können, da sie ja eigentlich alle direkt bezogen sind auf die VM.
Daher müsste ich jetzt nur noch wissen wie es denn am geschicktesten wäre den Thread auch wirklich zu starten? Muss der SwingWorker wieder herhalten?


----------



## hdi (23. Okt 2011)

Also mit SwingWorker hat das jetzt nichts zu tun. Einen SwingWorker brauchst du nur, wenn der Thread Zugriffe auf die GUI macht. Aber ist ja nicht so, oder? 

Wie und wo wir die Threads integrieren hängt jetzt davon ab, was die denn so benötigen um ihre Augabe erledigen zu können. Von dieser "Datei" die du da hast wusste ich bislang nichts. Was steht da drin, was hat die mit der Simulation zu tun?


----------



## mysticado (23. Okt 2011)

Aber ich dachte, die GUI muss verfügbar sein, denn ich möchte ja, dass ich z.B. die eine VM starte (was ja länger dauern kann, abhängig von der übergebenen Länge der Simulation), und während diese eine VM simuliert möchte ich auf eine andere switchen können, um auch sie starten zu können.
Das sind doch GUI-Zugriffe und daher dachte ich, brauche ich wieder den SwingWorker, oder?

Und was diese Datei anbelangt ist die ganze Geschichte eigentlich halb so wild. Bei der Datei handelt es sich um eine template Datei die für die Netzwerkauslastung wichtig ist. Die Netzauslastung stoße ich nämlich mit einem externen Programm an und dieses braucht in der Kommandozeile nunmal diese template Datei, welche durch eine XML-Struktur aufgebaut ist. Im Großen und Ganzen kann ich das alles aber checken noch bevor der Thread gestartet wird, also können wir das einfach mal vergessen. 

Der Thread selbst führt wie gesagt nur die Methode utilizeVM() aus, welche im Endeffekt nichts anderes macht als über [c]Runtime.getRuntime().exec[/c] sich per ssh auf der VM einzuloggen und sie auszulasten. Sprich, mit der GUI selbst hat das ja nichts mehr zu tun.
Die zweite Sache die der Thread dann noch machen müsste ist eben für jede VM den Timer ablaufen zu lassen. Nachdem der Timer dann abgelaufen ist, logge ich mich wieder per ssh in die VM und stoppe alle Auslastungsbefehle.

Das ist alles was ich brauche...jede VM soll für sich nach dem Drücken auf den "Simulate"-Button einen Timer starten und die Auslastung anstoßen und nichts mehr. Eigentlich sollte das ja dann nicht mehr so schwer zu bewerkstelligen sein, oder?


----------



## hdi (23. Okt 2011)

> Aber ich dachte, die GUI muss verfügbar sein, denn ich möchte ja, dass ich z.B. die eine VM starte (was ja länger dauern kann, abhängig von der übergebenen Länge der Simulation), und während diese eine VM simuliert möchte ich auf eine andere switchen können, um auch sie starten zu können.
> Das sind doch GUI-Zugriffe und daher dachte ich, brauche ich wieder den SwingWorker, oder?


SwingWorker brauchst du nur, wenn du GUI-Zugriffe aus einem externen Thread hast. Wenn du in deiner GUI rumklickst dann macht das der EDT. Also sowieso schon der richtige Thread. SwingWorker bräuchtest du nur an den Stellen, wo der neu gestartete Simulations-thread etwas an der GUI verändert. Aber das ist nicht vorgesehen, oder?


> Und was diese Datei anbelangt ist die ganze Geschichte eigentlich halb so wild. Bei der Datei handelt es sich um eine template Datei die für die Netzwerkauslastung wichtig ist. Die Netzauslastung stoße ich nämlich mit einem externen Programm an und dieses braucht in der Kommandozeile nunmal diese template Datei, welche durch eine XML-Struktur aufgebaut ist. Im Großen und Ganzen kann ich das alles aber checken noch bevor der Thread gestartet wird, also können wir das einfach mal vergessen.


Vergessen können wir das nicht. Pack das auch noch in deine VM-Klasse rein als Instanz-Variable mit Getter & Setter.



> Der Thread selbst führt wie gesagt nur die Methode utilizeVM() aus, welche im Endeffekt nichts anderes macht als über Runtime.getRuntime().exec sich per ssh auf der VM einzuloggen und sie auszulasten. Sprich, mit der GUI selbst hat das ja nichts mehr zu tun.
> Die zweite Sache die der Thread dann noch machen müsste ist eben für jede VM den Timer ablaufen zu lassen. Nachdem der Timer dann abgelaufen ist, logge ich mich wieder per ssh in die VM und stoppe alle Auslastungsbefehle.
> Das ist alles was ich brauche...jede VM soll für sich nach dem Drücken auf den "Simulate"-Button einen Timer starten und die Auslastung anstoßen und nichts mehr. Eigentlich sollte das ja dann nicht mehr so schwer zu bewerkstelligen sein, oder?



Nein, das ist noch recht easy. Aber vorhin meintest du noch:


> welcher den ganzen Thread abbrechen sollte entweder nachdem die Zeitspanne abgelaufen ist, *oder der User selbst auf "Stop" geklickt hat.*


Das wiederum macht das ganze deutlich komplizierter. 

Du... ich schreib an diesem Post seit über 30 Minuten. Es ist sau kompliziert, dir das zu erklären.. Wenn du nix dagegen hast, würde ichd ich bitten mir deinen gesamten Code zu geben. (Lad ihn irgendwo hoch). Ich msus das direkt da reincoden, sonst reden wir noch stundenlang weiter und ich weiß nie ob du das jetzt so eingebaut hast wie ich meinte..

Am Ende werde ich dir alle Fragen dazu beantworten, aber ich glaub das kriegen wir jetzt wesentlich schneller hin wenn ich dir dieses Programm einfach runterschreib...Schau mal wie viel Text hier schon in dem Topic steht.. Das geht noch über 10 Seiten so weiter wenn wir so weitermachen.


----------



## mysticado (23. Okt 2011)

Also,
ich habe es währenddessen ein bisschen selber probiert und meine, dass es so jetzt evtl. sogar klappen könnte - belehre mich bitte des Besseren 
Ich habe einfach noch einen SwingWorker drum rum gemacht (ja, ich weiss, gerade hattest du mir geschrieben, dass das so nicht gedacht ist - ich wollt's einfach mal probieren  ) und irgendwie benimmt sich das Programm jetzt so wie ich es mir dachte. Oder bilde ich es mir ein?

Ja, die Idee, dass du dir am Besten mal selber den Code anguckst ist gar nicht mal so schlecht, nur wollte ich dich nicht um sowas bitten. Jedenfalls schick ich dir jetzt gleich ne pn....und bitte bitte, sei nicht zu kritisch wenn du dann siehst was ich da so alles fabriziert habe 
Der Code braucht ja auch nicht schön sein - es soll einfach nur funktionieren 

Die wichtigsten Sachen findest du im [c]startButtonActionPerformed[/c] , [c]stopButtonActionPerformed[/c] und [c]createTree[/c] .

Wenn du die GUI dann live siehst, wird dir vielleicht einiges klarer, allein was die Funktionalität der einzelnen Dinger betrifft...

Also, danke nochmals!!!!!!!!!!!!!!!!


----------



## hdi (23. Okt 2011)

> und irgendwie benimmt sich das Programm jetzt so wie ich es mir dachte. Oder bilde ich es mir ein?


So, wie du das formulierst ("irgendwie") wahrscheinlich schon..  Ich werd's mir später oder morgen ansehen.


----------



## mysticado (27. Okt 2011)

Don't forget me please!!!!


----------



## SlaterB (27. Okt 2011)

muss man alles bisherige wissen/ lesen oder kannst du nochmal neu zusammenfassen was das Thema/ noch offene Punkte sind?


----------



## mysticado (27. Okt 2011)

Haha, ja es sollte machbar sein, dass du quer mit einspringst  
Die ersten zwei Seiten haben eigentlich nicht wirklich was mit meinem eigentlichen Problem zu tun, sondern behandelten ein paar hässliche Programmierfehler von mir. Nun da wir sie in den ersten 2 Seiten des Beitrag bearbeitet haben, können wir uns auch endlich meinem echten Problem widmen - den von mir benötigten Swing-Threads 
Im Großen und Ganzen geht es darum, dass ich eine GUI habe, welche aus dem LAN alle Rechner scannt, und die auf ihnen befindlichen VM's. An dieser Stelle ist es unwichtig zu sagen wie das geht - es funktioniert jedenfalls, was bedeutet, dass wir die Daten für meine GUI haben.
Nun habe ich auf der linken Seite meiner GUI einen JTree erstellt welcher die Rechner + die darauf befindlichen VMs anzeigt.
Das Ziel ist es nun, auf einer der VMs klicken zu können, ein paar Werte auf der rechten Seite der GUI zu verändern und dann per "Simulate"-JButton einen Befehl an die jeweilige VM zu schicken. Das sollte nun in einen Thread verpackt werden, um es mir zu ermöglichen gleichzeitig noch weitere VMs auszuwählen, um auch denen diese Befehle schicken zu können.
Das hört sich vielleicht simpel an, doch alleine kam ich da leider nicht weiter 
hdi war jedenfalls so nett und hat mir mühselig Schritt für Schritt erklärt gehabt bisher, und ich muss sagen - es hat gefruchtet  Wenn ihr mir noch mit dem letzten Schritt (Threads) helfen könntet wäre ich super zufrieden und auch mit der 1 müsste es dann klappen 
Gruß!


----------



## hdi (27. Okt 2011)

@mysticado

Ich hab kurz reingekuckt. Was mir sofort aufgefallen ist, ist dass du in der NetworkEntity Klasse die Liste der VMs static deklarierst hast. Ich weiß nicht wie du darauf gekommen bist, denn ich hab dir den Code ja ganz konkret gegeben. Also das ist falsch... Die Liste darf nicht static sein! Sonst hast du in jedem Netwerk-Knoten ALLE VM's drin, und nicht nur die zu der jeweiligen NetworkEntity gehörigen VMs.

Ansonsten muss ich ehrlich sagen hab ich dann das ganze schnell wieder geschlossen als ich deine GUI Klasse gesehen habe.. Wann begreifen die Leute endlich, dass diese GUI Builder vorallem am Anfang kontraproduktiv sind. Und anschauen will sich sowas keiner. 

Deswegen kann ich dir jetzt auch keinen konkreten Code mehr geben, zumindest nicht an bestimmte Zeilen in deinem Programm einbauen. Denn wie was wo genau gemacht wird hängt davon ab wie deine GUI im gesamten funktioniert. Da gibt's allerhand zu beachten. zB inwiefern müssen sich die Werte die in der GUI angezeigt werden mit der Simulation synchronisieren. Was passiert, wenn du einfach 5x hintereinander auf Start drückst? Was passiert, wenn du auf Stopp drückst obwohl gar keine Simulation läuft? Über solche Dinge solltest du dir Gedanken machen.

Wie es jetzt im Groben mit den Threads weitergeht:
Du musst beim Start einen neuen Thread starten, der mit der VM-Instanz verknüpft ist. zB kannst du dafür in der VM-Klasse das Interface Runnable implementieren,und starten kannst du die Simulation dann über


```
new Thread(vm).start();
```

In die run-Methode der VM-Klasse kommt dann die Simulation rein. Du haust deinen Befehl raus, und betrittst dann eine Schleife die die Zeit runterzählt.Sobald sie abgelaufen ist sendest du den Befehl um die Auslastung zu beendne. Da du das ganze jederzeit über "Stopp" beenden können willst, musst du eine entsprechende Variable anlegen, zB "running". Diese Variable wird in o.g. Schleife jedes mal geprüft. Bei Klick auf Stopp setzt du sie auf flase, das bekommt dann deine Simulation mit und verlässt die run-Methode. Also so in etwa:

```
@Override
public void run(){
    running = true;
    // start auslastung über dein externes programm
    while(running && restZeit > 0){
         // restzeit verringern um sleepWert (evtl zu sekunden runterrechnen, denn sleepWert ist in ms)
         // Kurze Pause via Thread.sleep(sleepWert);
    }
    // stop auslastung über programm
    running = false;
}
```
 
D.h. das Programm läuft solange bis die eingestellte Zeit vorbei ist oder bis der User running über die GUI auf false setzt. Für das Running machst du dir halt einen Getter und Setter, damit du in der GUI weißt ob grad ne Simulation läuft, und damit ob man auf Start oder Stopp drücken kann. Also zB


```
// Start drücken
if(vm.isRunning() == false){
    new Thread(vm).start(); 
}
else{
   // läuft bereits
}
```


```
// Stopp drücken
if(vm.isRunning() == true){
    vm.setRunning(false);
}
else{
    // simulation läuft gar nicht
}
```

wenn du das so machst ist esnur wichtig dass die oben genannte "sleeptime" für den Thread-Loop nicht zu lang ist.. Das ganze ist etwas schlampig gemacht. Aber ich glaub das reicht dir erstmal.. 

Wichtig ist dann noch, dass du synchronisieren musst. Wenn du's so machst wie grad beschrieben reicht es wenn du das running einfach volatile machst:


```
private volatile boolean running;
```

Ich hoffe du hast verstanden was ich meine. Die Simulation ziehst du halt in die VM-Klasse rein, also in die run-Methode. und über dieses "running" kannst du ermitteln ob grad ne Simulation läuft oder nicht und die jederzeit beenden.

edit: Wie du siehst hat das alles nix mit SwingWorker zu tun. Du startest einen ganz normalen neuen Thread, siehe die Zeile oben. Ich hab dir jetzt schon zwei mal ausführlich beschrieben, wann du SwingWorker brauchst und wann nicht.. Du musst die Antworten etwas aufmerksamer lesen.. Dann passiert es dir auch nicht dass du einfach irgendwo ein "static" einbaust, wo es nix zu suchen hat.


----------



## mysticado (29. Okt 2011)

Okay, all die Änderungen habe ich eingeführt (andere waren schon drin, ob Du's glaubst oder nicht  Z.B. die Variable "running" hatte ich schon, und auch die Getter und Setter usw. auch) und nun funktioniert das auch so wie ich mir das gedacht habe.

Eine Sache wäre da aber noch...ich starte ja nun jede VirtualMachine-Instanz als eigenen Thread in dem dann auch der Timer läuft usw. Das ist auch schön und gut so, nur habe ich jetzt folgendes Problem - ich habe in meiner GUI ein JTextField "output" in welchem der jeweils aktuellste Schritt rausgeschrieben wird.
Nun würde ich wollen, dass nachdem der Timer abgelaufen ist, das output-field auch upgedated wird. Dieses müsste ich nun aber aus der VirtualMachine aus aufrufen. Mit [c]new WebserverUtilizer().setOutput("bla")[/c] kann ich das aber nicht bewerkstelligen, da dies ja eine neue Instanz der GUI-Klasse erzeugen würde, oder?
Eventuelle Ideen?


----------



## hdi (29. Okt 2011)

> ich habe in meiner GUI ein JTextField "output" in welchem der jeweils aktuellste Schritt rausgeschrieben wird. Nun würde ich wollen, dass nachdem der Timer abgelaufen ist, das output-field auch upgedated wird.


Wie jetzt.. soll die VM einmalig was reinschreiben, wenn die Simulation fertig ist, oder soll sie _während _der Simulation, d.h. öfters, etwas da reinschreiben?

Im Prinzip gibt es wei Ansätze:
1) Die VM hat eine Referenz auf das Textfeld und schreibst da driekt was rein
2) Die GUI hängst sich als Listener an die VM und schreibt das Textfeld selbst.

Ich würd Variante 2 empfehlen.

D.h. mach ein Interface SimulationListener mit einer Methode outputFired(...), und spendier der VM-Klasse eine Variable zum Speichern solch eines Listeners. Deine GUI implementiert dieses Interface, d.h. diese outputFired-Methode, und hängst sich beim Start der Simulation als Listener an die VM. Und die VM ruft in ihrer run-Methode diese outputFired() auf.

Jetzt ist nur blöd wenn mehrere Simulationen laufen.. Weil ich schätze du willst ja nur den output anzeigen von der VM die grad in der GUI ausgewählt ist. D.h. diese outputFired() braucht wohl als Parameter schon mal die referenz auf die VM, die die Methode aufruft. Aber selbst dann funktioniert das ganze mit dem Textfeld-Update nur wenn zu diesem Zeitpunkt die richtige VM ausgewählt ist..

Das hätte man von vornherein berücksichtigen müssen Ich hatte das Thema öfters angesprochen:



> zB inwiefern müssen sich die Werte die in der GUI angezeigt werden mit der Simulation synchronisieren.





> [...] wo der neu gestartete Simulations-thread etwas an der GUI verändert. Aber das ist nicht vorgesehen, oder?



Du hast leider nie irgendetwas von Output gesagt...


----------



## mysticado (29. Okt 2011)

Ja, ich bin mir bewusst, dass du es schon öfters angesprochen hast, aber ich muss leider zugeben, dass ich nicht dachte, dass sich das kleine Projekt so weit entwickeln würde. Anfangs lief ja auch alles für eine VM, doch als mir gesagt wurde, dass auf einmal mehrere solcher VMs gleichzeitig bedienbar sein sollen, stieß ich an meine Grenzen. Es wäre wohl doch besser gewesen, ich hätte dann alles von Anfang an angefangen zu programmieren, statt den damals bestehenden Code abzuändern, doch aus Fehlern lernt man ja bekanntlich. Und du merkst, so viele Programmierprojekte hatte ich in meinem Leben noch nicht 

Übrigens, jede VM soll den Output ausgeben, wie z.B. "VM Nr. X - Simulation beendet".
Über die StopButtonActionPerformed geht sowas ja leicht. Das einzige Problem ist eben, sowas aus der VirtualMachine Klasse zu triggern nachdem der Timer abgelaufen ist. Aber ich probiers mal mit dem interface-Ansatz! Drück mir die Daumen 
Für weitere Tipps bin ich immer offen 

Edit: Vielleicht ist es doch einfacher es mit der Referenz zu machen, da jede VM ihren Text eingibt...
Was genau hast du denn gemeint mit Referenz?

Edit2: Also ich weiss schon was Referenzen sind (nur um Missverständnisse aus der Welt zu schaffen  ), nur war's mir unklar wie ich das denn mit diesem JTextField machen kann, da ich den Inhalt immer abändere mit [c]textfield.append("bla")[/c] ...und da weiss ich leider nicht wirklich, wie ich das aus einer anderen Klasse per Referenz machen kann???


----------



## mysticado (29. Okt 2011)

Einmal brauch ich noch Hilfe...dann bist du mich (höchstwahrscheinlich) los 

Also, ich habe nun in meiner WebserverUtilizer-Klasse noch "implements SimulationListener" hinzugefügt. Das hat dazu geführt, dass in dieser Klasse nun auch die Methode "outputFired()" erschienen ist.
Ich habe ausserdem in meiner VirtualMachine-Klasse eine Variable "String output" welche den rauszuschreibenden String enthalten soll, erstellt. Dieser String wird gesetzt nachdem der Timer abgelaufen ist.
Jetzt fehlt mir nur noch der Schritt wie meine GUI diesen String erhalten kann? Wie kann ich mich denn als Listener dran hängen? Die API liefert ja Millionen an Infos und Beispielen wie ActionListener erstellt werden, doch so konkret wie bei mir hab ich leider nichts finden können 

...dieses eine Mal noch bitte


----------



## hdi (29. Okt 2011)

```
class VM{

    private SimulationListener listener;

    public void setListener(SimulationListener l){
         listener = l;
    }

    // wenn du den Listener benachrichtigen willst:
    listener.outputFired(this, toString() +" has completed");
}
```


```
class GUI implements SimulationListener{

    // beim Starten der Simulation:
    vm.setListener(this);
    new Thread(vm).start();

    @Override
    public void outputFired(VM vm, String output){
          VM selected = (VM) tree.getSelectedValue();
          if(selected == vm){
               textfield.setText(output);
          }
    }
}
```

Ist eigentlich nichts magisches dran, oder?  Du übergibst der vm halt die GUI über setListener, und in der VM wird ja auf genau dieser Referenz die outputFired-Methode aufgerufen, also landest du in der GUI.


----------



## mysticado (6. Nov 2011)

Hi, da wär ich wieder 
also erst nochmal danke für die Erklärung bzgl. der Listener. Ich bin immer noch fasziniert wie einfach das doch geht 
Ich wollte nun erstmal abwarten, um zu sehen was mein Betreuer zur GUI sagt, bevor ich mich wieder hier melde. So, und nun hat er sie sich angeguckt und war überaus zufrieden mit ihr!
Eine Frage hat er mir aber noch gestellt und zwar fragt er sich, ob meine GUI noch folgendes anzeigen könnte: Wenn die Virtuellen Maschinen von einem Rechner auf der anderen migriert werden, müsste theoretisch auch der JTree angepasst werden. Er fragte mich daher, ob ich nicht noch einen Thread einbauen könnte, welcher nach einer bestimmten Zeit den Tree einfach aktualisieren könnte?
Im Großen und Ganzen wäre das ja nicht schwer. Das einzige Problem, dass ich dabei habe ist, dass ja in den einzelnen Nodes meines JTrees die ganzen VM-Infos drin stehen. Sprich, wenn ich die einzelnen Rechner und VM's neu einlesen würde, würde ich alle gesetzten Werte verlieren oder?
Was mich daher (rein theoretisch) interessieren würde: Könnte man irgendwie die Hierarchie neu einlesen und die alten Werte wieder übernehmen?


----------



## hdi (7. Nov 2011)

Du kannst dir vor dem Aktualisieren des Trees alle VM's in einer Liste abspeichern, und nach dem Aktualisieren die Einträge des neuen Trees gegen diese abgleichen. Dafür kannst du die equals()-Methode in der Klasse VM entsprechend implementieren. Wenn dann die neu eingelesne VM einer der alten aus der "Backup-Liste" entspricht (wie gesagt: per equals()), dann setzt du halt die Werte.


```
neueVm.setX(alteVm.getX());
```

Du musst jetzt nur überlegen wie du equals() implementierst. Du musst irgendwie rausfinden wann zwei VMs semantisch gleich sind. Selbe IP?


----------



## mysticado (7. Nov 2011)

Okay, nun habe ich das auch implementiert, doch wie das so ist will natürlich etwas wieder nicht so wie ich es gern hätte.
Mein Code sieht ungefähr so aus:


```
public void refreshTree(){

while(true){
Thread.sleep(60000);
System.out.println("Refreshing tree");

saveOldVMsInList();

deleteOldDatabase();

readInNewDatabase(){
compareVMsFromListWithVMsFromDB};

createTreeFromNewDatabase();
System.err.println("Show number of children: "+node.getChildCount());

tree.validate();
tree.setModel(new javax.swing.tree.DefaultTreeModel(root))
}
}
```

Die Idee war also die, alle 60 Sekunden die alten VMs in die Liste zu speichern und beim erzeugen der neuen Database die alten Werte zu berücksichtigen. Soweit klappt das auch ganz gut (so wie es aussieht - ich bekomme jedenfalls keine errors zurück), nur habe ich jetzt ein paar Probleme!
Zum Einen wird der Baum einfach nicht "refresht", obwohl die validate()-Methode das ja eigentlich sofort initiieren müsste, oder? Aber das könnte auch ein Bug von meiner Seite sein, denn ausserdem habe ich bemerkt, dass wenn ich meine GUI starte und nichts an ihr verändere, eine VM aber migriere, dass dann der Tree nach der Migration (und nach dem Update) korrekt angezeigt wird.
Wähle ich aber ein Element im Tree aus, oder verändere es - dann klappt das Neuzeichnen des Trees nicht mehr. Zudem ist die Ausgabe meines System.err auch ganz komisch.
Zuerst wird alles korrekt angezeigt - Knoten 1 hat 2 Kinder, Knoten 2 hat ein Kind. Dann migriere ich eine VM und bekomme prompt den neuen Wert auch korrekt angezeigt (also, Knoten 1: 2, Knoten 2: 1), doch gleich hintendran wird noch ein Wert ausgegeben, der dann anzeigt Knoten1: 0, Knoten 2: 0. Hä??? Ich verstehe überhaupt nicht wieso auf einmal 2 Werte ausgegeben wurden, zumal dieser Thread doch erst alle 60 Sekunden Sachen rausschreiben müsste?
Hier meine Ausgabe:


> Refreshing tree: 14:10:21
> Refreshing tree: 14:10:22
> Show number of children: 1
> Show number of children: 2
> ...



any ideas?


----------



## hdi (7. Nov 2011)

1) Du brauchst kein neues Model erzeugen. Du musst nur die Daten innerhalb des Models (also dem root) verändern

2) Bei allen Änderungen am Model musst du entsprechende fire-Methoden aufrufen damit der JTree das auch mitkriegt und sich neuzeichnet. Das hatten wir hier schon.

3) Alle Änderungen auf dem Tree müssen vom EDT ausgeführt werden, das sollte dir mittlerweile auch klar sein

edit: Ich würde dir übrigens empfehlen den Thread in einem schnelleren Intervall laufen zu lassen, allerdings nicht jedes mal das gesamte Model auseinander nehmen und neubauen, sondern nur wenn es tatsächlich eine Änderung gab. Den Check auf die Änderung musst du lediglich möglichst performant machen, dann kannst du den Thread das auch alle 3-5 Sekunden durchlaufen lassen.


----------



## mysticado (7. Nov 2011)

Na gut, jetzt habe ichs neu versucht einfach die VMs an die dazugehörigen NetworkEntities zu hängen, nachdem eine Migration festgestellt wurde. Hier der Code - sieht ein bisschen komplex aus, doch müsste seine Sache tun:


```
HashMap<String, String> host_vms = new HashMap<String, String>(); // Beispiel: [192.168.0.16, 192.168.0.2] (=VM,Host)
hostsUndVMsEinlesen();

		 for(int u=0; u<VMDatabase.networkEntities.size();u++){ //gehe alle NE's durch
			 for(int v=0;v<VMDatabase.networkEntities.get(u).virtualMachines.size();v++){ //gehe die dazugehörigen VMs durch
				 for (String key2 : host_vms.keySet()) {
						String value = host_vms.get(key2);
						if(VMDatabase.networkEntities.get(u).virtualMachines.get(v).getIP().equals(key2)){
							if(!VMDatabase.networkEntities.get(u).virtualMachines.get(v).getHostIP().equals(value)){ //falls die IP des Hosts der VM nicht mehr stimmt...
								VirtualMachine vm = VMDatabase.networkEntities.get(u).virtualMachines.get(v);
								for(int w=0; w<VMDatabase.networkEntities.size();w++){
                                                       // hole die entsprechende NE und füge die VM nun hinzu. Von der alten NE wird die VM entfernt.
									if(VMDatabase.networkEntities.get(w).getIP().equals(value)){
										VMDatabase.networkEntities.get(w).addVM(vm);
										VMDatabase.networkEntities.get(v).removeVM(vm);
										vm.setHostIP(value);
									}
								 }
							}
						}
			          }
			 }
		 }

		javax.swing.tree.DefaultMutableTreeNode root = new javax.swing.tree.DefaultMutableTreeNode(
				"root");
		for (NetworkEntity networkEntity : VMDatabase.networkEntities) {
			DefaultMutableTreeNode entityNode = new DefaultMutableTreeNode(
					networkEntity);
			for (VirtualMachine vm : networkEntity.getVirtualMachines()) {
				if (vm.getHostIP().equals(networkEntity.getIP())) {
					entityNode.add(new DefaultMutableTreeNode(vm));
				}
			}
			root.add(entityNode);
		}
		tree.validate();
```

Das müsste nun deinen Vorschlag implementieren und auch das TreeModel wird nicht neu erstellt. Ich weiss jedoch nicht, ob es denn so nun funktioniert. Leider habe ich aber keine andere Möglichkeit auf den alten Tree zuzugreifen, ausser eben so. Und trotzdem wird mein Tree wieder nicht geupdated. Was könnte jetzt der Grund sein?

Edit: Ja, ich weiss, dass alle GUI-Änderungen über den EDT aufgerufen werden müssen, da es ja sonst zu Inkonsistenzen kommt (wie es mir am Anfang dieses Threads beigebracht wurde ). Ich habe diese ganze Methode auch über einen SwingWorker aufgerufen, um diese ganze Thread.sleep Geschichte richtig ausführen zu können, ohne dass meine ganze GUI einfriert.


Edit2: Okay, ich habe grad festgestellt, dass ichs doch nciht so machen kann wie oben beschrieben, da ich auch irgendwie die Möglichkeit mit berücksichtigen muss, dass evtl. neue VM's dazukommen. Ich habe nun doch den Code wieder auf die alte Version gebracht (wo alles komplett eingelesen wird), was aber nix ausmacht, da eh nie mehr als 10 VM's gleichzeitig laufen werden und somit auch nicht wirklich ein Bottleneck darstellt.
Trotzdem krieg ich aber so komische Ergebnisse raus wie zB.:



> Refresh list: 16:35:40
> Refresh list: 16:35:40
> 
> Show number of children: 0
> ...


----------



## hdi (7. Nov 2011)

Wie sollen wir denn wissen was da falsch läuft? 

saveOldVMsInList();
deleteOldDatabase();
readInNewDatabase();
compareVMsFromListWithVMsFromDB();
createTreeFromNewDatabase();

Aha. :autsch:

Zeig uns, wie *genau *du dieses Update da durchführst.


----------



## mysticado (7. Nov 2011)

Nicht erschrecken 


```
public void refreshTree() throws IOException, InterruptedException{
        while(true){
            
            Thread.sleep(30000);
            System.out.println("Refresh list: "+now());
			
        // Alte VMs in Liste speichern
        List<VirtualMachine> virtualMachines = new ArrayList<VirtualMachine>();
        for(int i=0; i<VMDatabase.networkEntities.size();i++){
            for(int j=0;j<VMDatabase.networkEntities.get(i).virtualMachines.size();j++){
                virtualMachines.add(VMDatabase.networkEntities.get(i).virtualMachines.get(j));
            }
            VMDatabase.networkEntities.get(i).virtualMachines.clear();
        }
        
        // VMs neu einlesen
        VMDatabase.networkEntities.clear();
        Vector<String> one_hosts = new Vector<String>();
        HashMap<String, String> host_vms = new HashMap<String, String>();
         one_hosts.add(temp); // Speichere alle Hosts
         host_vms.put(ip,temp[6]); // Speichere alle VMs + Hosts auf dem sie sind
         }
         }

         // Hier alle Rechner und VMs in die neue Database einlesen
         boolean lock = false;
        for (int i = 0; i < one_hosts.size(); i++) {
            NetworkEntity nw = new NetworkEntity(one_hosts.get(i));
            for (String key2 : host_vms.keySet()) {
                String value = host_vms.get(key2);
                if (value.equals(one_hosts.get(i))) {
                    for(int k=0;k<virtualMachines.size();k++){
                        if(key2.equals(virtualMachines.get(k).getIP())){ // Falls Eintrag schon in alter Liste existiert...
                            VirtualMachine vm = virtualMachines.get(k);
                            vm.setHostIP(value);
                            
                            nw.addVM(vm);
                            lock = true;
                        }
                    }
                    if(lock==false){
                        VirtualMachine vm = new VirtualMachine(key2,one_hosts.get(i), 0L, 0, 0, false, false, 0, "");
                        nw.addVM(vm);
                    }
                    lock = false;
                }
                }
            VMDatabase.networkEntities.add(nw);
        }

        // Baum neu zeichnen
        javax.swing.tree.DefaultMutableTreeNode root = new javax.swing.tree.DefaultMutableTreeNode(
                "root");
        for (NetworkEntity networkEntity : VMDatabase.networkEntities) {
            DefaultMutableTreeNode entityNode = new DefaultMutableTreeNode(
                    networkEntity);
            for (VirtualMachine vm : networkEntity.getVirtualMachines()) {
                if (vm.getHostIP().equals(networkEntity.getIP())) {
                    entityNode.add(new DefaultMutableTreeNode(vm));
                }
            }
            root.add(entityNode);
            System.out.println("Kinder: "+entityNode.getChildCount());
        }
        tree.validate();
        expandAll(tree);
    }
        }
```


----------



## hdi (7. Nov 2011)

Du erstellst einen neuen Knoten namens root, der überhaupt nichts mit deinem TreeModel und dem Tree zu tun hat... Ich zitiere mich:


> Du musst nur die Daten innerhalb des Models (also dem root) verändern


Du solltest dir schön langsam mal die Basics aneignen. Das hier hat null komma nix mit Trees oder Threads zu tun, du hast einfach nicht den blassesten Schimmer wie Java funktioniert..


----------



## mysticado (7. Nov 2011)

Also "nicht den blassesten Schimmer" ist vielleicht doch ein bisschen zu hart, oder? Natürlich habe ich verstanden, was du vor 3 Kommentaren gemeint hast mit "innerhalb des Modells ändern", aber da ich wie gesagt eine Möglichkeit berücksichtigen muss welche es ermöglicht auch neue Knoten hinzuzufügen muss ich ja erstmal komplett die VMs einlesen und dann den Baum theoretisch neu erzeugen (geht jedenfalls schneller). 
Das Problem, das ich nun habe ist, wie kann ich denn den alten Tree verändern? Ich habe nur das eine JTree tree-Objekt, aber ich ging davon aus, dass ich es eben komplett neu zeichnen muss - daher auch der neue root. Wie würdest du das machen?

Und ja, ich gebe es ja zu, dass ich nicht wirklich der beste Programmierer der Welt bin, aber zu meiner eigenen Verteidigung muss ich auch sagen, dass das das erste Mal ist, dass ich was mit Swing mache. Trotzdem bin ich aber offen für alles. Ich möchte natürlich gerne dazulernen. Kannst du mir was empfehlen (ausser die API) wo man wirklich Schritt für Schritt alles beigebracht bekommt? Java ist ne Insel?


----------



## hdi (7. Nov 2011)

Wie ich schon sagte hat das grad nix mit Swing o.ä. zu tun. Du erzeugst irgendein Objekt, speicherst es dir lokal innerhalb einer Methode ab und wunderst dich dass es nicht auf magischem Wege mit deiner Table verknüpft wird? Dir musst doch klar sein dass es überhaupt keine Verbindung zwischen deinem Tree und dieser lokalen Root-Node gibt. Das *sind *absolute Basics. Ich weiß das "keinen blassen Schimmer" vllt hart klingt, aber du musst auch akzeptieren wenn das so ist. Ist ja keine Beleidigung, immerhin wird keiner geboren und kann Java  Aber ändert trotzdem nix daran dass du hier ganz, ganz fundamentale Dinge noch nicht verstanden hast, und dir weiterhin sehr schwer tun wirst, egal bei was, wenn du dir keine solide Grundlage schaffst.



> Das Problem, das ich nun habe ist, wie kann ich denn den alten Tree verändern?


So, wie du's bisher auch getan hast?! Du hast dein TreeModel, welches deine Daten auf den JTree mappt. In diesem TreeModel kannst du doch alles löschen, einfügen, ändern etc wie du lustig bist. Ganz einfach die Referenz auf den root-Knoten dieses Models verwenden in deiner Methode, und nicht irgendeinen neuen Knoten machen der damit gar nix am Hut hat.



> Kannst du mir was empfehlen (ausser die API) wo man wirklich Schritt für Schritt alles beigebracht bekommt? Java ist ne Insel?


Schau in meine Signatur


----------



## mysticado (7. Nov 2011)

ich versuchs grad per:
[c]javax.swing.tree.DefaultMutableTreeNode root = (DefaultMutableTreeNode) tree.getModel().getRoot();[/c] 



Edit: Hab grad gesehen, dass ich oben den Code nicht richtig kopiert habe - das war eine temporäre Version. Daher auch der Fehler, den du als "absolute Basics" bezeichnet hast  Puh, und dabei dachte ich ich wär schon grenzdebil


----------



## mysticado (7. Nov 2011)

Okay, letzter Versuch. Ich habe es nun geschafft den bestehenden Tree korrekt upzudaten, bzw. die alten Knoten erstmal rauszulöschen und dafür dann neue zu setzen.
Das Problem, das ich nun habe ist, dass der Tree jedoch nicht alle 60 Sekunden visuell upgedated wird, sprich, wenn ich an den Knoten nicht rumspiele wird die Ausgangssituation angezeigt. Erst wenn ich ganz oben auf die erste oder zweite Stelle klicke (da ich ja zwei aufklappbare Knoten = NetworkEntities habe), wird der Tree auch sichtbar upgedated.
Ich habe schon diverse Ansätze probiert:

1. ((DefaultTreeModel)tree.getModel()).reload();

2. tree.invalidate(); tree.validate();

3. repaint der kompletten JScrollPane, usw. 

doch leider hat nichts geholfen. 
Ausserdem stört mich, dass die ersten zwei Knoten auch collapsed sind und nicht wie von mir gewollt, gleich offen angezeigt werden.
Ich habe folgenden Code eingebaut, welchen ich nach dem "reload()" aufrufe, um eigentlich alle Knoten offen anzuzeigen:

```
public void expandAll(JTree tree) { // alle Nodes aufmachen
        int row = 0;
        while (row < tree.getRowCount()) {
            tree.expandRow(row);
            row++;
        }
    }
```
aber irgendwie wird das nicht übernommen. Ich habe auch schon das Forum durchsucht, aber dort fand ich nur Methoden die ich oben eben schon erwähnt habe und die bei mir nicht wirklich klappten.  
Wo ist jetzt der Denkfehler?


----------



## hdi (7. Nov 2011)

> 2) Bei allen Änderungen am Model musst du entsprechende fire-Methoden aufrufen damit der JTree das auch mitkriegt und sich neuzeichnet. Das hatten wir hier schon.
> 
> 3) Alle Änderungen auf dem Tree müssen vom EDT ausgeführt werden, das sollte dir mittlerweile auch klar sein



Diese beiden Dinge beachtet? Deswegen meinte ich auch du kannst gleich über dein TreeModel gehen, denn dort hast du Zugriff auf die fire-Methoden. Dann brauchst du kein repaint/reload/invalidate/revalidate/schiessmichtot


----------



## mysticado (7. Nov 2011)

mysticado hat gesagt.:


> Edit: Ja, ich weiss, dass alle GUI-Änderungen über den EDT aufgerufen werden müssen, da es ja sonst zu Inkonsistenzen kommt (wie es mir am Anfang dieses Threads beigebracht wurde ). Ich habe diese ganze Methode (refreshTree()) auch über einen SwingWorker aufgerufen, um diese ganze Thread.sleep Geschichte richtig ausführen zu können, ohne dass meine ganze GUI einfriert.




Das mit dem EDT weiss ich also. 
Das mit dem Model habe ich aber (noch) nicht so implementiert wie du es vorschlägst. Ich muss nämlich zugeben, dass ich wiedermals ein bisschen auf dem Schlauch stehe. Ich implementiere also einen TreeModelListener und hänge es ans Modell. Aber was muss ich denn nun in die treeStructureChanged-Methode schreiben, da ich sie nun brauche oder? Und wie kann ich dann den fire-Event abfangen über das DefaultTreeModel? Pls help 

Hier zumindest mal ein Anfang:


```
TreeModelListener tml = new TreeModelListener(){

			@Override
			public void treeNodesChanged(TreeModelEvent e) {
			}

			@Override
			public void treeNodesInserted(TreeModelEvent e) {
			}

			@Override
			public void treeNodesRemoved(TreeModelEvent e) {
			}

			@Override
			public void treeStructureChanged(TreeModelEvent e) {
				// Was muss hier rein? repaint()?
			}
        	
        };
        model.addTreeModelListener(tml);
```


----------



## hdi (7. Nov 2011)

Wie du jetzt auf einmal auf einen TreeModelListener kommst weiß ich nicht. Es geht hier um's TreeModel. Von einem Listener hab ich doch gar nix gesagt?! Du musst mal etwas besser aufpassen, iIch sag dir etwas und du scheinst es gar nicht zu lesen. zB grad im vorigen Beitrag hab ich gesagt:



> Dann brauchst du kein repaint/reload/invalidate/revalidate/schiessmichtot


Und als direkte Antwort darauf kommt die Frage "Muss hier repaint() rein?"

Und ich habe dir auch schon ausführlich erklärt wie das mit dem TreeModel und den fire-Methoden funktioniert. Den Link zur API-Doc hatte ich dir auch schon gegeben. Eigentlich solltest du das ja auch schon in deinem Code drin haben, immerhin wird der Tree bei Programmstart ja scheinbar auch korrekt angezeigt. Beim Update des Trees musst du genau das selbe machen. Lies dir nochmal alle Beiträge hier durch, all deine Fragen hast du schon mal gestellt und ich hab dir auf alle schon eine Antwort gegeben...

Sorry aber ich weiß auch nicht mehr was ich dir noch schreiben soll...


----------



## mysticado (7. Nov 2011)

Das passiert wenn ich versuche selber über die API drauf zu kommen 
So wie ich das Beispiel aus der API (How to Use Trees (The Java™ Tutorials > Creating a GUI With JFC/Swing > Using Swing Components)) verstehe muss nämlich so ein Listener erzeugt werden, welcher aufpasst ob die Knoten in irgend einer Art und Weise abgeändert werden. Falls ja, muss ja was gemacht werden - deswegen fragte ich oben auch in der treeStructureChanged-Methode was da nun hingehört.
Ich wollte es dir eigentlich mit mir einfacher machen - nehm es mir nicht übel 

Ich weiss, dass wir schon mal das alles durchgekaut hatten, hier ist auch der Code über den wir den Tree erzeugt hatten:


```
javax.swing.tree.DefaultMutableTreeNode root = new javax.swing.tree.DefaultMutableTreeNode(
                "root");

        for (NetworkEntity networkEntity : VMDatabase.networkEntities) {
            DefaultMutableTreeNode entityNode = new DefaultMutableTreeNode(
                    networkEntity);
            for (VirtualMachine vm : networkEntity.getVirtualMachines()) {
                if (vm.getHostIP().equals(networkEntity.getIP())) {
                    entityNode.add(new DefaultMutableTreeNode(vm));
                }
            }
            root.add(entityNode);
        }
        // TODO!!!
        tree.setModel(new javax.swing.tree.DefaultTreeModel(root));

tree.addTreeSelectionListener(new TreeSelectionListener() {...});
```

Genau den selben Code habe ich auch für meine refresh-Methode benutzt, weswegen ich ja auch wieder die richtigen Werte im tree habe. Ich brauche eben nur noch Hilfe bei dieser "fire-Geschichte", da ich nicht weiss wo die hinkommt. Offensichtlich war es ja per Listener falsch....
Und wenn ichs per Cast versuche: [c]TreeModel model = (DefaultTreeModel) tree.getModel();[/c] kriege ich keine [c]model.fireMethode()[/c] 
Deswegen verstehe ich leider nicht wirklich auf was du hinaus willst?


----------



## hdi (7. Nov 2011)

> Ich brauche eben nur noch Hilfe bei dieser "fire-Geschichte"





> Lies dir nochmal alle Beiträge hier durch, all deine Fragen hast du schon mal gestellt und ich hab dir auf alle schon eine Antwort gegeben...



:bahnhof:


----------



## mysticado (9. Nov 2011)

Hi,
es tut mir echt Leid dich zu nerven, doch ich muss dich einfach noch diese Kleinigkeit fragen, da du einfach mehr Ahnung hast als ich und wahrscheinlich auch sofort drauf kommen wirst, worum es sich bei meinem Problem handelt.
Kurz zur Erinnerung - ich habe in meine initComponents()-Methode der GUI einen SwingWorker eingebaut, welcher alle 30 Sekunden nach Änderungen in der Tree-Struktur schaut. Soweit funktioniert auch alles - die Daten werden korrekt upgedated (auch in der VMDatabase Klasse), nur will sich mein Tree nicht refreshen in der GUI.
Ich weiss, dass:
1. dieser sich wiederholende Thread in einen SwingWorker gepackt werden muss, da Swing nicht threadsicher ist.
2. Methoden die von innerhalb des SwingWorkers auf die GUI zugreifen, in der process() oder done() Methode aufgerufen werden müssen - alles andere kommt in die doInBackground()
3. ich über das TreeModel die Knoten einfach updaten kann und java übernimmt das refreshen (normalerweise) dann automatisch.


Aber! Das tut es bei mir leider immer noch nicht, obwohl ich verschiedenste Methoden ausprobiert hatte. Zuerst hatte ich es so gemacht, wie du es mir von ein paar Beiträgen erklärt hattest (wo ich meinen Tree das erste Mal erstellen wollte, beim Aufbau der GUI), sprich ich habe einfach nur diese alte Methode kopiert und sie für den SwingWorker etwas abgeändert - hat leider nichts gebracht. Nun versuche ich es über das Modell, aber trotzdem will es nicht so wirklich. Das erste Mal klappt es normalerweise immer, aber es will sich einfach nicht alle 30 Sekunden refreshen. Kann es sein, dass meine done()-Methode nur einmal aufgerufen wird, obwohl die doInBackground()-Methode endlos läuft (siehe Code unten)?

Ausserdem bekam ich immer wieder eine NullPointerException bei jedem Refresh, doch die fange ich nun ab in der Hoffnung, dass sie den Programmablauf nicht stört. Jedoch könnte auch sie vielleicht der Grund sein, dass der Refresh nicht funktioniert? Die NPE tauchte wie gesagt jedes Mal beim Refresh des Baums auf, nachdem mein TreeSelectionListener versucht, den letzten ausgewählten Pfad zu finden - du erinnerst dich:

```
DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree
                        .getLastSelectedPathComponent();
                Object data = node.getUserObject();
                VirtualMachine vm = null;
                if (data instanceof NetworkEntity) {
                } else {
                    vm = (VirtualMachine) data;
                    ueberschrift.setText(" VM: " + vm.getIP());
                }
```

Es könnte also mehrere Gründe haben und ich bin einfach der Hoffnung, du wirst es mit deiner reichen Erfahrung sofort rausfinden können 
Hier noch der Code für den SwingWorker:


```
SwingWorker job = new SwingWorker(){

            @Override
            protected Object doInBackground() throws Exception {
                while(true){
                Thread.sleep(30000);
                refreshTree();
                return null;
                }
            }
            protected void done(){
                // treeNode1 = rootNode !
                List<NetworkEntity> networkEntities = VMDatabase.networkEntities;
                treeNode1.removeAllChildren();
                for (int h=0; h<networkEntities.size();h++) {
                    NetworkEntity networkEntity = networkEntities.get(h);
                    DefaultMutableTreeNode entityNode = new DefaultMutableTreeNode(networkEntity);
                    model.insertNodeInto(entityNode, treeNode1, treeNode1.getChildCount());
                    for (VirtualMachine vm : networkEntity.getVirtualMachines()) {
                        
                        if (vm.getHostIP().equals(networkEntity.getIP())) {
                            model.insertNodeInto(new DefaultMutableTreeNode(vm), entityNode, entityNode.getChildCount());
                        }
                    }
                }
                // Verschiedene Refreshing-Versuche:
                //((DefaultTreeModel) model).nodeStructureChanged(treeNode1);
                //model.reload();
                //tree.treeDidChange();
                //expandAll(tree);    
                
                }
        };
        job.execute();
```


----------

