JavaFX ImageView throwing IllegalStateException

I've a ImageView within my view and try to display a WritableImage instance with it. I am drawing it within an outher thread and pass it to the view by listening to ObjectProperty's change event.

public void changed(ObservableValue<? extends Image> observable,
        Image oldValue, Image newValue) {
    this.imageView.setImage(newValue);
}

The imageView should be ready to recieve an image, it is shown by my mainView. But it is thrwoing an IllegalStateException from

Exception in thread "Thread-4" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-4

Does anyone can explain this?

Upvotes: 0

Views: 239

Answers (1)

James_D
James_D

Reputation: 209330

The exception basically tells you what the problem is: you are changing the state of part of the scene graph from a thread other than the FX Application Thread. The reason for this is that listener methods are invoked on the same thread that changes the property.

You have a couple of options for fixing this: one is to just use Platform.runLater(...). You can either do this in the listener:

public void changed(ObservableValue<? extends Image> observable,
        Image oldValue, Image newValue) {
    Platform.runLater(new Runnable() {
        @Override
        public void run() {
            this.imageView.setImage(newValue);
        }
    });
}

or you can do the same thing to set the value of your property on the FX Application Thread.

You haven't shown much code, but it may also be possible for you to use a Task to compute the Image. So instead of something like:

new Thread(new Runnable() {
    @Override
    public void run() {
        WritableImage image = new WritableImage(...);
        /// draw on image....
        myImageProperty.set(image);
    }
});

you can do something like

Task<Image> imageTask = new Task<Image>() {
    @Override
    public Image call() {
        WritableImage image = new WritableImage(...);
        // draw on image....
        return image ;
    }
});

imageTask.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
    @Override
    public void handle(WorkerStateEvent event) {
        myImageProperty.set(imageTask.getValue());
    }
});

new Thread(imageTask).start();

(This is much cleaner in Java 8; I posted Java 7 compatible code as you used that style in the question).

Here you avoid the low-level API (Platform.runLater()), instead using one of the callback methods (setOnSucceeded) from Task, which is guaranteed to be called on the FX Application Thread.

Upvotes: 4

Related Questions