Java 如何在TableView中使用箭头按钮在编辑模式下遍历单元格
我想使用arrow/enter键遍历Java 如何在TableView中使用箭头按钮在编辑模式下遍历单元格,java,javafx,Java,Javafx,我想使用arrow/enter键遍历TableView中的单元格,但是,如果我试图在我的自定义EditCell类中实现它,它似乎不起作用。有没有办法做到这一点?我在TextField上尝试了一个侦听器,但它实际上并没有启动实际单元格中的焦点 这是我的密码: Tester.java package tester; import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.contro
TableView
中的单元格,但是,如果我试图在我的自定义EditCell类中实现它,它似乎不起作用。有没有办法做到这一点?我在TextField
上尝试了一个侦听器,但它实际上并没有启动实际单元格中的焦点
这是我的密码:
Tester.java
package tester;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class Tester extends Application
{
@Override
public void start(Stage primaryStage)
{
TableView<LineItem> table = new TableView<>();
Callback<TableColumn<LineItem, String>, TableCell<LineItem, String>> textFactoryEditable = (TableColumn<LineItem, String> p) -> new EditableTextCell();
TableColumn<LineItem, String> column1 = new TableColumn<>("Test1");
column1.setCellValueFactory(cellData -> cellData.getValue().getString1Property());
column1.setEditable(true);
column1.setCellFactory(textFactoryEditable);
table.getColumns().add(column1);
TableColumn<LineItem, String> column2 = new TableColumn<>("Test2");
column2.setCellValueFactory(cellData -> cellData.getValue().getString2Property());
column2.setEditable(true);
column2.setCellFactory(textFactoryEditable);
table.getColumns().add(column2);
table.getItems().add(new LineItem());
table.getItems().add(new LineItem());
table.getItems().add(new LineItem());
table.setPrefWidth(500);
HBox root = new HBox();
root.getChildren().addAll(table);
Scene scene = new Scene(root, 500, 500);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* @param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
}
package tester;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class LineItem
{
private final StringProperty string1;
private final StringProperty string2;
public LineItem()
{
this.string1 = new SimpleStringProperty();
this.string2 = new SimpleStringProperty();
}
public final StringProperty getString1Property()
{
return this.string1;
}
public final StringProperty getString2Property()
{
return this.string2;
}
}
package tester;
import java.util.Objects;
import javafx.beans.value.ObservableValue;
import javafx.beans.value.WritableValue;
import javafx.geometry.Pos;
import javafx.scene.control.TableCell;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
public class EditableTextCell<E> extends TableCell<E, String>
{
private final TextField textField;
private boolean updating = false;
public EditableTextCell()
{
textField = new TextField();
textField.setAlignment(Pos.CENTER_RIGHT);
textField.textProperty().addListener((ObservableValue<? extends String> o, String oldValue, String newValue) ->
{
if (!updating)
{
((WritableValue<String>) getTableColumn().getCellObservableValue((E) getTableRow().getItem())).setValue(newValue);
getTableView().scrollTo(getTableRow().getIndex());
getTableView().scrollToColumn(getTableColumn());
}
});
textField.setOnKeyPressed((KeyEvent ke) ->
{
switch (ke.getCode())
{
case DOWN:
getTableView().getFocusModel().focusBelowCell();
break;
case UP:
getTableView().getFocusModel().focusAboveCell();
break;
case RIGHT:
getTableView().getFocusModel().focusRightCell();
break;
case LEFT:
getTableView().getFocusModel().focusLeftCell();
break;
default:
break;
}
});
}
@Override
protected void updateItem(String item, boolean empty)
{
super.updateItem(item, empty);
if (empty)
{
setGraphic(null);
} else
{
setGraphic(textField);
if (!Objects.equals(textField.getText(), item))
{
// prevent own updates from moving the cursor
updating = true;
textField.setText(item);
updating = false;
}
}
}
}
EditableTextCell.java
package tester;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class Tester extends Application
{
@Override
public void start(Stage primaryStage)
{
TableView<LineItem> table = new TableView<>();
Callback<TableColumn<LineItem, String>, TableCell<LineItem, String>> textFactoryEditable = (TableColumn<LineItem, String> p) -> new EditableTextCell();
TableColumn<LineItem, String> column1 = new TableColumn<>("Test1");
column1.setCellValueFactory(cellData -> cellData.getValue().getString1Property());
column1.setEditable(true);
column1.setCellFactory(textFactoryEditable);
table.getColumns().add(column1);
TableColumn<LineItem, String> column2 = new TableColumn<>("Test2");
column2.setCellValueFactory(cellData -> cellData.getValue().getString2Property());
column2.setEditable(true);
column2.setCellFactory(textFactoryEditable);
table.getColumns().add(column2);
table.getItems().add(new LineItem());
table.getItems().add(new LineItem());
table.getItems().add(new LineItem());
table.setPrefWidth(500);
HBox root = new HBox();
root.getChildren().addAll(table);
Scene scene = new Scene(root, 500, 500);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* @param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
}
package tester;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class LineItem
{
private final StringProperty string1;
private final StringProperty string2;
public LineItem()
{
this.string1 = new SimpleStringProperty();
this.string2 = new SimpleStringProperty();
}
public final StringProperty getString1Property()
{
return this.string1;
}
public final StringProperty getString2Property()
{
return this.string2;
}
}
package tester;
import java.util.Objects;
import javafx.beans.value.ObservableValue;
import javafx.beans.value.WritableValue;
import javafx.geometry.Pos;
import javafx.scene.control.TableCell;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
public class EditableTextCell<E> extends TableCell<E, String>
{
private final TextField textField;
private boolean updating = false;
public EditableTextCell()
{
textField = new TextField();
textField.setAlignment(Pos.CENTER_RIGHT);
textField.textProperty().addListener((ObservableValue<? extends String> o, String oldValue, String newValue) ->
{
if (!updating)
{
((WritableValue<String>) getTableColumn().getCellObservableValue((E) getTableRow().getItem())).setValue(newValue);
getTableView().scrollTo(getTableRow().getIndex());
getTableView().scrollToColumn(getTableColumn());
}
});
textField.setOnKeyPressed((KeyEvent ke) ->
{
switch (ke.getCode())
{
case DOWN:
getTableView().getFocusModel().focusBelowCell();
break;
case UP:
getTableView().getFocusModel().focusAboveCell();
break;
case RIGHT:
getTableView().getFocusModel().focusRightCell();
break;
case LEFT:
getTableView().getFocusModel().focusLeftCell();
break;
default:
break;
}
});
}
@Override
protected void updateItem(String item, boolean empty)
{
super.updateItem(item, empty);
if (empty)
{
setGraphic(null);
} else
{
setGraphic(textField);
if (!Objects.equals(textField.getText(), item))
{
// prevent own updates from moving the cursor
updating = true;
textField.setText(item);
updating = false;
}
}
}
}
包装测试仪;
导入java.util.Objects;
导入javafx.beans.value.observeValue;
导入javafx.beans.value.WritableValue;
导入javafx.geometry.Pos;
导入javafx.scene.control.TableCell;
导入javafx.scene.control.TextField;
导入javafx.scene.input.KeyEvent;
公共类EditableTextCell扩展了TableCell
{
私有最终文本字段文本字段;
私有布尔更新=false;
公共EditableTextCell()
{
textField=新的textField();
textField.setAlignment(右中位置);
textField.textProperty().addListener((observeValue多亏了Slaw,才算出了它
首先要启用单元格选择,table.getSelectionModel().setCellSelectionEnabled(true);
然后在EditableTextCell.java类中:
this.focusedProperty().addListener((ObservableValue<? extends Boolean> o, Boolean oldValue, Boolean newValue) ->
{
if (newValue)
{
textField.requestFocus();
}
});
textField.focusedProperty().addListener((ObservableValue<? extends Boolean> o, Boolean oldValue, Boolean newValue) ->
{
if (newValue)
{
getTableView().getFocusModel().focus(getTableRow().getIndex(), getTableColumn());
}
}
textField.setOnKeyPressed((KeyEvent ke) ->
{
switch (ke.getCode())
{
case DOWN:
getTableView().getFocusModel().focusBelowCell();
ke.consume();
break;
case ENTER:
getTableView().getFocusModel().focusBelowCell();
ke.consume();
break;
case UP:
getTableView().getFocusModel().focusAboveCell();
ke.consume();
break;
case RIGHT:
getTableView().getFocusModel().focusRightCell();
ke.consume();
break;
case LEFT:
getTableView().getFocusModel().focusLeftCell();
ke.consume();
break;
default:
break;
}
});
this.focusedProperty().addListener((observeValueRow选择模式
尽管如此,您似乎不需要为此启用。从的实现中得到启发,您的自定义TableCell
应该采取某种形式的回调来获取模型属性;它也可能需要,允许您使用TableCell
而不仅仅是String
s。以下是一个示例:
import java.util.Objects;
import java.util.function.IntFunction;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView.TableViewFocusModel;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.util.Callback;
import javafx.util.StringConverter;
import javafx.util.converter.DefaultStringConverter;
public class CustomTableCell<S, T> extends TableCell<S, T> {
public static <S> Callback<TableColumn<S, String>, TableCell<S, String>> forTableColumn(
IntFunction<Property<String>> extractor) {
return forTableColumn(extractor, new DefaultStringConverter());
}
public static <S, T> Callback<TableColumn<S, T>, TableCell<S, T>> forTableColumn(
IntFunction<Property<T>> extractor, StringConverter<T> converter) {
Objects.requireNonNull(extractor);
Objects.requireNonNull(converter);
return column -> new CustomTableCell<>(extractor, converter);
}
private final ObjectProperty<IntFunction<Property<T>>> extractor = new SimpleObjectProperty<>(this, "extractor");
public final void setExtractor(IntFunction<Property<T>> callback) { extractor.set(callback); }
public final IntFunction<Property<T>> getExtractor() { return extractor.get(); }
public final ObjectProperty<IntFunction<Property<T>>> extractorProperty() { return extractor; }
private final ObjectProperty<StringConverter<T>> converter = new SimpleObjectProperty<>(this, "converter");
public final void setConverter(StringConverter<T> converter) { this.converter.set(converter); }
public final StringConverter<T> getConverter() { return converter.get(); }
public final ObjectProperty<StringConverter<T>> converterProperty() { return converter; }
private Property<T> property;
private TextField textField;
public CustomTableCell(IntFunction<Property<T>> extractor, StringConverter<T> converter) {
setExtractor(extractor);
setConverter(converter);
// Assumes this TableCell will never become part of a different TableView
// after the first one. Also assumes the focus model of the TableView will
// never change. These are not great assumptions (especially the latter),
// but this is only an example.
tableViewProperty().addListener((obs, oldTable, newTable) ->
newTable.getFocusModel().focusedCellProperty().addListener((obs2, oldPos, newPos) -> {
if (getIndex() == newPos.getRow() && getTableColumn() == newPos.getTableColumn()) {
textField.requestFocus();
}
})
);
}
@Override
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
cleanUpProperty();
} else {
initializeTextField();
cleanUpProperty();
property = getExtractor().apply(getIndex());
Bindings.bindBidirectional(textField.textProperty(), property, getConverter());
setGraphic(textField);
if (getTableView().getFocusModel().isFocused(getIndex(), getTableColumn())) {
textField.requestFocus();
}
}
}
private void cleanUpProperty() {
if (property != null) {
Bindings.unbindBidirectional(textField.textProperty(), property);
property = null;
}
}
private void initializeTextField() {
if (textField == null) {
textField = new TextField();
textField.addEventFilter(KeyEvent.KEY_PRESSED, this::processArrowKeys);
textField.focusedProperty().addListener((observable, wasFocused, isFocused) -> {
if (isFocused) {
getTableView().getFocusModel().focus(getIndex(), getTableColumn());
}
});
}
}
private void processArrowKeys(KeyEvent event) {
if (event.getCode().isArrowKey()) {
event.consume();
TableViewFocusModel<S> model = getTableView().getFocusModel();
switch (event.getCode()) {
case UP:
model.focusAboveCell();
break;
case RIGHT:
model.focusRightCell();
break;
case DOWN:
model.focusBelowCell();
break;
case LEFT:
model.focusLeftCell();
break;
default:
throw new AssertionError(event.getCode().name());
}
getTableView().scrollTo(model.getFocusedCell().getRow());
getTableView().scrollToColumnIndex(model.getFocusedCell().getColumn());
}
}
}
单元选择模式
然而,您试图实现的这种行为似乎本质上是基于单元格的,因此启用单元格选择可能更好。这允许自定义TableCell
将其行为基于选择,而不是聚焦,并将箭头键处理留给TableView
上述例子:
import java.util.Objects;
import java.util.function.IntFunction;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.EventDispatcher;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.util.Callback;
import javafx.util.StringConverter;
import javafx.util.converter.DefaultStringConverter;
public class CustomTableCell<S, T> extends TableCell<S, T> {
/*
* -- CODE OMITTED --
*
* The factory methods (forTableColumn) and properties (extractor
* and converter) have been omitted for brevity. They are defined
* and used exactly the same way as in the previous example.
*/
private Property<T> property;
private TextField textField;
public CustomTableCell(IntFunction<Property<T>> extractor, StringConverter<T> converter) {
setExtractor(extractor);
setConverter(converter);
}
@Override
public void updateSelected(boolean selected) {
super.updateSelected(selected);
if (selected && !isEmpty()) {
textField.requestFocus();
}
}
@Override
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
clearProperty();
} else {
initializeTextField();
clearProperty();
property = getExtractor().apply(getIndex());
Bindings.bindBidirectional(textField.textProperty(), property, getConverter());
setGraphic(textField);
if (isSelected()) {
textField.requestFocus();
}
}
}
private void clearProperty() {
if (property != null) {
Bindings.unbindBidirectional(textField.textProperty(), property);
textField.setText(null);
property = null;
}
}
private void initializeTextField() {
if (textField == null) {
textField = new TextField();
textField.focusedProperty().addListener((observable, wasFocused, isFocused) -> {
if (isFocused && !isSelected()) {
getTableView().getSelectionModel().clearAndSelect(getIndex(), getTableColumn());
}
});
/*
* TableView has key handlers that will select cells based on arrow keys being
* pressed, scrolling to them if necessary. I find this mechanism looks cleaner
* because, unlike TableView#scrollTo, it doesn't cause the cell to jump to the
* top of the TableView.
*
* The way this works is by bypassing the TextField if, and only if, the event
* is a KEY_PRESSED event and the pressed key is an arrow key. This lets the
* event bubble up back to the TableView and let it do what it needs to. All
* other key events are given to the TextField for normal processing.
*
* NOTE: The behavior being relied upon here is added by the default TableViewSkin
* and its corresponding TableViewBehavior. This may not work if a custom
* TableViewSkin skin is used.
*/
EventDispatcher oldDispatcher = textField.getEventDispatcher();
textField.setEventDispatcher((event, tail) -> {
if (event.getEventType() == KeyEvent.KEY_PRESSED
&& ((KeyEvent) event).getCode().isArrowKey()) {
return event;
} else {
return oldDispatcher.dispatchEvent(event, tail);
}
});
}
}
}
导入java.util.Objects;
导入java.util.function.IntFunction;
导入javafx.beans.binding.Bindings;
导入javafx.beans.property.ObjectProperty;
导入javafx.beans.property.property;
导入javafx.beans.property.SimpleObject属性;
导入javafx.event.EventDispatcher;
导入javafx.scene.control.TableCell;
导入javafx.scene.control.TableColumn;
导入javafx.scene.control.TextField;
导入javafx.scene.input.KeyEvent;
导入javafx.util.Callback;
导入javafx.util.StringConverter;
导入javafx.util.converter.DefaultStringConverter;
公共类CustomTableCell扩展了TableCell{
/*
*--代码省略--
*
*工厂方法(forTableColumn)和属性(提取器
*为了简洁起见,省略了(和转换器)。定义了
*并使用与上一个示例完全相同的方法。
*/
私有财产;
私有文本字段文本字段;
公共CustomTableCell(IntFunction提取器、StringConverter转换器){
设置提取器(提取器);
设置转换器(转换器);
}
@凌驾
已选择公共无效更新(已选择布尔值){
super.updateSelected(已选择);
如果(已选择&&!isEmpty()){
textField.requestFocus();
}
}
@凌驾
受保护的void updateItem(T项,布尔值为空){
super.updateItem(项,空);
if(空){
setText(空);
设置图形(空);
clearProperty();
}否则{
initializeTextField();
clearProperty();
property=getExtractor().apply(getIndex());
Bindings.bindBidirective(textField.textProperty(),property,getConverter());
设置图形(文本字段);
if(isSelected()){
textField.requestFocus();
}
}
}
私有无效clearProperty(){
if(属性!=null){
Bindings.UnbindBioderial(textField.textProperty(),property);
textField.setText(空);
属性=null;
}
}
私有void initializeTextField(){
if(textField==null){
textField=新的textField();
textField.focusedProperty().addListener((可观察、聚焦、聚焦)->{
如果(isFocused&!isSelected()){
getTableView().getSelectionModel().clearAndSelect(getIndex(),getTableColumn());
}
});
/*
*TableView具有键处理程序,该处理程序将根据正在使用的箭头键选择单元格
*按下,必要时滚动到它们。我发现这个机制看起来更干净
*因为,与TableView#scrollTo不同,它不会导致单元格跳转到
*桌面视图的顶部。
*
*其工作方式是当且仅当事件发生时绕过TextField
*是按键按下事件,按键是箭头键。这使
*事件气泡返回到TableView,让它做它需要做的事情
*其他关键事件将提供给TextField进行正常处理。
*
*注意:此处所依赖的行为是由默认的TableViewSkin添加的
*以及相应的TableViewBehavior。如果自定义
*使用TableViewSkin。
*/
EventDispatcher oldDispatcher=textField.getEventDispatcher();
textField.setEventDispatcher((事件,尾部)->{
如果(event.getEventType()==KeyEvent.KEY_)被按下
&&((KeyEvent)event.getCode().isArrowKey()){
返回事件;
}否则{
返回oldDispatcher.dispatchEvent(事件,尾部);
}
});
}
}
}