2016-07-28 48 views
0

Ich versuche, einen Google Maps-Marker in FXML regelmäßig zu aktualisieren. Ich habe versucht, dies mit einem Timer und einem neuen Thread zu tun, kann aber nichts davon zum Laufen bekommen.Java FXML Update-Benutzeroberfläche mit neuen Thread-Fehlern

Ich testete die neue Thread mit der einfachen Aufgabe, eine TextField in meiner UI zu aktualisieren, die gut funktioniert.

Allerdings, wenn ich den eigentlichen Code verwenden, die ich brauche, um die Karte zu aktualisieren:

@FXML 
public void handleTracking() throws IOException, InterruptedException { 
    new Thread() { 
     @Override 
     public void run() { 
      while (true) { 
       try { 
        double ar[] = FileImport.getGpsPosition(); 
        System.out.println("Latitude: " + ar[0] + " Longitude: " + ar[1]); 
        double Ltd = ar[0]; 
        double Lng = ar[1]; 
        webEngine.executeScript("" 
      + "window.lat = " + Ltd + ";" 
      + "window.lon = " + Lng + ";" 
      + "document.goToLocation(window.lat, window.lon);"); 
        try { 
         Thread.sleep(555); 
        } catch (InterruptedException ex) { 
         Logger.getLogger(FXMLDocumentController.class.getName()).log(Level.SEVERE, null, ex); 
        } 
       } catch (IOException ex) { 
        Logger.getLogger(FXMLDocumentController.class.getName()).log(Level.SEVERE, null, ex); 
       } 
      } 
     } 
    }.start(); 
} 

ich die Ausgangsmeldung:

Exception in thread "Thread-26" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-26 
    at com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:236) 
    at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:423) 
    at javafx.scene.web.WebEngine.checkThread(WebEngine.java:1216) 
    at javafx.scene.web.WebEngine.executeScript(WebEngine.java:980) 
    at de.fkfs.v2x.eval.FXMLDocumentController$1.run(FXMLDocumentController.java:84=) 

Ähnliches passiert, wenn ich einen Timer verwenden, es funktioniert für die Aufgabe der Aktualisierung eines Etiketts, aber wenn ich versuche, die Marker-Position zu aktualisieren, wirft es die folgende Meldung:

+2

Mögliches Duplikat von [java.lang.IllegalStateException: Nicht im FX-Anwendungs-Thread; currentThread = Thread-4] (http://stackoverflow.com/questions/29449297/java-lang-illegalstateexception-not-on-fx-application-thread-currentthread-t) – DVarga

+1

Sie finden ein Beispiel für die Aktualisierung der Benutzeroberfläche von ein anderer Thread hier: http://stackoverflow.com/documentation/javafx/2230/threading/7291/updating-the-ui-using-platform-runlater#t=201607280912032640534 – fabian

Antwort

1

Updates für die Benutzeroberfläche, einschließlich Anrufe an webEngine.executeScript(...)müssen auf dem FX-Anwendungs-Thread ausgeführt werden.

Auf der anderen Seite ist der FX-Anwendungs-Thread (effektiv) der Thread, der zum Rendern der Benutzeroberfläche und zur Verarbeitung von Benutzereingaben verwendet wird. Wenn Sie also diesen Thread mit einer Endlosschleife oder einem anderen lang andauernden Prozess blockieren oder wenn Sie zu viele Dinge planen, die in diesem Thread ausgeführt werden, reagiert die UI nicht mehr.

Was Sie in Ihrem Code zu tun versuchen, scheint zu sein, die Benutzeroberfläche so schnell wie möglich zu aktualisieren. Wenn du die Schleife in den FX-Anwendungs-Thread legst, wirst du sie komplett blockieren: Wenn du sie in einen Hintergrund-Thread legst und die Updates unter Verwendung von Platform.runLater(...) einplanst, überflutet du den FX-Anwendungs-Thread mit zu vielen Updates und verhinderst, dass er seine übliche Arbeit erledigt, und es wird nicht mehr reagieren.

Die allgemeine Lösung hier dreht sich um die Tatsache, dass es wirklich überflüssig ist, die Benutzeroberfläche so oft zu aktualisieren. Das menschliche Auge kann nur sichtbare Veränderungen mit einer begrenzten Rate erkennen, und in technologischer Hinsicht sind Sie durch z.B. die Aktualisierungsrate des physischen Bildschirms und der zugrunde liegenden grafischen Software. JavaFX versucht, die Benutzeroberfläche nicht mehr als 60 Hz zu aktualisieren (in der aktuellen Implementierung). Es macht also keinen Sinn, öfter zu aktualisieren, als das zugrunde liegende JavaFX-Toolkit die Szene aktualisiert.

Die AnimationTimer bietet eine handle Methode, die garantiert einmal pro Szeneupdate aufgerufen wird, egal wie oft das auftritt. AnimationTimer.handle(...) wird im FX-Anwendungs-Thread aufgerufen, sodass Sie hier Änderungen an der Benutzeroberfläche vornehmen können. So könnten Sie Ihre Tracking-Implementierung mit:

private AnimationTimer tracker ; 

public void initialize() { 
    tracker = new AnimationTimer() { 
     @Override 
     public void handle(long timestamp) { 

      try { 
       double ar[] = FileImport.getGpsPosition(); 
       // System.out.println("Latitude: " + ar[0] + " Longitude: " + ar[1]); 
       double Ltd = ar[0]; 
       double Lng = ar[1]; 
       webEngine.executeScript("" 
     + "window.lat = " + Ltd + ";" 
     + "window.lon = " + Lng + ";" 
     + "document.goToLocation(window.lat, window.lon);"); 
      } catch (IOException ex) { 
       Logger.getLogger(FXMLDocumentController.class.getName()).log(Level.SEVERE, null, ex); 
      } 

     } 
    }; 
} 

@FXML 
public void handleTracking() { 
    tracker.start(); 
} 

das einzige, was von hier vorsichtig zu sein, ist, dass, weil handle() auf dem FX-Anwendung Gewinde aufgerufen wird, sollten Sie keine ausführen lang laufende Code hier. Es sieht so aus, als ob Ihre FileImport.getGpsPosition()-Methode einige IO-Operationen ausführt, daher sollte sie wahrscheinlich an einen Hintergrundthread delegiert werden.Der Trick hier, der von JavaFX-Klassen wie Task verwendet wird, besteht darin, einen Wert kontinuierlich von einem Hintergrundthread zu aktualisieren, und nur einen Aufruf an Platform.runLater(...) planen, wenn einer nicht bereits aussteht.

Zunächst definieren nur eine einfache Klasse für den Standort darstellt (es unveränderlich, so ist es Thread-sicher):

class Location { 
    private final double longitude ; 
    private final double latitude ; 

    public Location(double longitude, double latitude) { 
     this.longitude = longitude ; 
     this.latitude = latitude ; 
    } 

    public double getLongitude() { 
     return longitude ; 
    } 

    public double getLatitude() { 
     return latitude ; 
    } 
} 

und jetzt:

@FXML 
private void handleTracking() { 

    AtomicReference<Location> location = new AtomicReference<>(null); 

    Thread thread = new Thread(() -> { 
     try { 
      while (true) { 
       double[] ar[] = FileImport.getGpsPosition(); 
       Location loc = new Location(ar[0], ar[1]); 

       if (location.getAndSet(loc) == null) { 
        Platform.runLater(() -> { 
         Location updateLoc = location.getAndSet(null); 
         webEngine.executeScript("" 
          + "window.lat = " + updateLoc.getLatitude() + ";" 
          + "window.lon = " + updateLoc.getLongitude() + ";" 
          + "document.goToLocation(window.lat, window.lon);"); 
        }); 
       } 
      } 
     } catch (IOException exc) { 
      Logger.getLogger(FXMLDocumentController.class.getName()).log(Level.SEVERE, null, ex); 
     } 
    }); 

    thread.setDaemon(true); 
    thread.start(); 
} 

Die Art und Weise dies funktioniert, ist, dass Es erstellt einen (Thread-sicheren) Halter für den aktuellen Standort und aktualisiert ihn so schnell wie möglich. Wenn es aktualisiert wird, prüft es (atomar) auch, ob der aktuelle Wert null ist. Wenn es null ist, plant es ein UI-Update über Platform.runLater(). Ist dies nicht der Fall, wird lediglich der Wert aktualisiert, es wird jedoch kein neues Benutzeroberflächen-Update geplant.

Das UI-Update (atomar) ruft den aktuellen (d. H. Neuesten) Wert ab und setzt es auf null, um anzuzeigen, dass es für ein neues UI-Update bereit ist. Es verarbeitet dann das neue Update.

Auf diese Weise "drosseln" Sie die UI-Aktualisierungen, so dass neue nur geplant werden, wenn die aktuelle verarbeitet wird. Vermeiden Sie, den UI-Thread mit zu vielen Anfragen zu überfluten.

+0

vielen Dank James_D! Beide Techniken funktionieren. Du hast recht, ich möchte wahrscheinlich die ganze Zeit neue GPS-Daten aus meinen Logfiles bekommen. Ich werde Ihren Code ein paar Mal durchgehen müssen, um es vollständig zu verstehen. Danke ich schätze das wirklich! – Evo

0

Alle JavaFX-UI-Elemente müssen innerhalb des FX-Anwendungsthreads aktualisiert werden!

Wenn Sie einen zusätzlichen Thread verwenden, verwenden Sie unbedingt platform.Runlater(), um Ihre UI-Elemente zu aktualisieren!

+0

Wenn ich den Code in Platform ausgeführt werden soll. runLater, das Programm läuft, aber es wird so langsam, dass zum Beispiel die Karte nicht mehr gescrollt werden kann. – Evo

+0

@Evo warum benutzt du nicht stattdessen eine Timeline? Es wäre viel einfacher! –

+0

@Evo Stellen Sie sicher, dass Ihre WhileLoop um die Plattform Runlater-Methode gewickelt ist! Ich meinte! –