javafx setFocus after tabPaine change - javafx

Problem:
Have tabPane tabs OK.
In the first tab there is a text field. I am able to get focus on this field when starting the application.
After changing the tabs and coming back to the first tab I want focus to be on this textfield (barcodereader should be active in this field) without having to select the field with the mouse.
I am able to catch event from tabs with
tp.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Tab>()
{ etc
(could not post with code)
and I am able to trigger en event for the first tab.
But field.requestFocus(); does not work. Probably because this method comes before rendering the textfield.
So here is my question:
How do you set focus on a control after clicking tabs in TabPane?

If you handle the mouse release event, it works: (The doFocus enables the requestFocus handling only when a tab selection changed before, otherwise it kicks in every time you click somewhere in the TabPane.)
final SimpleBooleanProperty doFocus = new SimpleBooleanProperty(false);
tabPane.setOnMouseReleased(new EventHandler<Event>() {
#Override
public void handle(Event event) {
if (!doFocus.get()) {
return;
}
doFocus.set(false);
switch (tabPane.selectionModelProperty().getValue().selectedIndexProperty().intValue()) {
case 0: tf1b.requestFocus(); break;
case 1: tf2a.requestFocus(); break;
default: break;
}
}
});
tabPane.selectionModelProperty().getValue().selectedIndexProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable,
Number oldValue, Number newValue) {
doFocus.set(true);
}
});
When the TabPane has focus, one can change tab selection with the cursor keys and there the TextFields also won't get the focus with selection based approach. This probably should be handled too, if you need it.
(Recently I had a similar problem. I noticed, that the TabPane switches tabs immediately when you press the mouse button. My guess would be, that the selection based approach requests focus on the TextField right after mouse down, but the continued mouse down steals the focus back to the TabPane. Or maybe even the single mouse down event which changes selection causes the focus to go back to TabPane. However, my assumptions regarding the reasons may not be correct, as I am a newbie to JavaFX.)
EDIT: That handling certainly is not optimal. For instance, if you change tabs with the keys, the doFocus will be enabled and then clicking anywhere in the TabPane will trigger the requestFocus call. I thought this should be mentioned.

Also, take a look at my solution for setting focus on TextArea, when user changes selected tab(using mouse or keyboard) https://stackoverflow.com/a/19046535/2791746

Related

Prevent a toggle group from not having a toggle selected - Java FX

I have a toggle group with two toggle buttons that should look like this (Yellow toggle is the selected toggle).
However when I click on the selected toggle both toggles become unselected and look like this.
Then if I try to get whether the toggle is selected, I get a nullPointerException.
(Boolean) toggleGroup.getSelectedToggle().getUserData();
Is it possible to prevent the selected toggle from being unselected when it is clicked?
This one works for me. If the new selected element is null that means theres no selected element, therefore just select the previous one (which is the "oldValue")
toggleGroup.selectedToggleProperty().addListener((obsVal, oldVal, newVal) -> {
if (newVal == null)
oldVal.setSelected(true);
});
You can try the following code to create a persistent toggle.
/**
* Create a toggle group of buttons where one toggle will always remain switched on.
*/
class PersistentButtonToggleGroup extends ToggleGroup {
PersistentButtonToggleGroup() {
super();
getToggles().addListener(new ListChangeListener<Toggle>() {
#Override public void onChanged(Change<? extends Toggle> c) {
while (c.next()) {
for (final Toggle addedToggle : c.getAddedSubList()) {
((ToggleButton) addedToggle).addEventFilter(MouseEvent.MOUSE_RELEASED, new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
if (addedToggle.equals(getSelectedToggle())) mouseEvent.consume();
}
});
}
}
}
});
}
}
This just reacts to mouse events, so perhaps not ideal if you want to account for keyboard events or changing toggles in code.
A similar, but perhaps more complete approach that I haven't tried may be defined in the blog entry Button of Choice: Use ToggleButtons as RadioButtons.
Perhaps the simplest behavior to accomplish what you desire might be to just use RadioButtons instead of ToggleButtons.
If, additionally, you want the buttons styled like ToggleButtons, instead of RadioButtons, then you can try the styling technique which is outlined in: How to make a RadioButton look like regular Button in JavaFX.
RadioButton radioButton=new RadioButton("Radio");
radioButton.getStyleClass().remove("radio-button");
radioButton.getStyleClass().add("toggle-button");

JavaFX: set fullscreen exclusive mode (FSEM) in non-mouse/keyboard-handler

my application consists of a TabPane named tabPane with, lets say, two Tabs: firstTab and secondTab. Whenever firstTab is selected, I want the application to be in FSEM (full-screen exclusive mode, see documentation); otherwise it should be in normal windowed mode.
My usual way of being notified about which Tab is active is either
BooleanBinding isFirstTabActive = tabPane.getSelectionModel().
selectedItemProperty().isEqualTo(firstTab);
or
tabPane.getSelectionModel.selectedItemProperty().addListener(...);
Since stage.fullScreenProperty() is read-only and therefore cannot be binded (as stated in docs), I can not use my first method and do, lets say,
stage.fullScreenProperty().bind(isFirstTabActive) //not working.
Since I am not planning on sigining the application for now and, as the docs state:
Applications can only enter FSEM in response to user input. More specifically, entering is allowed from mouse (Node.mousePressed/mouseReleased/mouseClicked) or keyboard (Node.keyPressed/keyReleased/keyTyped) event handlers.
I can not use the second method either, by doing
tabPane.getSelectionModel.selectedItemProperty().addListener(new ChangeListener<Tab>() {
#Override
public void changed(ObservableValue<? extends Tab> observable, Tab oldValue, Tab newValue) {
stage.setFullScreen(newValue == firstTab);
}
);
because this way the application does not enter FSEM but only emulated fullscreen mode.
I do know that I can enter FSEM by creating EventHandlers which listen to tabPane events. But this way I had to add two EventHandlers for both MouseEvent.MOUSE_CLICKED and KeyEvent.KEY_TYPED, and the EventHandlers would have to explicitly find out whats going on ignoring their Event, like:
tabPane.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
stage.setFullScreen(tabPane.getSelectionModel().getSelectedItem() == firstTab);
}
});
So is this really the way to go here? Or can I do some workaround with the Bindings? Any suggestions here? Thanks.

How to skip DRAG_DETECTED Events from TableHeaderRow (resizing TableColumns)

In order to use a TableView (with resizable columns) as drag source,
I have attached an onDragDetected handler on that TableView with the effect, that resizing TableColumns does not work anymore: If the user clicks into the TableHeaderRow for dragging the column separators, these mouse events are consumed by my handler too.
The handler is attached to the whole TableView and I do not see a way to distinguish between events from TableRows and events from the TableHeaderRow.
Attaching the handler to the data-rows only is not appropriate in my case, since I need multiline selection (and do not want to have dependencies from the RowFactory to the application data model).
Any suggestions?
best Hans
I found the solution myself: I had to look up the TableHeaderRow instance with the lookup-method, and discard the DRAG_DETECTED event there.
Pane header = (Pane) mainTableView.lookup("TableHeaderRow");
header.setOnDragDetected(new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
System.out.println("ignoring drag operation from table header.");
event.consume();
}
});
After that, resising of table columns works. Maybe there is something wrong with my drag+drop event handling that initially caused the problem!? Somebody voted down without comment...
The solution by Hans does work, but only after the window is shown. I added a window.setOnShown((WindowEvent e) listener and then I was able to do the lookup and header.setOnDragDetected.
Here is how I added it:
window.setOnShown((WindowEvent e) -> {
Pane header = (Pane) mainTableView.lookup("TableHeaderRow");
....
});
Otherwise, lookup returns null.

JavaFX alert dialog ignores the focused button

Why does the JavaFX alert dialog fires the Platform.exit(); when I press the Enter key even though the focused button in the alert dialog is Cancel?
soaStage.setOnCloseRequest(new EventHandler<WindowEvent>()
{
#Override
public void handle(WindowEvent event)
{
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle("Confirm");
alert.setHeaderText("Are you sure you want to exit?");
alert.setContentText("Press OK to exit, or Cancel to stay.");
alert.initOwner(soaStage);
Optional<ButtonType> result = alert.showAndWait();
if (result.get() == ButtonType.OK)
{
Platform.exit();
}
else
{
event.consume();
}
}
});
Default buttons are fired on enter
The OK button is fired when you press enter because it is a default button.
A default Button is the button that receives a keyboard VK_ENTER press, if no other node in the scene consumes it.
In the default JavaFX 8 Modena stylesheet, the default button is indicated by being a blue color rather than than the gray color of a standard button.
How to remove default button behaviour
You can remove this behavior from the alert dialog by not making the OK button a default button:
Button okButton = (Button) alert.getDialogPane().lookupButton(ButtonType.OK);
okButton.setDefaultButton(false);
I advise you not to do this, but instead to always leave a default button in alert dialogs.
On OS X, standard alert type dialogs have a default button which is triggered by enter even if another button is focused, so the standard behavior in JavaFX is consistent with that. Note: to allow this behavior in default dialogs in OS X it is necessary to enable full keyboard access.
If you do change the OK button to not be a default button, I suggest you change its text to something else (e.g. Exit for your case):
okButton.setText("Exit");
How to make enter fire focused buttons
Now, if you also want to make it so that the focused button fires when you press enter, then you can do this:
EventHandler<KeyEvent> fireOnEnter = event -> {
if (KeyCode.ENTER.equals(event.getCode())
&& event.getTarget() instanceof Button) {
((Button) event.getTarget()).fire();
}
};
DialogPane dialogPane = alert.getDialogPane();
dialogPane.getButtonTypes().stream()
.map(dialogPane::lookupButton)
.forEach(button ->
button.addEventHandler(
KeyEvent.KEY_PRESSED,
fireOnEnter
)
);
Note: In any case, focused buttons are always fired when you press space.
We can add ENTER binding to the whole buttons by creating a class that need to be instantiated once when the application starts.
public class EnableButtonEnterKey extends ButtonBehavior<Button> {
public EnableButtonEnterKey() {
super(new Button());
BUTTON_BINDINGS.add(new KeyBinding(ENTER, KEY_PRESSED, "Press"));
BUTTON_BINDINGS.add(new KeyBinding(ENTER, KEY_RELEASED, "Release"));
}
}
When starting the application, call
new EnableButtonEnterKey();
That's it.

JavaFX TreeView: remove expand / collapse button (disclosure node) & functionality

I want to have a TreeView that has all of its children permanently expanded, and I don't want the user to be able to expand or collapse any of the children.
To do this I've found that I need to do the following:
Remove icon with CSS (Done)
Change expand and collapse image TreeView JavaFX 2.2
[edit] Above link should be used to change image; to remove completely, use this solution: https://stackoverflow.com/a/27831191/4430591
Remove double click functionality (Done)
Disable TreeItem's default expand/collapse on double click JavaFX 2.2
[edit] Remove ability to collapse / expand using keyboard arrrow keys (Done)
Given in José Pereda's solution below ( https://stackoverflow.com/a/27831085/4430591 )
[edit] Remove ability to right click for a ContextMenu (Done)
Given in José Pereda's solution below ( https://stackoverflow.com/a/27831085/4430591 )
Remove icon's clickablity (How do I do this?)
[edit] solution: https://stackoverflow.com/a/27831191/4430591
Even though the icon is no longer visible, it's still clickable. I don't see any way of filtering this; I only see ways to be able to respond to it after the fact.
Also, if I'm missing anything else that I need to do to ensure this functionality, please let me know.
I feel quite silly. I think this was mostly just a matter of not knowing what that darn arrow was called. Apparently it's a disclosureNode? Maybe that's common knowledge.
In the custom defined TreeCell, all I did was add this line in the updateItem method:
setDisclosureNode(null);
The solution to avoid modifying the skin or the default behavior is more simple if we trap the clicks before they are dispatched, and consume the right ones.
For that we can use an EventDispatcher, to filter both the mouse pressed and the right click over the arrows, which are StackPane nodes:
class CellEventDispatcher implements EventDispatcher {
private final EventDispatcher original;
public CellEventDispatcher(EventDispatcher original) {
this.original = original;
}
#Override
public Event dispatchEvent(Event event, EventDispatchChain tail) {
if (event.getEventType().equals(MouseEvent.MOUSE_PRESSED) ||
event.getEventType().equals(ContextMenuEvent.ANY)){
event.consume();
}
if(event instanceof KeyEvent && event.getEventType().equals(KeyEvent.KEY_PRESSED)){
if((((KeyEvent)event).getCode().equals(KeyCode.LEFT) ||
((KeyEvent)event).getCode().equals(KeyCode.RIGHT))){
event.consume();
}
}
return original.dispatchEvent(event, tail);
}
}
Now we apply our custom dispatcher to the tree view:
#Override
public void start(Stage primaryStage) {
TreeView<String> tree = new TreeView<>();
...
EventDispatcher treeOriginal = tree.getEventDispatcher();
tree.setEventDispatcher(new CellEventDispatcher(treeOriginal));
Scene scene = new Scene(tree);
primaryStage.setScene(scene);
primaryStage.show();
}
This will consume any click (left or right) over the arrows on the tree.
EDIT
Added to the event dispatcher class the case where the user uses the keyboard to traverse the tree view, consuming the collapse/expand events with arrow LEFT or RIGHT.

Resources