How to focus a specific node when selected tab changes in JavaFX? - java

I want to set focus to a specific node in the content of a Tab. I added a ChangeListener to selectedItem property as follows (assume that the class contains a field named secondNode of type Node):
tabPane.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Tab>() {
#Override
public void changed(ObservableValue<? extends Tab> observable, Tab oldValue, Tab newValue) {
if (newValue != null) {
Platform.runLater(new Runnable() {
#Override
public void run() {
secondNode.requestFocus();
}
});
}
}
});
However, this does not work. I assume the reason is because TabPane performs some additional actions, which affect focus, after the new tab has been selected (but, looking through TabPane source code, I can't figure out what). If I single-step through this code in a debugger, it works as expected, so it appears to be a race condition. If this is so, how can this be resolved?

You could try replacing the Runnable with a Task:
new Thread( new Task<Void>()
{
#Override
public Void call() throws Exception // This is NOT on FX thread
{
Thread.sleep(100);
return null;
}
#Override
public void succeeded() // This is called on FX thread.
{
secondNode.requestFocus();
}
}).start();

Related

JavaFX EventHandler - new Alert if boolean equals true

When the user has reached the end of this Tetris game I want a new alert to be opened.
In the model class I have a boolean that will switch to true if a new Tetris block cannot be spawned.
I'm working with model view presenter, so in the model is the boolean + getter and in the presenter a new alert will be created if the boolean returns true.
The question is how do I add this to the eventHandlers() in the presenter?
public Presenter(Model model, View view) {
this.model = model;
this.view = view;
addEventHandlers();
}
private void addEventHandlers() {
//view.setOnKeyPressed... this is for rotating the blocks to give you an example
}
JavaFX implements observable properties, which are extensions of the Java Bean pattern that support notification for invalidation and for changes to the underlying value. These are fundamental to the JavaFX library: all controls in JavaFX make use of these. So, for example, if you want to respond to changes to the text in a text field, you would do
myTextField.textProperty().addListener((observable, oldText, newText) -> {
// ... do something with newText (and perhaps oldText) here...
});
So you can just achieve this with a BooleanProperty (or similar) in your model class:
private final BooleanProperty gameEnded = new SimpleBooleanProperty();
public BooleanProperty gameEndedProperty() {
return gameEnded ;
}
public final boolean isGameEnded() {
return gameEndedProperty().get();
}
public final void setGameEnded(boolean gameEnded) {
gameEndedProperty().set(gameEnded);
}
Then you can do:
model.gameEndedProperty().addListener((obs, gameWasEnded, gameIsNowEnded) -> {
if (gameIsNowEnded) {
// show alert, etc...
}
});
See "Properties and Bindings" in the Oracle tutorial for more details, including bindings, etc. You might also consider a ReadOnlyBooleanWrapper if you don't want the property to be changed from outside the class it is defined in.
public void setEind() {
boolean oldValue = this.eind;
eind = true;
System.out.println(eind);
firePropertyChange("eind",oldValue,eind);
}
private final List<PropertyChangeListener> listeners = new ArrayList<>();
public void addPropertyChangeListener(PropertyChangeListener listener) {
listeners.add(listener);
}
public void firePropertyChange(String property, Object oldValue, Object newValue) {
for (PropertyChangeListener listener : listeners) {
listener.propertyChange(new PropertyChangeEvent(this,property,oldValue,newValue));
}
}
spel.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
val.stop();
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Game over!");
alert.setContentText("Enter your name in the next window for the highscores.");
alert.setTitle("End");
alert.show();
}
});

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?
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.

Show always the last line in a TextArea

I have a JavaFx TextArea where i update the text from another thread.
/**
* Start a new thread to update the text in the textarea. The progressmanager is called in a loop to deleiver the updated text
*/
private void startTextAreaUpdate() {
final Task<Void> updateTextAreaTask = new Task<Void>() {
#Override
protected Void call() throws Exception {
// Start updating the progresstext
while (!prog.getIsDone()) {
updateMessage(prog.getProgressText());
Thread.sleep(100);
}
return null;
}
};
//Here the TextArea text is bound to the task
taProgressText.textProperty()
.bind(updateTextAreaTask.messageProperty());
// Start the action in a new thread
Thread th = new Thread(updateTextAreaTask);
th.setDaemon(true);
th.start();
}
Because the text doesn't fit in the textarea i always want to show the last line of the text.
In the main thread i add a ChangeListener.
taProgressText.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable,
String oldValue, String newValue) {
int lastPos = taProgressText.getText().length();
taProgressText.positionCaret(lastPos);
}
});
However the position doesn't change. What is going wrong here?
I think the position of the carat changes, but it doesn't actually scroll.
Try this (ugly hack...) instead of the binding and the current listener you have:
updateTextAreaTask.messageProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> obs, String oldMessage, String newMessage) {
taProgressText.setText(newMessage);
ScrollBar scrollBar = (ScrollBar) taProgressText.lookup(".scroll-bar:vertical");
if (scrollBar != null) {
scrollBar.setValue(scrollBar.getMax());
}
}
});

JavaFX 2 Window event handling in controllers

So I am trying to handle WINDOW_SHOWN event from my controller with code like this:
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
initializeDatePickers();
System.out.println("payer number in initialize: " + payerNumber);
URL location = getClass().getResource("/createUser.fxml");
FXMLLoader loader = new FXMLLoader();
try {
Parent root = (Parent) loader.load(location.openStream());
root.getScene().getWindow().setOnShown(new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent event) {
System.out.println("ONSHOWN");
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
But all I've got was endless cycle and program crash.
The code below didn't work either, it returns NullPointerException:
#FXML private AnchorPane createUserDialog; //my root pane
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
createUserDialog.getScene().getWindow().addEventHandler(WindowEvent.WINDOW_SHOWN,
new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent window) {
System.out.println("ONSHOWN");
}
});
}
Implementing WindowEvent interface didn't work at all, don't know why.
So, how could I handle this event? And why I've got NullPointerException? In docs said that initialize() calling only after root pane completely processed.
When the initialize() method is being executed, the root pane is completely constructed but is not added to a scene, or a window. (The initialize() method is executed as part of the execution of your FXMLLoader's load() method; check the code where you call that and you will see that you add the root to a scene and place it in a window after that.) So during the execution of intialize(), root.getScene() will return null.
You can use a Binding to check when the window changes and attach a listener to it:
final EventHandler<WindowEvent> shownHandler = new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent event) {
System.out.println("Shown");
}
};
Bindings.<Window>select(createUserDialog.sceneProperty(), "window").addListener(new ChangeListener<Window>() {
#Override
public void changed(ObservableValue<? extends Window> observable,
Window oldValue, Window newValue) {
if (oldValue != null) {
oldValue.removeEventHandler(WindowEvent.WINDOW_SHOWN, shownHandler);
}
if (newValue != null) {
newValue.addEventHandler(WindowEvent.WINDOW_SHOWN, shownHandler);
}
}
});
This code assumes the root is only ever added to one window; in the unlikely event you're taking the root out of one window and putting it in another during your application life cycle, you would need to remove the listener from the old window. If you need this I'll update the code, but it makes it more complex.

Update components GUI in JavaFX

I need to update some components (Labels, ProgressBar, button) from an handle function in javafx.
The dialog is a modal dialog and it's used to perform sequentially some operations.
#FXML public void updateHandle(ActionEvent action){
buttonSend.setDisable(true);
/* Operation 1 */
progressBar.setProgress(0.05);
label.setText("Init..");
myInitFunction();
myVar = new var(); //global
/* Op2 */
progressBar.setProgress(0.10);
label.setText("Check connection..");
myConnFunction();
// ....
// ....
}
The problem is that all my functions are correctly processed, but the elements on the GUI didn't change.
EDIT
I tried to use Platform.runlater but it seems to don't work...
void updateLabelLater(final Label label, final String text) {
Platform.runLater(new Runnable() {
#Override public void run() {
label.setGraphic(null);
label.setText(text);
}
});
}
void updateProgressBar(final ProgressBar progressBar, final double val){
Platform.runLater(new Runnable() {
#Override public void run() {
progressBar.setProgress(val);
}
});
}
Is updateHandle running on the Event Thread? I didn't bother with FXML because of tooling issues so this isn't much of an answer. (But hopefully it helps!)
//Pseudo Code
public doLongRuningOperation(final Object ... objects){
final Thread longRunning = new Thread(){
public void run(){
update("this");
sleep(1000); //Pause/do something etc...
update("should");
sleep(1000);
update("update");
System.err.println("completed");
}
};
longRunning.start(); //Start this process
}
//Rejoin the UI Thread and update it...
public void update(final String text){
//reference to label 'lbl'
Platform.runLater(new Runnable(){
lbl.setText(text);
});
}

Categories