This discussion is archived
7 Replies Latest reply: Dec 11, 2012 4:15 PM by jsmith RSS

Can't update GridPane from Button Action

936835 Newbie
Currently Being Moderated
I'm making a simple gui for Conway's Game of Life with a grid (GridPane) and a simple toolbar with Start/Stop buttons. My start button action looks like this:
startButton.setOnAction(new EventHandler<ActionEvent>() {
        @Override
        public void handle(ActionEvent actionEvent) {
            grid.change();
        }
    });
So when i click it the grid updates ones as i expect. But i when i add something like a Service/Platform.runLater/Runnable inside it evaluates generations, grid model, but refuses to update the grid view. My Service code looks like this:
 
private Service task = new Service(){
    @Override
    protected Task createTask() {
        System.out.println("Task created");
        return new Task<Void>() {
            @Override
            protected Void call() throws Exception {
                for (int i = 0; i < 3; i++) {
                    System.out.println("calling");
                    Platform.runLater(new Runnable(){
                        @Override
                        public void run() {
                            System.out.println("running");
                           grid.changeGeneration();
                            try {
                                Thread.sleep(1500L);
                            } catch (InterruptedException e) {
                                e.printStackTrace();  
                            }
                        }
                    });
                }
                return null;
            }
        };
    }
};
It prints Task created calling calling calling running running running to the console, but doesn't update the GridPane after each running, only after everything is done it updates it (in this case after the third "running"). Where am i making a mistake and how to fix this. I don't want to use Swing =)

Same question on [http://stackoverflow.com/questions/13820734/cant-update-gridpane]

Edited by: Riddle on 11.12.2012 7:21
  • 1. Re: Can't update GridPane from Button Action
    James_D Guru
    Currently Being Moderated
    I think you need to call Thread.sleep(...) from outside the Platform.runLater(...). It's usually a very bad idea to sleep on the FX Application thread, but I think you logically want this anyway:
    private Service task = new Service(){
        @Override
        protected Task createTask() {
            System.out.println("Task created");
            return new Task<Void>() {
                @Override
                protected Void call() throws Exception {
                    for (int i = 0; i < 3; i++) {
                        System.out.println("calling");
                        Platform.runLater(new Runnable(){
                            @Override
                            public void run() {
                                System.out.println("running");
                               grid.changeGeneration();
                            }
                        });
                        Thread.sleep(1500L);
                    }
                    return null;
                }
            };
        }
    };
  • 2. Re: Can't update GridPane from Button Action
    936835 Newbie
    Currently Being Moderated
    No, moving Thread.sleep() to call() method, doesn't change anything. And how then i can delay rendering grid between generations?
  • 3. Re: Can't update GridPane from Button Action
    James_D Guru
    Currently Being Moderated
    It'll pause because you invoke a delay between submitting the Runnables to the FX Application Thread.

    I can't answer why it's still coalescing those calls without knowing more about what your grid.changeGeneration() method does. Here's a very simple example of the same structure which updates a label three times, with the 1.5 second pause you want:
    import javafx.application.Application;
    import javafx.application.Platform;
    import javafx.concurrent.Service;
    import javafx.concurrent.Task;
    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.HBox;
    import javafx.stage.Stage;
    
    public class SimpleCounter extends Application {
    
      @Override
      public void start(Stage primaryStage) {
        final HBox root = new HBox(5);
        final Button start = new Button("Start");
        final Label label = new Label("Press Start to start...");
        root.getChildren().addAll(start, label);
        final Service<Void> task = new Service<Void>() {
          @Override
          protected Task<Void> createTask() {
            return new Task<Void>() {
              @Override
              protected Void call() throws Exception {
                for (int i = 0; i < 3; i++) {
                  System.out.println("Calling");
                  final int count = i;
                  Platform.runLater(new Runnable() {
                    @Override
                    public void run() {
                      System.out.println("Running");
                      label.setText("Generation: " + count);
                    }
                  });
                  Thread.sleep(1500);
                }
                return null;
              }
            };
          }
        };
        start.setOnAction(new EventHandler<ActionEvent>() {
          @Override
          public void handle(ActionEvent event) {
            task.start();
          }
        });
    
        Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.show();
      }
    
      public static void main(String[] args) {
        launch(args);
      }
    }
  • 4. Re: Can't update GridPane from Button Action
    936835 Newbie
    Currently Being Moderated
    public void changeGeneration() {
            Cell[][] newGen = new Cell[rows][cols];
            Cell cell;
            for (int i = 0; i < newGen.length; i++) {
                for (int j = 0; j < newGen.length; j++) {
    cell = new Cell(destiny(cells, i, j));
    newGen[i][j] = cell;
    add(cell, i, j);
    }
    }
    curGen = newGen;
    }
    My grid class is a subclass of GridPane
    
    So when i click button it's working, but not from other Runnable
    
    Edited by: Riddle on 11.12.2012 9:11                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
  • 5. Re: Can't update GridPane from Button Action
    James_D Guru
    Currently Being Moderated
    Assuming the surrounding logic and the layout is working, I don't see why this doesn't do what you want. You could conceivably be flooding the FX Application thread with all those calls, but I can't imagine any implementation of a single generation of Life at any reasonable size would take any significant time. So no clue. I don't know if it's possible to reduce this to a concise, complete executable which shows the same problem.

    One kind of unrelated thing you should be aware of, though: GridPane will allow multiple nodes to coexist in the same space in the grid. Their z-orders are determined by the order in which they're added. So it looks like you are piling cells on top of cells at each generation (you're calling add and never calling remove); doing this you're going to run out of memory, and possibly slow the rendering down, at some point.

    I think my strategy for this would be to create a new GridPane instance for each generation, then replace it in your overall layout. This is nice for the threading too, because you can generate the entire new grid in an application thread, then just insert it into the layout in a Platform.runLater(...) call. This will at a minimum increase responsiveness, and possibly keeping the FX Application Thread so much cleaner will fix the problem you're seeing.
  • 6. Re: Can't update GridPane from Button Action
    936835 Newbie
    Currently Being Moderated
    Thanks for the idea.
    I've moved cells rendering (clear them and then add) in outer method and now it is working as it should.
  • 7. Re: Can't update GridPane from Button Action
    jsmith Guru
    Currently Being Moderated
    Although the Server/Task/RunLater combination works, I'd be inclined to use a Timeline for this and not have to worry about any kind of potential thread safety issues:
    import javafx.animation.*;
    import javafx.application.Application;
    import javafx.beans.property.*;
    import javafx.event.*;
    import javafx.geometry.Insets;
    import javafx.scene.Scene;
    import javafx.scene.control.*;
    import javafx.scene.layout.HBox;
    import javafx.stage.Stage;
    import javafx.util.Duration;
     
    public class SimpleCounterTransition extends Application { 
      final Label label = new Label("Press Start to start...");
      final LongProperty generationCount = new SimpleLongProperty(0);
      
      @Override public void start(Stage primaryStage) {
        final Button start = new Button("Start");
    
        final Timeline timeline = 
            new Timeline(
                new KeyFrame(Duration.ZERO, new EventHandler<ActionEvent>() {
                    @Override public void handle(ActionEvent t) {
                        nextGeneration();
                    }
                }),
                new KeyFrame(Duration.millis(1000), (KeyValue) null)
            );
        timeline.setCycleCount(Timeline.INDEFINITE);
        
        start.setOnAction(new EventHandler<ActionEvent>() {
          @Override public void handle(ActionEvent event) {
            timeline.play();
            start.setDisable(true);
          }
        });
     
        final HBox root = new HBox(5);
        root.setPadding(new Insets(5));   
        root.getChildren().addAll(start, label);
        primaryStage.setScene(new Scene(root));
        primaryStage.show();
      }
      
      private void nextGeneration() {
        label.setText("Generation: " + generationCount.get());
        generationCount.set(generationCount.get() + 1);
      }
     
      public static void main(String[] args) { launch(args); }
    }

Legend

  • Correct Answers - 10 points
  • Helpful Answers - 5 points