# While-Schleife in einem Service für GUI



## heinz ketchup (19. Feb 2018)

Halli Hallo,
ich programmiere derzeit eine "Suchmaschine", welche mithilfe einer While-Schleife überprüfen soll, ob die Buchstaben, welche ich in ein TextField eintippe, mit einem "Random String", welchen ich mit einer zufälligen Reihenfolge an Buchstaben des Alphabets auf die länge des Textes im TextField fülle, übereinstimmen. Das passiert dann natürlich sehr oft und sehr schnell. Ich möchte mir den "RandomString" in der GUI anzeigen lassen, was ich mithilfe eines Services machen lasse.
Die Überprüfung, ob TextField.getText() = "RandomString" passiert hier:

```
label.textProperty().addListener(new ChangeListener<String>()
        {
            @Override
            public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue)
            {
                if(inputField.getText().equals(label.getText()))
                {
                    winLabel.setText("ERFOLG");
                    System.out.println("ERFOLG");
                    Platform.runLater(() -> pb.setProgress(1));
                }
            }
        });
```

und mein Service sieht so aus:

```
ScheduledService<Integer> service = new ScheduledService<Integer>()
        {
            @Override
            protected Task<Integer> createTask()
            {
                return new Task<Integer>()
                {
                    @Override
                    protected Integer call() throws Exception
                    {                                      
                        Platform.runLater(() ->
                        {
                            System.out.println("Service läuft...");
                            System.out.println("Having: " + label.getText() +
                                    " | Looking for: " + inputField.getText() +
                                    " | inputField length: " + inputField.getLength() +
                                    " | meineZahl length: " + meineZahl +
                                    " | getMeineZahl length: " + getMeineZahl());
                            meineZahl = inputField.getLength();
                          
                            while(!winLabel.getText().equals("ERFOLG"))
                            {
                                label.setText(das_affenprojekt_experimentieren.RandomTextausgabe());
                              
                                if(winLabel.getText().equals("ERFOLG"))
                                {
                                    break;
                                }
                            }
                        });
                        return 0;
                    }
                          
                };
            }
          
        };
        //service.setDelay(Duration.seconds(1));
        service.setPeriod(Duration.millis(speed));
```
Den Service steuer ich mit service.restart(); und service.cancel(); in jeweils einem Start- und Stop-Button.
Das Problem ist, dass sich das Interface aufhängt und auch nicht die System Printlines in der Konsole anzeigt. Sobald das Wort jedoch gefunden wurde, werden die Printlines angezeigt. Meine Vermutung ist, dass es an der While-Schleife liegt. Mit einer If-Abfrage hängt sich da nämlich nichts auf, jedoch wird nur einmal Abgefragt.
Ich bitte höflichst um Hilfestellung und Lösungsvorschläge, wie ich das evtl. sogar besser hinbekommen könnte.
Falls ihr Fragen habt oder ich etwas vergessen haben sollte, lasst euch in den Comments aus.
Vielen Dank im Voraus.


----------



## krgewb (19. Feb 2018)

Ich zitiere Saxony:
,,isein bekannstes Problem. Dein break steht innerhalb eines anderen Blockes (if) und nicht direkt als Anweisung im while Block."


----------



## mrBrown (19. Feb 2018)

Ich bin ehrlich gesagt noch nicht ganz schlau geworden aus deiner Beschreibung.

eine while-schleife im Event-Thread blockiert diesen aber logischerweise, woher dein eines Problem rührt.




krgewb hat gesagt.:


> Ich zitiere Saxony:
> ,,isein bekannstes Problem. Dein break steht innerhalb eines anderen Blockes (if) und nicht direkt als Anweisung im while Block."


Ein break bezieht sich ohne Label immer auf die nächste umgebende Schleife oder Switch. Völlig unabhängig, wie tief das geschachtelt ist.


----------



## heinz ketchup (20. Feb 2018)

mrBrown hat gesagt.:


> Ich bin ehrlich gesagt noch nicht ganz schlau geworden aus deiner Beschreibung.


Wobei brauchst du denn detailliertere Erklärung?



mrBrown hat gesagt.:


> eine while-schleife im Event-Thread blockiert diesen aber logischerweise, woher dein eines Problem rührt.


Naja. Als ich dafür einen stink normalen Thread verwendet habe, hat das mit der while-schleife problemlos funktioniert, siehe hier:

```
Thread t = new Thread()
{
    @SuppressWarnings("unlikely-arg-type")
    public void run()
    {
        try
        {
            Thread.sleep(speed);
        }
        catch (InterruptedException e1)
        {
            e1.printStackTrace();
        }
         
        Platform.runLater(() ->
        {
            while(run == true)
            {         
                System.out.println("Having: " + label.getText() +
                        " | Looking for: " + inputField.getText() +
                        " | inputField length: " + inputField.getLength() +
                        " | meineZahl length: " + meineZahl +
                        " | getMeineZahl length: " + getMeineZahl());
             
                while(!inputField.getText().equals(label))
                {
                    inputField.setEditable(false);
         
                    label.setText(das_affenprojekt_experimentieren.RandomTextausgabe());
                 
                    if(winLabel.getText().equals("ERFOLG") || run == false)
                    {
                        break;
                    }
                 
                    if(winLabel.getText().equals("ERFOLG"))
                    {
                        label.setText(inputField.getText());                     
                    }
                }
            }
        });
    };
    t.start();
```
Problem beim Thread ist nur, das ich den nicht mit Start- und Stopp-Buttons steuern kann. Über while-schleifen mit Booleans hat das auch nicht ganz funktioniert.


----------



## mrBrown (20. Feb 2018)

heinz ketchup hat gesagt.:


> Wobei brauchst du denn detailliertere Erklärung?


Dafür, was du eigentlich erreichen willst.



heinz ketchup hat gesagt.:


> Naja. Als ich dafür einen stink normalen Thread verwendet habe, hat das mit der while-schleife problemlos funktioniert, siehe hier:


Das führt zu dem gleichen Problem, die Schleife blockiert den Event-Thread und damit die GUI.

In beiden Fällen verschiebst du die Schleife ganz explizit aus dem anderem Thread in den Event-Thread.
Steuern kannst du auch bei deiner Variante mit Service nichts, da nichts innerhalb des Services läuft, außer alles in den Event-Thread zu schieben


----------



## Barista (20. Feb 2018)

Als ich noch Swing gemacht habe (lang her) sollten langlaufende Aufgaben mit

SwingUtilities.invokeLater

in einen anderen Thread als den Event-Dispatch-Thread verschoben werden.


----------



## mrBrown (20. Feb 2018)

Barista hat gesagt.:


> Als ich noch Swing gemacht habe (lang her) sollten langlaufende Aufgaben mit
> 
> SwingUtilities.invokeLater
> 
> in einen anderen Thread als den Event-Dispatch-Thread verschoben werden.


`SwingUtilities.invokeLater` schiebt es allerings *in* den Event-Dispatch-Thread


----------



## heinz ketchup (20. Feb 2018)

Also, ich weiß nur, dass es mit dem Thread problemlos funktioniert hat. Das einzige Problem und auch K.O.-Kriterium war, das ich den Thread nicht mit t.start(); und t.cancel(); kontrollieren konnte (auch andere Befehle, wie t.notify(); und t.wait(); funktionieren nicht). Den Service kann ich einfach mit service.restart(); im Start-Button und serivce.cancel(); im Stop-Button kontrollieren.
Von Event-Threads höre ich jetzt allerdings zum ersten mal.
SwingUtilities.invokeLater ist das selbe, wie Platform.runLater.


----------



## heinz ketchup (20. Feb 2018)

Ok, ich hab mich ein wenig Schlau gemacht.
Bei Stackoverflow meinen die Herrschaften, ich soll bei Services einen Task anstatt Platform.runLater verwenden.
Das ganze sieht bei mir dann so aus:

```
ScheduledService<Integer> service = new ScheduledService<Integer>()
        {
            @Override
            protected Task<Integer> createTask()
            {
                return new Task<Integer>()
                {
                    @Override
                    protected Integer call() throws Exception
                    {
                        Task task = new Task<Void>()
                        {
                            @Override
                            public Void call()
                            {
                                System.out.println("Service läuft...");
                                System.out.println("Having: " + label.getText() + 
                                        " | Looking for: " + inputField.getText() + 
                                        " | inputField length: " + inputField.getLength() + 
                                        " | meineZahl length: " + meineZahl + 
                                        " | getMeineZahl length: " + getMeineZahl());
                                meineZahl = inputField.getLength();
                           
                                while(!winLabel.getText().equals("ERFOLG"))
                                {
                                    label.setText(das_affenprojekt_experimentieren.RandomTextausgabe());
                               
                                    if(winLabel.getText().equals("ERFOLG"))
                                    {
                                        break;
                                    }
                                }
                                return null;
                            }
                        };
                        new Thread(task).start();
                        return 0;
                    }
                           
                };
            }
           
        };
        //service.setDelay(Duration.seconds(1));
        service.setPeriod(Duration.millis(speed));
```
Tja, leider funktioniert das auch nicht, wie gewollt.
Jetzt bekomme ich die Meldung "Exception in thread "Thread-8" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-8".
Eine weitere angebliche Lösung aus dem Internet wäre eine "Timeline".
Ich hab mir kurz ein Video von NetLamp darüber reingezogen und das hatte nix mit dem zu tun, was ich machen möchte.



mrBrown hat gesagt.:


> Dafür, was du eigentlich erreichen willst.


Tieferen Sinn hat das ganze nicht. Ein Kumpel ist auf mich zugekommen und meinte, das wäre einen gute Übung für mich, da ich noch neu bin am Javahimmel.


----------



## mrBrown (20. Feb 2018)

Eine 9-Blöcke tiefe Schachtelung sollte Grund genug sein, den Computer stehen zu lassen und  schreiend wegzurennen 



heinz ketchup hat gesagt.:


> Tieferen Sinn hat das ganze nicht. Ein Kumpel ist auf mich zugekommen und meinte, das wäre einen gute Übung für mich, da ich noch neu bin am Javahimmel.


Auch wenn das eine gute Übung ist, weiß ich immer noch nicht, *was* du da eigentlich machen möchtest 

Wenn ich das richtig verstehe, soll solange irgendwo immer ein neuer, zufälliger Text angezeigt werden, bis eine Bedingung erfüllt ist, und das ganze soll Start- und Pausierbar sein?


----------



## heinz ketchup (20. Feb 2018)

mrBrown hat gesagt.:


> Eine 9-Blöcke tiefe Schachtelung sollte Grund genug sein, den Computer stehen zu lassen und  schreiend wegzurennen



Ich hab einen Ryzen 7 1800X, falls du das meinst. Und ja, mein Programmier-Stil lässt noch zu wünschen übrig.
Außerdem zähle ich nur acht.




mrBrown hat gesagt.:


> Auch wenn das eine gute Übung ist, weiß ich immer noch nicht, *was* du da eigentlich machen möchtest
> 
> Wenn ich das richtig verstehe, soll solange irgendwo immer ein neuer, zufälliger Text angezeigt werden, bis eine Bedingung erfüllt ist, und das ganze soll Start- und Pausierbar sein?



Präzise


----------



## mrBrown (20. Feb 2018)

heinz ketchup hat gesagt.:


> Ich hab einen Ryzen 7 1800X, falls du das meinst.


Ne, eigentliche hat der Prozessor ziemlich wenig mit der Struktur des Quellcodes zu tun...



heinz ketchup hat gesagt.:


> Außerdem zähle ich nur acht.


Kommt auf die Zählweise und das drumherum an 

##

Eine Timeline oder ein Simpler Timer dürfen für diesen Fall das passende sein.
Die läufen einfach mit passendem Intervall (ohne Schleife!) und machen nichts anderes(!), als den zufälligen Text zu setzen (pseudocodemäßig ein runLater( setText(...))).

Wenn der Text nicht mehr geändert werden soll, stoppst du den Timer/die Timeline einfach. Genauso wie mit dem  Button zum Stoppen.


----------



## heinz ketchup (20. Feb 2018)

mrBrown hat gesagt.:


> Eine Timeline oder ein Simpler Timer dürfen für diesen Fall das passende sein.
> Die läufen einfach mit passendem Intervall (ohne Schleife!) und machen nichts anderes(!), als den zufälligen Text zu setzen (pseudocodemäßig ein runLater( setText(...))).
> 
> Wenn der Text nicht mehr geändert werden soll, stoppst du den Timer/die Timeline einfach. Genauso wie mit dem  Button zum Stoppen.



Meinst du quasi eine Timeline anstatt des Services oder die Timeline in den Service hinein setzen?


----------



## mrBrown (20. Feb 2018)

Anstatt des Services


----------



## heinz ketchup (20. Feb 2018)

Okay ich hab jetzt Folgendes:

```
Timeline tl = new Timeline(new KeyFrame(
                Duration.INDEFINITE,
                ae -> {
                    System.out.println("Service läuft...");
                    System.out.println("Having: " + label.getText() + 
                            " | Looking for: " + inputField.getText() + 
                            " | inputField length: " + inputField.getLength() + 
                            " | meineZahl length: " + meineZahl + 
                            " | getMeineZahl length: " + getMeineZahl());
                    meineZahl = inputField.getLength();
                    label.setText(das_affenprojekt_experimentieren.RandomTextausgabe());
                }
                ));
```

und tl.play(); befindet sich im Start-Button.
Das führt er so nicht aus, sprich ich bekomme nicht die entsprechenden Printlines in der Konsole angezeigt.
Auch mit "Duration.hours(1)" oder Duration.ZERO" usw passiert nichts spannendes.
Hab ich etwas übersehen oder so? Ist ja schon spät. Ich weiß gar nicht, warum ich noch Wach bin.
Das Wissen über Timelines hab ich jetzt hier gefunden.


----------



## thecain (20. Feb 2018)

warum nicht so wie du im Beitrag verlinkt hast? 



> *Periodic action*
> This is how to schedule a periodic action using Timeline:
> 
> ```
> ...


----------



## heinz ketchup (21. Feb 2018)

Ok, jetzt funktioniert das.
Schaut im Moment so aus:

```
Timeline tl = new Timeline(new KeyFrame(
                Duration.millis(speed),
                ae -> 
                {
                    System.out.println("Service läuft...");
                System.out.println("Having: " + label.getText() + 
                        " | Looking for: " + inputField.getText() + 
                        " | inputField length: " + inputField.getLength() + 
                        " | meineZahl length: " + meineZahl + 
                        " | getMeineZahl length: " + getMeineZahl());
                meineZahl = inputField.getLength();
                label.setText(das_affenprojekt_experimentieren.RandomTextausgabe());
                if(label.getText().equals(inputField.getText()))
                {
                    System.out.println("tl if");
                    tlstop();
                }
                }));
        tl.setCycleCount(Animation.INDEFINITE);
       
        Object tlstop;
        {
            tl.stop();
        }
```

Object tlstop(); wollte ich als meinen Auslöser einsetzen, wenn das Wort gefunden wurde.
Leider geht er gar nicht erst in die if-abfrage in der Timeline hinein. Ich glaube aber, ich kann
in dem Fall auch mit SCHEDULED und so arbeiten. Muss ich mir aber noch weiter durchlesen.


----------



## mrBrown (21. Feb 2018)

Guck dir noch mal die Java-Syntax an.



heinz ketchup hat gesagt.:


> ```
> Object tlstop;
> {
> tl.stop();
> ...


Damit deklariertet du das Objekt `tlstop` und führst unabhängig davon die Funktion `tl.stop();` aus. Miteinander zu tun haben diese beiden Dinge nichts.


----------



## heinz ketchup (21. Feb 2018)

Jo weiß ich. Ist auch nicht aktuell, ich habs gelöscht


----------



## heinz ketchup (21. Feb 2018)

Was ich brauche, ist quasi `Duration.while(!label.getText().equals(inputField.getText()));`.
Fällt jemanden dazu was ein? Im Internet konnte ich bisher nichts finden.

Edit: Das der Code oben nicht funktioniert, ist mir durchaus bewusst.


----------



## heinz ketchup (21. Feb 2018)

Ok, hab ne Lösung eigenständig gefunden:

```
Timeline tl = new Timeline(new KeyFrame(
                Duration.millis(speed),
                ae ->
                {
                    Platform.runLater(() ->
                    {
                        if(!winLabel.getText().equals("ERFOLG"))
                        {
                            System.out.println("Service läuft...");
                            System.out.println("Having: " + label.getText() + 
                                    " | Looking for: " + inputField.getText() + 
                                    " | inputField length: " + inputField.getLength() + 
                                    " | meineZahl length: " + meineZahl + 
                                    " | getMeineZahl length: " + getMeineZahl());
                            meineZahl = inputField.getLength();
                            label.setText(das_affenprojekt_experimentieren.RandomTextausgabe());
                        }
                    });
                }));
        tl.setCycleCount(Animation.INDEFINITE);
```

Das hat bis jetzt funktioniert.
Ich bedanke mich für eure freiwillige Mitarbeit und Hilfestellung
und wünsche euch einen guten Abend.
Bis dahin


----------



## mrBrown (21. Feb 2018)

einen Listener auf die Textfelder, und wenn beide gleich sind ein `t1.stop()` oder ei stop innerhalb des KeyFrames

(Im Idealfall refactored man das danach MVC-Konform)


----------



## heinz ketchup (21. Feb 2018)

mrBrown hat gesagt.:


> (Im Idealfall refactored man das danach MVC-Konform)


Erklärung bitte :thinking:


----------

