I've upgraded my app from JavaFX8 to JavaFX11 (OpenJDK with OpenJFX) and am trying to resolve an issue wrt using the keyboard to navigate through TableCells
in a TableView
.
In JavaFX8, I used setOnKeyPressed
to trap and process keys special keys eg. TAB to move to the next right cell, SHIFT+TAB to move to the previous left cell. That worked fine.
However, when the same code runs in JavaFX11, TAB moves focus from the TableView
to other controls on the screen and SHIFT+TAB does the same but in reverse control order. It's as if JavaFX11 is reverting to default behaviour for TAB and SHIFT+TAB and ignoring the setOnKeyPressed
.
Why does that happen? Has something changed wrt keyboard navigation between JavaFX8 and JavaFX11? Am I doing something wrong in my code?
I modified the JavaFX11 code to register and use an EventHandler
rather than setOnKeyPressed
. Keyboard navigation then worked like it did in JavaFX8. However, the new EventHandler
is interferring with other EventHandler
s in my app and I would really like to be able to use setOnKeyPressed
.
I've been Googling but have thus far found only one reference, at least part of which looks like the same issue: JavaFX 11 TableView Cell navigation by TAB key pressed without custom editableCell class. Other references were for JavaFX8 or earlier.
Here is an MVCE that demonstrates the issue. The same code runs in both JavaFX8 and JavaFX11. You can run it with no event handling to see default behaviour, or use either setOnKeyPressed
or the EventHandler
to see the difference in behaviour when TAB and SHIFT+TAB are used.
I'm using jdk1.8.0_202 and the 11.0.2 versions of OpenJDK and OpenJFX, all running in Netbeans 10.0 on Windows 7.
package test009;
import java.util.Arrays;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.converter.DefaultStringConverter;
public class Test009 extends Application {
private EventHandler<KeyEvent> keyEditingHandler;
private ObservableList<DataModel> ol = FXCollections.observableArrayList();
private TableView<DataModel> tv = new TableView();
private Parent createContent() {
loadDummyData();
createTableColumns();
tv.getSelectionModel().setCellSelectionEnabled(true);
tv.setEditable(true);
tv.setItems(ol);
//******************************************************************************************
//JavaFX8 code that doesn't appear to work in JavaFX11
/*
tv.setOnKeyPressed(event -> {
doTheKeyEvent(event);
});
*/
//******************************************************************************************
//Code needed to achieve the same end in JavaFX11
/*
registerKeyEventHandler();
addKeyEditingHandler();
*/
BorderPane content = new BorderPane();
content.setCenter(tv);
HBox hb = new HBox();
hb.setPadding(new Insets(10D));
hb.setSpacing(10D);
hb.setAlignment(Pos.CENTER);
Button btn1 = new Button("any old object");
Button btn2 = new Button("another object");
hb.getChildren().addAll(Arrays.asList(btn1, btn2));
content.setTop(hb);
return content;
}
public void registerKeyEventHandler() {
keyEditingHandler = (KeyEvent event) -> {
doTheKeyEvent(event);
};
}
private <S> void doTheKeyEvent(KeyEvent event) {
final KeyCombination shiftTAB = new KeyCodeCombination(KeyCode.TAB, KeyCombination.SHIFT_DOWN);
@SuppressWarnings("unchecked") TablePosition<DataModel, ?> pos = tv.getFocusModel().getFocusedCell();
if ( shiftTAB.match(event) ) {
tv.getSelectionModel().selectLeftCell();
event.consume();
} else if ( event.getCode() == KeyCode.TAB ) {
tv.getSelectionModel().selectRightCell();
event.consume();
//... test for other keys and key combinations
//... otherwise fall through to edit the TableCell
} else if ( ! event.isControlDown() && ! event.isAltDown() ){
tv.edit(pos.getRow(), tv.getVisibleLeafColumn(pos.getColumn()));
}
}
public void addKeyEditingHandler() {
tv.addEventFilter(KeyEvent.KEY_PRESSED, keyEditingHandler);
}
private void createTableColumns() {
TableColumn<DataModel,String> col1 = new TableColumn<>("field1");
TableColumn<DataModel,String> col2 = new TableColumn<>("field2");
TableColumn<DataModel,String> col3 = new TableColumn<>("field3");
TableColumn<DataModel,String> col4 = new TableColumn<>("field4");
TableColumn<DataModel,String> col5 = new TableColumn<>("field5");
col1.setCellValueFactory(cellData -> cellData.getValue().field1Property());
col1.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
col2.setCellValueFactory(cellData -> cellData.getValue().field2Property());
col2.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
col3.setCellValueFactory(cellData -> cellData.getValue().field3Property());
col3.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
col4.setCellValueFactory(cellData -> cellData.getValue().field4Property());
col4.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
col5.setCellValueFactory(cellData -> cellData.getValue().field5Property());
col5.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
tv.getColumns().addAll(Arrays.asList(col1, col2, col3, col4, col5));
}
private void loadDummyData() {
ol.add(new DataModel("1", "a", "x", "y", "z"));
ol.add(new DataModel("2", "a", "x", "y", "z"));
ol.add(new DataModel("3", "a", "x", "y", "z"));
ol.add(new DataModel("4", "a", "x", "y", "z"));
ol.add(new DataModel("5", "a", "x", "y", "z"));
}
private class DataModel {
private final StringProperty field1;
private final StringProperty field2;
private final StringProperty field3;
private final StringProperty field4;
private final StringProperty field5;
public DataModel(
String field1,
String field2,
String field3,
String field4,
String field5
) {
this.field1 = new SimpleStringProperty(field1);
this.field2 = new SimpleStringProperty(field2);
this.field3 = new SimpleStringProperty(field3);
this.field4 = new SimpleStringProperty(field4);
this.field5 = new SimpleStringProperty(field5);
}
public String getField1() {return field1.get().trim();}
public void setField1(String field1) {this.field1.set(field1);}
public StringProperty field1Property() {return field1;}
public String getField2() {return field2.get().trim();}
public void setField2(String field2) {this.field2.set(field2);}
public StringProperty field2Property() {return field2;}
public String getField3() {return field3.get().trim();}
public void setField3(String field3) {this.field3.set(field3);}
public StringProperty field3Property() {return field3;}
public String getField4() {return field4.get().trim();}
public void setField4(String field4) {this.field4.set(field4);}
public StringProperty field4Property() {return field4;}
public String getField5() {return field5.get().trim();}
public void setField5(String field5) {this.field5.set(field5);}
public StringProperty field5Property() {return field5;}
}
@Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.setTitle("JavaFX11 - TableView keyboard navigation");
stage.setWidth(600D);
stage.setHeight(600D);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}