RequestFocus in TextField doesn't work - java

I use JavaFX 2.1 and I created GUI using FXML, in the controller of this GUI I added myTextField.requestFocus();.
But I always get the focus in the other control.

At the time of initialize() controls are not yet ready to handle focus.
You can try next trick:
#Override
public void initialize(URL url, ResourceBundle rb) {
Platform.runLater(new Runnable() {
#Override
public void run() {
tf.requestFocus();
}
});
}
For tricky complex applications (like Pavel_K has in the comments) you may want to repeat this routine several times and call method line next one:
private void requestFocusOrDieTrying(Node node) {
Platform.runLater(() -> {
if (!node.isFocused()) {
node.requestFocus();
requestFocusOrDieTrying(node);
}
});
}
Note this is the undocumented approach and it may be wise to add a limit for repetitions to avoid endless loop if something changed or broke in future Java releases. Better to lose focus than a whole app. :)
Example with the described threshold:
#Override
public void requestFocus() {
requestFocus( getNode(), 3 );
}
private void requestFocus( final Node node, final int max ) {
if( max > 0 ) {
runLater(
() -> {
if( !node.isFocused() ) {
node.requestFocus();
requestFocus( node, max - 1 );
}
}
);
}
}

The exact same answer as #Sergey Grinev. Make sure your version of java is up-to-date (JDK 1.8 or later).
Platform.runLater(()->myTextField.requestFocus());

If you requestFocus(); after initializing the scene, it will work!
Like this:
Stage stage = new Stage();
GridPane grid = new GridPane();
//... add buttons&stuff to pane
Scene scene = new Scene(grid, 800, 600);
TEXTFIELD.requestFocus();
stage.setScene(scene);
stage.show();
I hope this helps. :)

This can occur when the Scene property for the Node is not yet set.
Alas, the scene property can take a "long" time to be set.
The child node's scene property lags when a scene is first created, and also, when items are added to some parents, such as a TabPane (oddly some parents seem immune, I'm not sure why).
The correct strategy, which has always worked for me :
if (myNode.scene) == null {
// listen for the changes to the node's scene property,
// and request focus when it is set
} else {
myNode.requestFocus()
}
I have a handy Kotlin extension function which does this.
fun Node.requestFocusOnSceneAvailable() {
if (scene == null) {
val listener = object : ChangeListener<Scene> {
override fun changed(observable: ObservableValue<out Scene>?, oldValue: Scene?, newValue: Scene?) {
if (newValue != null) {
sceneProperty().removeListener(this)
requestFocus()
}
}
}
sceneProperty().addListener(listener)
} else {
requestFocus()
}
}
You can then call it from within you code like so :
myNode.requestFocusOnSceneAvailable()
Perhaps somebody would like to translate it to Java.

I ran into the same problem using JavaFX 11 and solved it in a similar way that nickthecoder proposed.
ChangeListener<Scene> sceneListener = new ChangeListener<Scene>() {
#Override
public void changed(ObservableValue<? extends Scene> observable, Scene oldValue, Scene newValue) {
if (newValue != null) {
editInput.requestFocus();
editInput.sceneProperty().removeListener(this);
}
}
};
editInput.sceneProperty().addListener(sceneListener);
Basicly just add a listener to the sceneProperty of the node and in that listener request focus once the scene is set. I also wrote it in such a way that the listener will be removed after it is invoked.

I would rather using timer to enforce focus to text field. The process of checking whether or not the text field has focus, is done in a separate (background) thread. While the process of requesting focus is done in the GUI thread, with the help of Platform.runLater().
//I'd rather using timer to enforce focus
Timer checkIfTFIsFocusedTimer = new Timer();
TimerTask checkIfTFIsFocusedTask = new TimerTask() {
#Override
public void run() {
if (!textField.isFocused()) {
Platform.runLater(() -> {
textField.requestFocus();
});
} else {
checkIfTFIsFocusedTimer.cancel();
}
}
};
checkIfTFIsFocusedTimer
.scheduleAtFixedRate(checkIfTFIsFocusedTask,
0, 100);

The older answers account for the case of Platform.runLater not working, but this answer covers also the case of multiple requests on multiple nodes.
Problem is: the order in which the scene property becomes non-null for the nodes, and thence the order in which the added listeners get called, is not necessarily the same as the order in which the two listeners were added. And so this order:
requestFocusOnSceneAvailable(node1)
requestFocusOnSceneAvailable(node2)
might unexpectedly result in this order:
node2.requestFocus()
node1.requestFocus()
A solution requires having the listeners call requestFocus() only on the most recent node, which can be tracked with a static variable:
private static Node nodeToRequestFocusOnOnceSceneAvailable;
public static void requestFocusOnceSceneAvailable(Node node) {
// Remember this node as the latest node requested to receive focus.
nodeToRequestFocusOnOnceSceneAvailable = node;
// Schedule the focus request to happen whenever
// JavaFX finally adds the node to the scene.
Listeners.addAndFire(node.sceneProperty(), new ChangeListener<Scene>() {
#Override
public void changed(ObservableValue<? extends Scene> observable, Scene oldScene, Scene newScene) {
if (newScene != null) {
if (node == nodeToRequestFocusOnOnceSceneAvailable) {
node.requestFocus();
// We no longer need to remember this node,
// since its focus has been requested.
nodeToRequestFocusOnOnceSceneAvailable = null;
}
// We no longer need the listener
// after it has run once.
observable.removeListener(this);
}
}
});
}
Note, this solution assumes there is only one scene.

Related

Is there a better method than onMouseExited that detects when it exits no matter how fast it moves?

Can someone tell me how I can do so that an animation is made in a pane when the mouse is over said pane and make another when it is the opposite? I tried using the onMouseEntered and onMouseExited methods, with the first one there was no problem, however if we remove the mouse very quickly or abruptly, the program will not detect the exit of the mouse and therefore the animation will not be executed, can someone help me to solve this bug? please
first add 2 methods to an events from the scene builder for the pane, these are said methods
public void scale1 () {
double currentScaleX = pane1.getScaleX();
double currentScaleY = pane1.getScaleY();
if(currentScaleX ==1.0 && currentScaleY ==1.0) {
scale.setNode(pane1);
scale.setDuration(Duration.millis(100));
scale.setByX(0.75);
scale.setByY(0.75);
scale.play();
}
}
public void scale1Re () {
double currentScaleX = pane1.getScaleX();
double currentScaleY = pane1.getScaleY();
if(currentScaleX >1.0 && currentScaleY >1.0) {
scale.setNode(pane1);
scale.setDuration(Duration.millis(100));
scale.setByX(-0.75);
scale.setByY(-0.75);
scale.play();
}
}
However, when executed it works, but if we move the mouse very quickly, it will not detect the change and sometimes it will only execute the first methodIt is not seen in the screenshot, but my mouse is out of the pane
I even tried to make a mouselistener class in my controller class so that it detects when the mouse is out of the pane or something and automatically executes the second method, but I think I did it wrong
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
pane1.setOnMouseExited(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
Bounds bounds = principalPane.getBoundsInParent();
if (bounds.contains(mouseEvent.getSceneX(), mouseEvent.getSceneY())) {
scale1Re();
}
}
});
}
It's not the MouseExited that's being missed, it's the in progress transitions that cause the problem.
Two things need to be done. First, the ScaleTransition has be set to absolutes targets, so setToX() not setByX(). Second, change the play() to playFromStart(). Like so...
class MouseOverExample : Application() {
override fun start(stage: Stage) {
stage.scene = Scene(createContent())
stage.show()
}
private fun createContent(): Region = HBox(40.0).apply {
children += listOf(animatedPane(100.0), animatedPane(500.0), animatedPane(1200.0))
padding = Insets(40.0)
}
private fun animatedPane(transitionDuration: Double): Region = StackPane().apply {
children += Label("H").apply { style = "-fx-font-size: 40px;" }
style = "-fx-border-color: blue; -fx-border-width: 5px;"
padding = Insets(20.0)
val transition: ScaleTransition = ScaleTransition().also {
it.node = this
it.duration = Duration(transitionDuration)
}
onMouseEntered = EventHandler {
with(transition) {
toX = 1.5
toY = 1.5
playFromStart()
}
}
onMouseExited = EventHandler {
with(transition) {
toX = 1.0
toY = 1.0
playFromStart()
}
}
}
}
fun main() {
Application.launch(MouseOverExample::class.java)
}
This is Kotlin, but it should be easy enough to figure out.
This gives three Panes, each with a transition associated with it, and the durations are all different so that you can see how mouse speed has an impact.
At 100ms, a partial grow followed by a shrink looks just fine. At 1200ms, if the mouse exits and the grow transition stops after, say, 100ms then it will still take 1200ms to shrink back to the original size, which is kind of goofy. But in every case the Pane goes back to its original size no matter how fast you sweep the mouse through it.

JavaFX scroll started and ended

I have a very costly action to do on a mouse scroll on a pane. I currently use
pane.setOnScroll({myMethod()}).
The problem is that if you scroll a lot it computes everything many times. So what I want is to do my actions only when the scroll is finished. I hoped to use setOnScrollStarted, save the starting value and setOnScrollFinished to do my actions.
But I don't know why these two methods are never called. As a test I used
pane.setOnScroll({System.out.println("proof of action"});
and it was clearly never called.
Any idea on how to call my method only at the end of the scroll?
Thanks in advance, A
From the javadoc of ScrollEvent (emphasis mine):
When the scrolling is produced by a touch gesture (such as dragging a
finger over a touch screen), it is surrounded by the SCROLL_STARTED
and SCROLL_FINISHED events. Changing number of involved touch points
during the scrolling is considered a new gesture, so the pair of
SCROLL_FINISHED and SCROLL_STARTED notifications is delivered each
time the touchCount changes. When the scrolling is caused by a mouse
wheel rotation, only a one-time SCROLL event is delivered, without the
started/finished surroundings.
A possible workaround:
Increment a counter variable every time a scroll is detected. In the listener start a new thread that waits 1 second and performs the action that you want only if the counter equals to 1 (the last scrolling) then decrements the counter.
I created a Gist, but I copy here the code:
public class ScrollablePane extends Pane {
private Integer scrollCounter = 0;
private final ObjectProperty<EventHandler<? super ScrollEvent>> onScrollEnded = new SimpleObjectProperty<>();
public final ObjectProperty<EventHandler<? super ScrollEvent>> onScrollEndedProperty() {
return onScrollEnded;
}
public ScrollablePane() {
this.setOnScroll(e -> {
scrollCounter++;
Thread th = new Thread(() -> {
try {
Thread.sleep(1000);
if (scrollCounter == 1)
onScrollEnded.get().handle(e);
scrollCounter--;
} catch (Exception e1) {
e1.printStackTrace();
}
});
th.setDaemon(true);
th.start();
});
}
public void setOnScrollEnded(EventHandler<? super ScrollEvent> handler) {
onScrollEnded.setValue(handler);
}
}
To use it:
public class MyApplication extends Application {
#Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
Scene scene = new Scene(root, 400, 400);
ScrollablePane pane = new ScrollablePane();
pane.setOnScrollEnded(e -> System.out.println("Scroll just has been ended"));
root.setCenter(pane);
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}

JavaFX. stop everything, except one process

public void close(){
KeyValue opacity = new KeyValue(canvas
.opacityProperty(), 0);
KeyFrame end = new KeyFrame(Duration.millis(500),
opacity);
Timeline t = new Timeline(end);
t.play();
t.setOnFinished(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
stage.close();
}
});
}
I have such a method, that changes opacity before quitting from app. It works fine, but during these 0.5 seconds Im still able to interact with UI. The question is how to stop everything except this Timeline execution before quitting?
Depends on how you set up your project. You could add a pane over the entire scene. Well, that doesn't prevent anyone from using the keyboard. So an alternative would be to consume all events like this:
root.addEventFilter(Event.ANY, e -> {
e.consume();
});
Call
stage.getScene().getRoot().setDisable(true);
before you start the timeline

Obtaining a de-focus event other than from Stage - JavaFX

I have a piece of code much like this:
package blah;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.TextInputControl;
import javafx.stage.Stage;
public class SimpleExample {
TextInputControl textFieldForWork;
LocalTextChangeListener localTextChangeListener;
public SimpleExample(TextInputControl textFieldForWork, Stage s) {
this.textFieldForWork = textFieldForWork;
localTextChangeListener = new LocalTextChangeListener();
System.out.println("Creating new focus listener for TextField component");
LocalFocusListener localFocusListener = new LocalFocusListener();
s.focusedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if (observable.getValue().toString().equals("false")) {
System.out.println("Removing TextField focus listener");
textFieldForWork.focusedProperty().removeListener(localFocusListener);
} else {
System.out.println("Adding TextField focus listener");
textFieldForWork.focusedProperty().addListener(localFocusListener);
}
}
});
}
private class LocalFocusListener implements ChangeListener {
#Override
public void changed(ObservableValue observable, Object oldValue, Object newValue) {
if (observable.getValue().toString().equals("true")) {
System.out.println("Adding text change listener");
textFieldForWork.textProperty().addListener(localTextChangeListener);
} else {
System.out.println("Removing text change listener");
textFieldForWork.textProperty().removeListener(localTextChangeListener);
}
}
}
private class LocalTextChangeListener implements ChangeListener {
#Override
public void changed(ObservableValue observable, Object oldValue, Object newValue) {
System.out.println("Textfield changed - do processing");
}
}}
The purpose of this code is to fire a listener each time the user types something in the textfield. The other necessary function of this code, is that upon dialog defocus, the textfield listeners should be removed.
Any help appreciated!
I'm not sure I really understand why you need to observe the focused property of the stage. Can't you just register the listener with the text field once and leave it there? It won't be invoked unless the text changes.
If you really need the functionality you describe, you can do it. Here's a description of what's going on:
The focusedProperty of the text field tracks whether or not the Node with focus within the current scene graph is the text field. It is "local to the scene graph", which means it is independent of whether or not the window is the active or focused window.
The focusedProperty of the window tracks whether or not the window has focus. This changes if you move the application to the background, etc.
Obviously, at the point where you create the text field, is hasn't been added to a scene or window, so just doing
textFieldForWork.getScene().getWindow().focusedProperty().addListener(...)
won't work, because getScene() will return null at this point. Even if the scene is non-null, it might not yet belong to a window, so you may have getScene() non-null but getScene().getWindow() null at some point.
So what you actually want to do is observe the sequence of properties. Start with textFieldForWork.sceneProperty() and observe it; if it changes and is non-null, then observe textFieldForInput.getScene().windowProperty(); when that changes and is non-null, observe textFieldForInput.getScene().getWindow().focusedProperty().
You could handle this yourself, creating listeners for each step in the chain and adding and removing them as necessary, but the EasyBind framework has API that manages exactly this use case. Using EasyBind you can do
MonadicObservableValue<Boolean> stageFocused =
EasyBind.monadic(textFieldForWork.sceneProperty())
.flatMap(Scene::windowProperty)
.flatMap(Window::focusedProperty)
.orElse(false);
stageFocused.addListener((obs, wasFocused, isNowFocused) -> {
if (isNowFocused) {
// stage now has focus...
} else {
// stage has lost focus...
}
});
If you want to check the condition that the text field has focus and the window containing it has focus, you can do
BooleanBinding stageAndTextFieldFocused = Bindings.createBooleanBinding(() ->
stageFocused.get() && tf.isFocused(),
stageFocused, tf.focusedProperty());
with stageFocused as above. Then just do
stageAndTextFieldFocused.addListener((obs, wasFocused, isNowFocused) ->
{ /* etc ... */ });

closed javafx window handler

I have to write a method that will do something when the user closes a window. So far I managed to write this code but it does not work (i placed it in my initialize method in my controller) :
Scene scene = myTable.getScene();
Window window = null;
if (scene != null)
{
window = scene.getWindow();
System.out.println("scene is not null");
window.addEventHandler(WindowEvent.WINDOW_HIDDEN, new EventHandler<WindowEvent>
()
{
#Override
public void handle(WindowEvent w)
{
System.out.println("do somethong here");
};
});
Unfortunately Even my message "scene is not null does not get displayed. Does anyone have a better idea on how to do it?
If you want to do something when the user closes the window you should use the setOnCloseRequest() method like this :
window.setOnCloseRequest(new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent event) {
//do something
}
});
Now if scene is null then this code won't be executed and nothing will happen, maybe a little System.out.println(scene); before the test would help you debug this issue.
Add a change listener to the scene property of the table, and only add your event handler when the scene is changed to a non-null value.
As recommended by Marc, calling setOnCloseRequest or setOnHidden, is probably a better way to configure your EventHandler.

Categories

Resources