JavaFX8 Как запросить фокусировку на графических узлах в табличном представлении при обходе с помощью клавиши TAB или другого ключа?
У меня есть TableView, который содержит текстовое и графическое содержимое (поля со списком, флажки и т. Д.).
Когда я перемещаюсь по ячейкам с помощью клавиатуры и прихожу к ячейке, содержащей графический элемент, я бы хотел, чтобы графика была выбрана так, чтобы я мог, например, нажать F4 и выпадающий список со списком, или нажать пробел и есть кнопка изменения состояния переключателя.
Однако в тот момент, когда я нажимаю клавишу (или другую клавишу) на ячейке, ячейка, содержащая графику, выделяется, и я вынужден использовать мышь для манипулирования графикой.
Как мне выбрать сам графический элемент, а не ячейку, в которой он находится?
IE. Вот что он делает сейчас, когда я вкладываю в нетекстовую ячейку:
Как я могу заставить это сделать это?
Я пробовал несколько способов получить клеточную графику, но она всегда нулевая.
ОБНОВЛЕНИЕ:
Я проделал большую работу и теперь могу добраться до графического элемента. Это была ошибка новичка на Java. Извиняюсь!
Однако, несмотря на то, что теперь я могу получить графику, я все еще не смог выделить ее или сосредоточиться на ней. Может кто-нибудь сказать мне, как это сделать, пожалуйста? Большое спасибо!
Вот выдержки из моего обновленного кода, использующего поля со списком и TABbing в качестве примера.
Ключевые события попали в общий setOnKeyPressed
обработчик на уровне TableView. Вот код для вкладки. Я указал места, где я застрял.
} else if ( event.getCode() == KeyCode.TAB ) {
tv.getSelectionModel().selectRightCell();
endOfRowCheck(tv, event, pos, firstCol, maxCols);
event.consume();
//==> IS IT BETTER TO USE THE FOCUS MODEL OR THE SELECTION MODEL? BOTH GIVE THE CELL GRAPHIC.
//==> IS THERE A BETTER WAY OF GETTING THE CELL GRAPHIC?
TablePosition<S, ?> focussedPos = tv.getFocusModel().getFocusedCell();
TableColumn tableColumn = (TableColumn<S, ?>) focussedPos.getTableColumn();
TableCell cell = (TableCell) tableColumn.getCellFactory().call(tableColumn);
Node cellGraphic = cell.getGraphic();
System.out.println(cellGraphic);
//Output: ComboBox@44cf20e7[styleClass=combo-box-base combo-box]
//==> HOW DO I NOW FOCUS ON (OR SELECT?) THE GRAPHIC?
//I tried Platform.runLater() on the requestFocus but that didn't work either.
cellGraphic.requestFocus();
} else if ...
Для полноты вот так называется endOfRowCheck
метод:
private void endOfRowCheck(TableView tv, KeyEvent event, TablePosition pos, TableColumn col, int maxCols) {
if ( pos.getColumn() == maxCols ) {
//We're at the end of a row so position to the start of the next row
tv.getSelectionModel().select(pos.getRow()+1, col);
event.consume();
}
}
Я создаю столбцы комбинированного списка следующим образом.
В контроллере FXML:
TableColumn<TestModel, DBComboChoice> colComboBoxField = DAOGenUtil.createComboBoxColumnTEST(colComboBoxField_HEADING, TestModel::comboBoxFieldProperty, arlMasterAssetClasses);
В классе DAOGenUtil:
public <S> TableColumn<S, DBComboChoice> createComboBoxColumnTEST(String title,
Function<S, StringProperty> methodGetComboFieldProperty,
ObservableList<DBComboChoice> comboData) {
TableColumn<S, DBComboChoice> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> {
String masterCode = methodGetComboFieldProperty.apply(cellData.getValue()).get();
DBComboChoice choice = DBComboChoice.getDescriptionByMasterCode(masterCode, comboData);
return new SimpleObjectProperty<>(choice);
});
col.setCellFactory(column -> ComboBoxCell.createComboBoxCell(comboData));
return col;
}
Класс ComboBoxCell, который я использую для визуализации нередактируемых комбо в виде комбо, а не как меток.
public class ComboBoxCell<S, T> extends TableCell<S, T> {
private final ComboBox<DBComboChoice> combo = new ComboBox<>();
public ComboBoxCell(ObservableList<DBComboChoice> comboData) {
combo.getItems().addAll(comboData);
combo.setEditable(false);
setGraphic(combo);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
combo.setOnAction((ActionEvent event) -> {
try {
String masterCode = combo.getSelectionModel().getSelectedItem().getMasterCode();
S datamodel = getTableView().getItems().get(getIndex());
try {
Method mSetComboBoxField = datamodel.getClass().getMethod("setComboBoxField", (Class) String.class);
mSetComboBoxField.invoke(datamodel, masterCode);
} catch (NoSuchMethodException | SecurityException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException ex) {
System.err.println(ex);
DAOGenUtil.logError(ex.getClass().toString(), ex.getMessage(), "Call to 'setComboBoxField' failed in ComboBoxCell.setOnAction for master code '" + masterCode + "'");
}
} catch (NullPointerException ex) {
//temporary workaround for bad test data
System.out.println("caught NPE in combo.setOnAction");
}
});
}
public static <S> ComboBoxCell<S, DBComboChoice> createComboBoxCell(ObservableList<DBComboChoice> comboData) {
return new ComboBoxCell<S, DBComboChoice>(comboData);
}
@Override
protected void updateItem(T comboChoice, boolean empty) {
super.updateItem(comboChoice, empty);
if (empty) {
setGraphic(null);
} else {
combo.setValue((DBComboChoice) comboChoice);
setGraphic(combo);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
}
}
Я использую JavaFX8, NetBeans 8.2 и Scene Builder 8.3.
ОБНОВЛЕНО СНОВА:
Вот полный тестовый пример в соответствии с запросом, воспроизводимый в NetBeans. Приношу свои извинения, если он не в ожидаемом формате... Я все еще относительно новичок в Java и не знаю, как превратить его в то, что вы можете запустить автономно.
Если щелкнуть столбец текстового поля, а затем нажать клавишу TAB в столбце поля со списком, фокус получит ячейка, содержащая поле со списком, а не само поле со списком.
Для моего приложения поля со списком должны быть недоступны для редактирования и всегда отображаться как комбинации. Когда пользователь достигает конца строки таблицы и нажимает клавишу TAB (или СТРЕЛКУ ВПРАВО), фокус должен перемещаться к началу следующей строки.
Вот код теста.
Приложение:
package test;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Test extends Application {
@Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Контроллер FXML:
package test;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.beans.Observable;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
public class FXMLDocumentController implements Initializable {
private DAOGenUtil DAOGenUtil = new DAOGenUtil();
public ObservableList<TestModel> olTestModel = FXCollections.observableArrayList(testmodel ->
new Observable[] {
testmodel.textFieldProperty(),
testmodel.comboBoxFieldProperty()
});
ObservableList<DBComboChoice> comboChoices = FXCollections.observableArrayList();
TableColumn<TestModel, String> colTextField = new TableColumn("text col");
TableColumn<TestModel, DBComboChoice> colComboBoxField = DAOGenUtil.createComboBoxColumn("combo col", TestModel::comboBoxFieldProperty, comboChoices);
@FXML
private TableView<TestModel> tv;
@Override
public void initialize(URL url, ResourceBundle rb) {
comboChoices.add(new DBComboChoice("F", "Female"));
comboChoices.add(new DBComboChoice("M", "Male"));
olTestModel.add(new TestModel("test row 1", "M"));
olTestModel.add(new TestModel("test row 2", "F"));
olTestModel.add(new TestModel("test row 3", "F"));
olTestModel.add(new TestModel("test row 4", "M"));
olTestModel.add(new TestModel("test row 5", "F"));
colTextField.setCellValueFactory(new PropertyValueFactory<>("textField"));
tv.getSelectionModel().setCellSelectionEnabled(true);
tv.setEditable(true);
tv.getColumns().addAll(colTextField, colComboBoxField);
tv.setItems(olTestModel);
tv.setOnKeyPressed(event -> {
TableColumn firstCol = colTextField;
TableColumn lastCol = colComboBoxField;
int firstRow = 0;
int lastRow = tv.getItems().size()-1;
int maxCols = 1;
DAOGenUtil.handleTableViewSpecialKeys(tv, event, firstCol, lastCol, firstRow, lastRow, maxCols);
});
}
}
Класс ComboBoxCell:
package test;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
public class ComboBoxCell<S, T> extends TableCell<S, T> {
private final ComboBox<DBComboChoice> combo = new ComboBox<>();
private final DAOGenUtil DAOGenUtil;
public ComboBoxCell(ObservableList<DBComboChoice> comboData) {
this.DAOGenUtil = new DAOGenUtil();
combo.getItems().addAll(comboData);
combo.setEditable(false);
setGraphic(combo);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
combo.setOnAction((ActionEvent event) -> {
String masterCode = combo.getSelectionModel().getSelectedItem().getMasterCode();
S datamodel = getTableView().getItems().get(getIndex());
try {
Method mSetComboBoxField = datamodel.getClass().getMethod("setComboBoxField", (Class) String.class);
mSetComboBoxField.invoke(datamodel, masterCode);
} catch (NoSuchMethodException | SecurityException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException ex) {
System.err.println(ex);
}
});
}
@Override
protected void updateItem(T comboChoice, boolean empty) {
super.updateItem(comboChoice, empty);
if (empty) {
setGraphic(null);
} else {
combo.setValue((DBComboChoice) comboChoice);
setGraphic(combo);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
}
}
Модель данных TableView:
package test;
import javafx.beans.property.StringProperty;
import javafx.beans.property.SimpleStringProperty;
public class TestModel {
private StringProperty textField;
private StringProperty comboBoxField;
public TestModel() {
this(null, null);
}
public TestModel(
String textField,
String comboBoxField
) {
this.textField = new SimpleStringProperty(textField);
this.comboBoxField = new SimpleStringProperty(comboBoxField);
}
public String getTextField() {
return textField.get().trim();
}
public void setTextField(String textField) {
this.textField.set(textField);
}
public StringProperty textFieldProperty() {
return textField;
}
public String getComboBoxField() {
return comboBoxField.get().trim();
}
public void setComboBoxField(String comboBoxField) {
this.comboBoxField.set(comboBoxField);
}
public StringProperty comboBoxFieldProperty() {
return comboBoxField;
}
}
Модель данных DBComboChoice:
package test;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.ObservableList;
public class DBComboChoice {
private StringProperty masterCode;
private StringProperty masterDescription;
public DBComboChoice(
String masterCode,
String masterDescription
) {
this.masterCode = new SimpleStringProperty(masterCode);
this.masterDescription = new SimpleStringProperty(masterDescription);
}
public String getMasterCode() {
return masterCode.get();
}
public StringProperty masterCodeProperty() {
return masterCode;
}
public String getMasterDescription() {
return masterDescription.get();
}
public StringProperty masterDescriptionProperty() {
return masterDescription;
}
public static DBComboChoice getDescriptionByMasterCode(String inMasterCode, ObservableList<DBComboChoice> comboData) {
for ( int i=0; i<comboData.size(); i++ ) {
if ( comboData.get(i).getMasterCode().equals(inMasterCode) ) {
return comboData.get(i);
}
}
return null;
}
@Override
public String toString() {
return this.masterDescription.get();
}
}
Класс DAOGenUtil:
package test;
import java.util.function.Function;
import javafx.application.Platform;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
public class DAOGenUtil {
public <S> TableColumn<S, DBComboChoice> createComboBoxColumn(String title,
Function<S, StringProperty> methodGetComboFieldProperty,
ObservableList<DBComboChoice> comboData) {
TableColumn<S, DBComboChoice> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> {
String masterCode = methodGetComboFieldProperty.apply(cellData.getValue()).get();
DBComboChoice choice = DBComboChoice.getDescriptionByMasterCode(masterCode, comboData);
return new SimpleObjectProperty<>(choice);
});
col.setCellFactory((TableColumn<S, DBComboChoice> param) -> new ComboBoxCell<>(comboData));
return col;
}
public <S> void handleTableViewSpecialKeys(TableView tv, KeyEvent event,
TableColumn firstCol, TableColumn lastCol,
int firstRow, int lastRow,
int maxCols) {
//NB: pos, at this point, is the cell position that the cursor is about to leave
TablePosition<S, ?> pos = tv.getFocusModel().getFocusedCell();
if (pos != null ) {
if ( event.getCode() == KeyCode.TAB ) {
tv.getSelectionModel().selectRightCell();
endOfRowCheck(tv, event, pos, firstCol, maxCols);
event.consume();
TablePosition<S, ?> focussedPos = tv.getFocusModel().getFocusedCell();
TableColumn tableColumn = (TableColumn<S, ?>) focussedPos.getTableColumn();
TableCell cell = (TableCell) tableColumn.getCellFactory().call(tableColumn);
Node cellGraphic = cell.getGraphic();
System.out.println("node cellGraphic is " + cellGraphic);
if ( cellGraphic instanceof ComboBox<?> ) {
System.out.println("got a combo");
//nbg cellGraphic.requestFocus();
Platform.runLater(() -> {
((ComboBox<?>) cellGraphic).requestFocus();
});
}
} else if ( ! event.isShiftDown() && ! event.isControlDown() ){
//edit the cell
tv.edit(pos.getRow(), pos.getTableColumn());
}
}
}
private void endOfRowCheck(TableView tv, KeyEvent event, TablePosition pos, TableColumn col, int maxCols) {
if ( pos.getColumn() == maxCols ) {
//We're at the end of a row so position to the start of the next row
tv.getSelectionModel().select(pos.getRow()+1, col);
event.consume();
}
}
}
FXML:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.BorderPane?>
<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="test.FXMLDocumentController">
<center>
<TableView fx:id="tv" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER" />
</center>
</BorderPane>
1 ответ
Ваш вопрос слишком широкий, вы пытаетесь решить слишком много проблем одновременно, например:
- изменить последовательность навигации, то есть обработку endOfRow
- использовать вкладку для навигации
- реализовать пользовательскую ячейку
- сопоставить визуальное представление значения с чем-то значимым для пользователя
- начать редактирование (иначе: "фокус") при достижении ячейки
Самым важным (IMO) заблуждением в вашем коде является последний пункт: вы не должны игнорировать механизм редактирования при изменении базовых данных. Так что поменяйте мышление "сфокусируйте изображение" на "начать редактирование"
Ниже приведен отдельный пример, который демонстрирует, как начать с поддержки ядра и изменить ее, чтобы приблизиться к тому, что действительно необходимо. Это
- настраивает ядро ComboBoxTableCell с (raw;) StringConverter для сопоставления masterCode -> masterDescription
- расширяет эту базовую ячейку для запроса фокуса в startEdit
- регистрирует прослушиватель в свойстве focusCell таблицы (на самом деле это свойство focusModel), которое начинает редактирование новой ячейки
Варианты, чтобы продолжить отсюда:
- чтобы всегда отображать редактируемый элемент управления, посмотрите на код ComboBoxTableCell и измените его, чтобы всегда отображать комбо
- повторно применить обработку вкладок (выглядит хорошо для меня)
- изменить последовательность навигации по мере необходимости
Код:
public class TableCellFocusApp extends Application {
private Parent createContent() {
ObservableList<TestModel> olTestModel = FXCollections
.observableArrayList(testmodel -> new Observable[] {
testmodel.textFieldProperty(),
testmodel.comboBoxFieldProperty() });
TableView<TestModel> table = new TableView<>();
olTestModel.add(new TestModel("test row 1", "M"));
olTestModel.add(new TestModel("test row 2", "F"));
olTestModel.add(new TestModel("test row 3", "F"));
olTestModel.add(new TestModel("test row 4", "M"));
olTestModel.add(new TestModel("test row 5", "F"));
TableColumn<TestModel, String> colTextField = new TableColumn<>("text col");
colTextField
.setCellValueFactory(cb -> cb.getValue().textFieldProperty());
TableColumn<TestModel, String> gender= new TableColumn<>("Gender");
gender.setMinWidth(100);
gender.setCellValueFactory(cb -> cb.getValue().comboBoxFieldProperty());
StringConverter<String> converter = new StringConverter<>() {
@Override
public String toString(String object) {
return "F".equals(object) ? "Female" : "Male";
}
@Override
public String fromString(String string) {
return "Female".equals(string) ? "F" : "M";
}
};
gender.setCellFactory(cb -> new ComboBoxTableCell<>(converter, "F", "M") {
@Override
public void startEdit() {
super.startEdit();
if (getGraphic() != null) {
getGraphic().requestFocus();
}
}
});
// just to see that the data is updated correctly - add a readonly column
TableColumn<TestModel, String> plainGender = new TableColumn<>("readonly");
plainGender.setCellValueFactory(cb -> cb.getValue().comboBoxFieldProperty());
plainGender.setEditable(false);
table.getFocusModel().focusedCellProperty().addListener((src, ov, nv) -> {
if (nv != null && nv.getTableColumn() == gender) {
table.edit(nv.getRow(), gender);
}
});
table.getSelectionModel().setCellSelectionEnabled(true);
table.setEditable(true);
table.getColumns().addAll(colTextField,gender, plainGender ); //, colComboBoxField );
table.setItems(olTestModel);
BorderPane content = new BorderPane(table);
return content;
}
@Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.setTitle(FXUtils.version());
stage.show();
}
public static void main(String[] args) {
launch(args);
}
@SuppressWarnings("unused")
private static final Logger LOG = Logger
.getLogger(TableCellFocusApp.class.getName());
}