I'm a newbie to JavaFx. In my JavaFX application I have set onAction property and it works fine when I press the button using mouse. I want to fire the same even when user press Enter on button. I know I can use a even handler to do that.
But when I read the onAction JavaDoc it says that this event get fire by a key press.
Property description:
The button's action, which is invoked whenever
the button is fired. This may be due to the user clicking on the
button with the mouse, or by a touch event, or by a key press, or if
the developer programmatically invokes the fire() method.
But when I press Enter key nothing happens. Is it error in documentation? Are there any other way to achieve that without adding alistener to the button?
P.S
After the comments I checked with space key then it get fired. But I want to set that to Enter key.
I have many buttons. I tried button.setDefaultButton(true); but it is not get fired. I think that is becacuse there are more than one button. If I set it just to a single button it works fine. How to set that to multiple buttons?
You can dynamically change the default button property of the currently focused button by using binding
btn.defaultButtonProperty().bind(btn.focusedProperty());
If you want to apply this to every Button in your program you can subclass the JavaFX-Button and bind this in the constructor. In your fxml-File you'll need to include your custom Button.
I wrote the following subclass:
public class FocusedButton extends javafx.scene.control.Button {
public FocusedButton ( ) {
super ( );
bindFocusToDefault ( );
}
public FocusedButton ( String text ) {
super ( text );
bindFocusToDefault ( );
}
public FocusedButton ( String text, Node graphic ) {
super ( text, graphic );
bindFocusToDefault ( );
}
private void bindFocusToDefault ( ) {
defaultButtonProperty().bind(focusedProperty());
}
}
To use this Code you will need to include your custom class in the fxml-File:
<?import your-package.*?>
If you want to use the Scene Builder things get a little bit more difficult: You'll need to export your custom Button in a jar-file and add this to Scene Builder as described here
To override the Enter key press behavior I use the function below calling it in the scene's key press event filter:
public static void overrideEnterKeyPressEvent(KeyEvent evt) {
EventTarget eventTarget = evt.getTarget();
if ((eventTarget instanceof TextArea) || (eventTarget instanceof TableView)) {
return;
}
if (eventTarget instanceof Button) {
Platform.runLater(() -> {
KeyEvent newEventPressed = new KeyEvent(KeyEvent.KEY_PRESSED, " ", " ", KeyCode.SPACE, false, false, false, false);
Event.fireEvent(eventTarget, newEventPressed);
KeyEvent newEventReleased = new KeyEvent(KeyEvent.KEY_RELEASED, " ", " ", KeyCode.SPACE, false, false, false, false);
Event.fireEvent(eventTarget, newEventReleased);
});
evt.consume();
return;
}
Platform.runLater(() -> {
KeyEvent tabEvent = new KeyEvent(KeyEvent.KEY_PRESSED, "", "\t", KeyCode.TAB, evt.isShiftDown(), false, false, false);
Event.fireEvent(eventTarget, tabEvent);
});
evt.consume();
}
Based on the event's target the function works as follows. For a TextArea or TableView, it's a NoOp. For a button, it consumes the Enter press event and fires Space key press and release events. And for all the other controls, it also consumes the Enter press event and fires a Tab event so pressing Enter moves focus to the next control just like Tab.
Then you just register an event filter for the whole scene:
scene.addEventFilter(KeyEvent.KEY_PRESSED, this::onSceneKeyPressedFilter);
And the event filter looks like:
private void onSceneKeyPressedFilter(KeyEvent evt) {
switch (evt.getCode()) {
case ENTER:
if (evt.isControlDown() && FxTools.isAncestorNodeTargeted(evt.getTarget(), fxHBoxInputAp)) {
return; //let the events for the fxHBoxInputAp pane pass through
}
overrideEnterKeyPressEvent(evt);
break;
...
default:
break;
}
}
----- edit because I forgot to include the isAncestorNodeTargeted() function; thanks for the comment, Robert -----
public static boolean isDescendantOf(Node node, Node ancestor) {
while ((node != null) && (node != ancestor)) {
node = node.getParent();
}
return (node != null);
}
public static boolean isAncestorNodeTargeted(EventTarget target, Node ancestor) {
return (target instanceof Node) ? isDescendantOf((Node) target, ancestor) : false;
}
Related
When I receive a ChangeEvent from my JSpinner, I'd like to detect if user used the arrows to increase/decrease the number value, or directly typed a new number.
What would be the best approach to do this ?
EDIT: for now my non-reliable solution is just to save the last JSpinner changed value, and if new change value is +/- equals to the step value, then I assume user clicked on an arrow. It works except if user typed a value which is equals to (oldValue +/- step).
EDIT: why ?
I want to reproduce the behavior found in Midi editors of several famous DAWs. The JSpinner represents the velocity (0-127) of selected notes. It shows the velocity of the first selected note. Usually notes velocity differ. When you increase with arrow, you want to increase all selected notes by the same amount. When you type in a new value, you want all velocities to be reset to this value.
Distinguishing the trigger of a value change is not supported - the only value-related event fired by JSpinner is a ChangeEvent which carries no state except the source. We need another type of listener or a combination of listeners.
First thought: listen to changes of the editor's textField, f.i. an actionListener or a propertyChangeListener on the value property. This doesn't work, mainly because
both change- and propertyChangeListener are fired always (change before property)
actionListener is not fired on focusLost
Second thought: go dirty and dig into implementation details to hook into the action fired by the arrow buttons.
The idea:
look up the action for increment/decrement from the spinner's actionMap: this is the same as the arrows' actions and also used by up/down keys (which I assume not counting a "editing")
for each, create a wrapper that sets a flag before delegating to super
put that wrapper into the spinner's actionMap
look up the arrow buttons in the spinner's children and replace their respective actionListener with the wrapper
Client code would change the tweak's changeListener to acts according to the flag as appropriate.
Some code doing the tweaking (beware: not formally tested!):
public static class SpinnerTweaker {
private JSpinner spinner;
private boolean wasButtonAction;
private Object oldValue;
public SpinnerTweaker(JSpinner spinner) {
this.spinner = spinner;
AbstractAction incrementDelegate = createDelegate("increment");
spinner.getActionMap().put("increment", incrementDelegate);
AbstractAction decrementDelegate = createDelegate("decrement");
spinner.getActionMap().put("decrement", decrementDelegate);
// replacing arrow button's action
Component[] components = spinner.getComponents();
for (Component component : components) {
if (component instanceof JButton) {
if (component.getName() == "Spinner.nextButton") {
ActionListener[] actions = ((JButton) component).getActionListeners();
ActionListener uiAction = actions[0];
((JButton) component).removeActionListener(uiAction);
((JButton) component).addActionListener(incrementDelegate);
}
if (component.getName() == "Spinner.previousButton") {
ActionListener[] actions = ((JButton) component).getActionListeners();
ActionListener uiAction = actions[0];
((JButton) component).removeActionListener(uiAction);
((JButton) component).addActionListener(decrementDelegate);
}
}
}
spinner.addChangeListener(e -> {
if (wasButtonAction) {
System.out.println("value changed by button: " + spinner.getValue());
} else {
System.out.println("value changed by editing: " + spinner.getValue());
}
wasButtonAction = false;
});
}
protected AbstractAction createDelegate(String actionCommand) {
// hooking into original button action to set button flag
AbstractAction action = (AbstractAction) spinner.getActionMap().get(actionCommand);
AbstractAction delegate = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
oldValue = spinner.getValue();
wasButtonAction = true;
action.actionPerformed(e);
// hit min/max - TBD: needs testing!
if (oldValue.equals(spinner.getValue())) {
wasButtonAction = false;
}
}
};
return delegate;
}
}
#kleopatra solution works but I found a simpler solution.
The trick is that commitEdit() is only called internally by JSpinner when change results from the increment or decrement action.
public class Spinner2 extends JSpinner
{
// To be checked by ChangeListener after receiving the ChangeEvent
public boolean wasManualEdit=true;
#Override
public void commitEdit() throws ParseException
{
wasManualEdit = false;
super.commitEdit();
}
#Override
protected void fireStateChanged()
{
super.fireStateChanged();
wasManualEdit = true;
}
}
I'm working on a project where we use javafx, and I've come across a problem I just don't know how to solve. I'm fairly new to the whole gui thing, but this seem to be a pretty easy problem to solve, so I don't know why I can't seem to find anything useful online.
The app is composed of a lot of tabs, and we would like the tabs to call a method on the pane whenever a new tab is selected. This method would then update a combobox or talbelview depending on the specific tab.
In the class where we define the tabs and add them to a tabPane, we're able to get the selected tab by adding a listener to the tabPane, but I'm not sure if the actual Tab object got access to the functions on the pane, or anything else that might be useful.
I image the best way to solve this would be to add a listener to a "this tab/pane is selected/in-focus", but I haven't found anything like that yet.
You could add a Runnable as userData to each Tab that requires such updates and use the selection model add a listener to the selected tab that retrieves the user data and triggers the update when the tab selection changes.
Example
#Override
public void start(Stage primaryStage) {
final Label countLabel = new Label("You visited this tab 0 time(s) before.");
// increments visit count every time the tab is activated
Runnable labelUpdater = new Runnable() {
private int count = 0;
#Override
public void run() {
countLabel.setText("You visited this tab " + (++count) + " time(s) before.");
}
};
Tab tab1 = new Tab("tab1");
tab1.setContent(countLabel);
// add runnable as user data
tab1.setUserData(labelUpdater);
TabPane tabPane = new TabPane(tab1, new Tab("tab2"));
// execute update when a newly selected tab contains a updater
tabPane.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
if (newValue != null) {
Runnable updater = (Runnable) newValue.getUserData();
if (updater != null) {
updater.run();
}
}
});
Scene scene = new Scene(tabPane, 300, 300);
primaryStage.setScene(scene);
primaryStage.show();
}
You could also set the updater as user data to the Tab's content, if this is more convenient for you:
// tab1.setUserData(labelUpdater);
countLabel.setUserData(labelUpdater);
TabPane tabPane = new TabPane(tab1, new Tab("tab2"));
tabPane.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
if (newValue != null) {
Node content = newValue.getContent();
if (content != null) {
Runnable updater = (Runnable) content.getUserData();
if (updater != null) {
updater.run();
}
}
}
});
If you look in here you can under field summary the event you are looking for ie. SELECTION_CHANGED_EVENT. Just add a listener for this event for each of your tabs and you should be golden.
Basically, I have a okayButton that sits in a stage and when it is clicked , it performs a list of tasks. Now I want to bind the Enter key to this button such that when it is clicked OR the ENTER key is pressed, it performs a list of tasks.
okayButton.setOnAction(e -> {
.........
}
});
How can I do that ? I have read the following post already. However, it did not help me to achieve what I want to do.
First, set a hanlder on your button :
okayButton.setOnAction(e -> {
......
});
If the button has the focus, pressing Enter will automatically call this handler. Otherwise, you can do this in your start method :
#Override
public void start(Stage primaryStage) {
// ...
Node root = ...;
setGlobalEventHandler(root);
Scene scene = new Scene(root, 0, 0);
primaryStage.setScene(scene);
primaryStage.show();
}
private void setGlobalEventHandler(Node root) {
root.addEventHandler(KeyEvent.KEY_PRESSED, ev -> {
if (ev.getCode() == KeyCode.ENTER) {
okayButton.fire();
ev.consume();
}
});
}
If you have only one button of this kind, you can use the following method instead.
okayButton.setDefaultButton(true);
You can dynamically change the default button property of the currently focused button by using binding
btn.defaultButtonProperty().bind(btn.focusedProperty());
I've had the same problem like mynameisJEFF. (I'm using Windows and as I read here: http://mail.openjdk.java.net/pipermail/openjfx-dev/2016-June/019234.html it is the SPACE_BAR and not ENTER, which fires a Button in JavaFX) I didn't want to add a listener to every Button, so I registered a Listener to the root node and asked the scene, which node is focused to fire that one. Here is my code (it is xtend, but I think it very easy to understand):
override start(Stage primaryStage) throws Exception {
val root = FXTable.createRoot
val mainScene = new Scene(root)
root.addEventHandler(KeyEvent.KEY_RELEASED, [event|
if(event.code === KeyCode.ENTER){
switch(focusedNode : mainScene.focusOwnerProperty.get){
Button:{
focusedNode.fire
event.consume
}
default:{
}
}
}
])
primaryStage.scene = mainScene
primaryStage.show
primaryStage.maximized = true
}
There is a much more simple a standard way to do that using setOnKeyPressed
okayButton.setOnKeyPressed(event -> {
if (event.getCode().equals(KeyCode.ENTER)) {
okayButton.fire();
}
}
);
And don't forget that you should define SetOnAction too, other way it's work but it's doing nothing.
okayButton.setOnAction(event -> {
// Do what ever you want to your button do. Like :
System.Out.Print("Okay Button Fired (Clicked or Pressed");
}
);
This should work:
okayButton.addKeyListener(new java.awt.event.KeyAdapter() {
public void keyPressed(java.awt.event.KeyEvent evt) {
if(evt.getKeyCode() == KeyEvent.VK_ENTER){
System.out.print("Your function call or code can go here");
}
}
});
I want to validate user input in JavaFX, so for my TextField i used event filter like below code
#FXML
private TextField textField;
#FXML
void initialize()
{
textField.addEventFilter(KeyEvent.KEY_PRESSED, numeric_tab(textField.getText().toString()));
}
public EventHandler<KeyEvent> numeric_tab(String number)
{
return new EventHandler<KeyEvent>()
{
#Override
public void handle(KeyEvent e)
{
if (e.getCode() == KeyCode.TAB)
{
System.out.println("Cursor is here");
System.out.println(number);
}
}
};
}
As you can see i add an Event Filter for TextField so whenever a user press any key of keyboard we will go to numeric_tab method and inside that we check if the pressed key was Tab-Button, we should print the parameter which is passed by Event Filter.
But I've got problem, although my application prints "Cursor is here" on the console but it does not print the value of TextField. ( again let me say that the value of TextField has sent by event filter)
Why? Why i can't get entire value of the TextField.
The value passed to the filter is only set when the filter is added to the component. In your case the TextField is probably empty so nothing is displayed. To display the current contents you can simply invoke getText
System.out.println(textField.getText());
I've a frame with a text field, a table and two buttons!
I would like to add a KeyListener to listen when a modifier key is pressed and released, in order to change the text of the Ok button and let user select different methods. The listener should work regardless which component is in focus.
For example:
No modifiers: "Ok" - Do Something;
Shift key pressed: "Ok and close" - Do Something and close the frame;
Alt key pressed... etc
Right now I've found this solution, but it seems a bit cluncky and not elegant at all.
public class MyFrame extends JFrame {
private boolean shiftPressed;
private MyDispatcher keyDispatcher;
public MyFrame () {
super();
initGUI();
shiftPressed = false;
KeyboardFocusManager currentManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
keyDispatcher = new MyDispatcher();
currentManager.addKeyEventDispatcher(keyDispatcher);
//...remain of code
}
#Override
public void dispose() {
// I remove the dispatcher when the frame is closed;
KeyboardFocusManager currentManager = KeyboardFocusManager
.getCurrentKeyboardFocusManager();
currentManager.removeKeyEventDispatcher(keyDispatcher);
super.dispose();
}
private class MyDispatcher implements KeyEventDispatcher {
#Override
public boolean dispatchKeyEvent(KeyEvent e) {
if (e.getModifiers() == KeyEvent.SHIFT_MASK) {
if (e.getID() == KeyEvent.KEY_PRESSED) {
System.out.println("Shift Pressed");
shiftPressed = true;
btnOk.setText("Ok and Close");
}
} else if (e.getID() == KeyEvent.KEY_RELEASED) {
if(!e.isShiftDown() && shiftPressed) {
System.out.println("Shift Released");
btnOk.setText("Ok");
}
}
return false;
}
}
}
Any suggestion on how to improve the code?
I would like to add a KeyListener to listen when a modifier key is pressed and released, in order to change the text of the Ok button and
let user select different methods. The listener should work regardless
which component is in focus.
IMHO using Key bindings is a more flexible and reliable approach that brings with these benefits:
Separates the action itself from the key strokes.
There are different conditions to trigger those actions: WHEN_FOCUSED, WHEN_IN_FOCUSED_WINDOW, WHEN_ANCESTOR_OF_FOCUSED_COMPONENT
We can take advantage of those conditions to attach different actions to different components relying a precedence-based policy (see the example presented here).
Having said that, we can create KeyStrokes using appropriate masks like follows:
KeyStroke shiftKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_SHIFT, KeyEvent.SHIFT_DOWN_MASK);
And we can even specify that the key stroke will be triggered on a key release event:
KeyStroke shiftReleasedKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_SHIFT, 0, true);
Note: 0 means no modifiers and true is a flag indicating the key stroke represents a key release event. See the API for more details.
Now, what I'd do is create a wrapper JPanel that contains all my components and attach actions using WHEN_IN_FOCUSED_WINDOW condition:
JPanel wrapperPanel = new JPanel();
...
InputMap wrapperPanelInputMap = wrapperPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
wrapperPanelInputMap.put(shiftKeyStroke, "pressedAction");
wrapperPanelInputMap.put(shiftReleasedKeyStroke, "releasedAction");
...
wrapperPanel.getActionMap().put("pressedAction", pressedAction);
wrapperPanel.getActionMap().put("releasedAction", releasedAction);
Where both pressedAction and releasedAction are different Actions that can do whatever you want (i.e.: change button's text).
The only minor disadvantage is the LOC needed to set key bindings. However you can avoid boilerplate code by creating a reusable panel that can accept Actions for the different modifiers (SHIFT, ALT, CTRL, etc.) and set key bindings internally in that class.
Your approach is ok but it uses a global focus manager. It's a little bit dangerous and can provoke some errors when you for example forget to dispose your frame. Much better is to add your listener to all focusable components in the frame.
private static void addToAll(Component aStart, KeyListener aListener) {
if (aStart.isFocusable()) {
aStart.addKeyListener(aListener);
}
if (aStart instanceof Container) {
Container container = (Container) aStart;
for (Component comp : container.getComponents()) {
addToAll(comp, aListener);
}
}
}
To use it:
public MyFrame () {
super();
initGUI();
shiftPressed = false;
keyDispatcher = new MyDispatcher();
addToAll(this.getContentPane(), keyDispatcher);
//...remain of code
}
Disadvantage of this approach: when you modify your GUI dynamically (add/remove components after your GUI is created and listener is added to all components) you need to remove the listener from all components and add it again.