Saltar al contenido

Actualización de la interfaz de usuario de diferentes hilos en JavaFX

Solución:

No estoy seguro si lo entiendo completamente, pero creo que esto puede ayudar.

Utilizando Platform.runLater(...) es un enfoque apropiado para esto.

El truco para evitar saturar el subproceso de aplicación FX es usar una variable atómica para almacenar el valor que le interesa. Platform.runLater , recupérelo y configúrelo en un valor centinela. Desde su hilo de fondo, actualice la variable Atomic, pero solo emita una nueva Platform.runLater si ha vuelto a su valor centinela.

Descubrí esto mirando el código fuente de Task. Eche un vistazo a cómo updateMessage Se implementa el método (línea 1131 en el momento de escribir este artículo).

Aquí hay un ejemplo que usa la misma técnica. Esto solo tiene un hilo de fondo (ocupado) que cuenta lo más rápido posible, actualizando un IntegerProperty. Un observador observa esa propiedad y actualiza un AtomicInteger con el nuevo valor. Si el valor actual de la AtomicInteger es -1, programa un Platform.runLater.

En el Platform.runLater, Recupero el valor del AtomicInteger y utilícelo para actualizar un Label, estableciendo el valor de nuevo en -1 en el proceso. Esto indica que estoy listo para otra actualización de la interfaz de usuario.

import java.text.NumberFormat;
import java.util.concurrent.atomic.AtomicInteger;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;

public class ConcurrentModel extends Application 

  @Override
  public void start(Stage primaryStage) 
    
    final AtomicInteger count = new AtomicInteger(-1);
    
    final AnchorPane root = new AnchorPane();
    final Label label = new Label();
    final Model model = new Model();
    final NumberFormat formatter = NumberFormat.getIntegerInstance();
    formatter.setGroupingUsed(true);
    model.intProperty().addListener(new ChangeListener() 
      @Override
      public void changed(final ObservableValue observable,
          final Number oldValue, final Number newValue) 
        if (count.getAndSet(newValue.intValue()) == -1) 
          Platform.runLater(new Runnable() 
            @Override
            public void run() 
              long value = count.getAndSet(-1);
              label.setText(formatter.format(value));
            
          );          
        

      
    );
    final Button startButton = new Button("Start");
    startButton.setOnAction(new EventHandler() 
      @Override
      public void handle(ActionEvent event) 
        model.start();
      
    );

    AnchorPane.setTopAnchor(label, 10.0);
    AnchorPane.setLeftAnchor(label, 10.0);
    AnchorPane.setBottomAnchor(startButton, 10.0);
    AnchorPane.setLeftAnchor(startButton, 10.0);
    root.getChildren().addAll(label, startButton);

    Scene scene = new Scene(root, 100, 100);
    primaryStage.setScene(scene);
    primaryStage.show();
  

  public static void main(String[] args) 
    launch(args);
  

  public class Model extends Thread 
    private IntegerProperty intProperty;

    public Model() 
      intProperty = new SimpleIntegerProperty(this, "int", 0);
      setDaemon(true);
    

    public int getInt() 
      return intProperty.get();
    

    public IntegerProperty intProperty() 
      return intProperty;
    

    @Override
    public void run() 
      while (true) 
        intProperty.set(intProperty.get() + 1);
      
    
  

Si realmente desea “controlar” el back-end desde la interfaz de usuario: es decir, acelerar la velocidad de la implementación del back-end para que vea todas las actualizaciones, considere usar un AnimationTimer. Un AnimationTimer tiene un handle(...) que se llama una vez por fotograma render. Por lo tanto, podría bloquear la implementación de back-end (por ejemplo, utilizando una cola de bloqueo) y liberarla una vez por invocación del método handle. los handle(...) El método se invoca en el subproceso de aplicación FX.

los handle(...) El método toma un parámetro que es una marca de tiempo (en nanosegundos), por lo que puede usarlo para ralentizar aún más las actualizaciones, si una vez por fotograma es demasiado rápido.

Por ejemplo:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.property.LongProperty;
import javafx.beans.property.SimpleLongProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;


public class Main extends Application 
    @Override
    public void start(Stage primaryStage) 
        
        final BlockingQueue messageQueue = new ArrayBlockingQueue<>(1);
        
        TextArea console = new TextArea();
        
        Button startButton = new Button("Start");
        startButton.setOnAction(event -> 
            MessageProducer producer = new MessageProducer(messageQueue);
            Thread t = new Thread(producer);
            t.setDaemon(true);
            t.start();
        );
        
        final LongProperty lastUpdate = new SimpleLongProperty();
        
        final long minUpdateInterval = 0 ; // nanoseconds. Set to higher number to slow output.
        
        AnimationTimer timer = new AnimationTimer() 

            @Override
            public void handle(long now) 
                if (now - lastUpdate.get() > minUpdateInterval) 
                    final String message = messageQueue.poll();
                    if (message != null) 
                        console.appendText("n" + message);
                    
                    lastUpdate.set(now);
                
            
            
        ;
        
        timer.start();
        
        HBox controls = new HBox(5, startButton);
        controls.setPadding(new Insets(10));
        controls.setAlignment(Pos.CENTER);
        
        BorderPane root = new BorderPane(console, null, null, controls, null);
        Scene scene = new Scene(root,600,400);
        primaryStage.setScene(scene);
        primaryStage.show();
    
    
    private static class MessageProducer implements Runnable 
        private final BlockingQueue messageQueue ;
        
        public MessageProducer(BlockingQueue messageQueue) 
            this.messageQueue = messageQueue ;
        
        
        @Override
        public void run() 
            long messageCount = 0 ;
            try 
                while (true) 
                    final String message = "Message " + (++messageCount);
                    messageQueue.put(message);
                
             catch (InterruptedException exc) 
                System.out.println("Message producer interrupted: exiting.");
            
        
    
    
    public static void main(String[] args) 
        launch(args);
    

La mejor manera de realizar esto es mediante el uso de Task en JavaFx. Esta es, con mucho, la mejor técnica con la que me he encontrado para actualizar los controles de la interfaz de usuario en JavaFx.

Task task = new Task() 
    @Override public Void run() 
        static final int max = 1000000;
        for (int i=1; i<=max; i++) 
            updateProgress(i, max);
        
        return null;
    
;
ProgressBar bar = new ProgressBar();
bar.progressProperty().bind(task.progressProperty());
new Thread(task).start();

Si para ti ha resultado de utilidad nuestro artículo, sería de mucha ayuda si lo compartes con otros programadores de esta manera contrubuyes a difundir este contenido.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *