I was expecting that when I use Alt+mnemonic that the related radio button would become focused and selected. My expectations are based on my knowledge of how mnemonics work in Swing. Are my expectation wrong?
In my example code below I set the number of each radio button to be the mnemonic.
Here is the code:
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.image.*;
import javafx.scene.input.*;
import javafx.scene.effect.*;
import javafx.scene.paint.*;
import javafx.beans.value.*;
import javafx.geometry.*;
import javafx.event.*;
public class RadioButtonSSCCE extends Application
{
public Parent createScene()
{
ToggleGroup group = new ToggleGroup();
RadioButton rb1 = new RadioButton( "Option _1" );
rb1.setUserData("Option 1 selected");
rb1.setToggleGroup( group );
RadioButton rb2 = new RadioButton( "Option _2" );
rb2.setUserData("Option 2 selected");
rb2.setToggleGroup( group );
RadioButton rb3 = new RadioButton( "Option _3" );
rb3.setUserData("Option 3 selected");
rb3.setToggleGroup( group );
VBox root = new VBox(5);
root.setAlignment(Pos.CENTER);
root.getChildren().addAll(rb1, rb2, rb3);
ChangeListener<Toggle> cl = new ChangeListener<Toggle>()
{
@Override
public void changed(ObservableValue<? extends Toggle> ov, Toggle old_toggle, Toggle new_toggle)
{
if (group.getSelectedToggle() != null)
{
String text = group.getSelectedToggle().getUserData().toString();
System.out.println( text );
}
}
};
group.selectedToggleProperty().addListener(cl);
rb1.setSelected( true );
return root;
}
@Override
public void start(Stage primaryStage)
{
Scene scene = new Scene(createScene(), 125, 125);
primaryStage.setScene(scene);
primaryStage.setTitle("Radio Button SSCCE");
primaryStage.show();
}
public static void main(String[] args)
{
launch(args);
}
}
I have found that in order to invoke the mnemonic I have to:
Why do I need to press the space bar? Should the radio button not be selected when the mnemonic is pressed?
Also, why can't I just use Alt+mnemonic as a single key combination to select a radio button?
Is my code wrong?
Edit:
The action is the key to selecting the radio button. I created a generic action to be shared by all buttons:
EventHandler<ActionEvent> onAction = new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent e)
{
RadioButton button = (RadioButton)e.getSource();
button.setSelected(true);
}
};
rb1.setOnAction( onAction );
rb2.setOnAction( onAction );
rb3.setOnAction( onAction );
Since the radio buttons are part of a button group I didn't want the ability to unselect a button.
The Mnemonic
in JavaFX fires an ActionEvent targeted at the registered Node
. In the case of RadioButton
, the ActionEvent doesn't toggle selection. You can add a generic handler to the RadioButton
to toggle on the ActionEvent like this (assuming this is JavaFX 8).
rb1.setOnAction(event -> rb1.setSelected(!rb1.isSelected()));
As OttPrime notes in his answer and in the Mnemonic Javadoc:
When a
Mnemonic
is registered on aScene
, and theKeyCombination
reaches theScene
unconsumed, then the targetNode
will be sent anActionEvent
.
Now I think this is kind of an issue, because you don't really want to have only an ActionEvent
sent to the radio button, instead you want to fire()
the radio button.
Toggles the state of the radio button if and only if the RadioButton has not already selected or is not part of a ToggleGroup.
Note: firing a radio button will also send an ActionEvent
to the button.
Here is the Java 8u40 code for Button::fire
@Override public void fire() {
if (!isDisabled()) {
fireEvent(new ActionEvent());
}
}
Here is the code for ToggleButton::fire
@Override public void fire() {
// TODO (aruiz): if (!isReadOnly(isSelected()) {
if (!isDisabled()) {
setSelected(!isSelected());
fireEvent(new ActionEvent());
}
}
And here is the code for RadioButton::fire
@Override public void fire() {
// we don't toggle from selected to not selected if part of a group
if (getToggleGroup() == null || !isSelected()) {
super.fire();
}
}
Note: RadioButton is a ToggleButton, so the super.fire() call will invoke the ToggleButton fire() code.
So, a mnemonic for a button will be fine: All the mnemonic is doing is sending an ActionEvent to the button (which is exactly the same as the fire implementation for the button). However, ToggleButtons and RadioButtons do extra stuff in their fire methods for manipulating the selection state of the ToggleButton, so mnemonic invocations aren't going to do that.
Now, you might say, well that's OK, I'll just implement OttPrime's workaround:
rb1.setOnAction(event -> rb1.setSelected(!rb1.isSelected()));
This will be great when you select the button via a mnemonic, but (as far as I can see), it will break other functionality for button selection (for example, if you programmatically fire the radio button and, probably, also if you click on the radio button with a mouse). This is because the selection state of the button will change twice (once in the fire() code and again in your action code) and you probably don't want that. Unfortunately, I can't actually test this theory as I don't have access to a machine which can use mnemonics.
So, to answer your original question "How do Mnemonics work with Radio Buttons"? => They don't (at least in IMO, not in any way that really makes sense).
I'd advise just disabling Mnemonic parsing for radio buttons and toggle buttons.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With