【发布时间】:2019-09-09 14:58:39
【问题描述】:
通过使用此示例使用箭头键遍历可编辑的TableView:
How to use arrow buttons to traverse cells in edit mode in TableView
按住向下箭头时会出现一个奇怪的问题。它似乎工作得很好,但如果你按下向下箭头键和向上箭头键,桌子就会开始陷入快速上下移动的循环中,你无法摆脱它。只有在表格上存在滚动条时才会发生这种情况,因此您必须向表格中添加一些项目。
这是我的代码:
Test.java
package test;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.stage.Stage;
public class Test extends Application
{
/**
* @param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception
{
TableView<Model> table = new TableView();
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getItems().add(new Model());
table.getSelectionModel().setCellSelectionEnabled(true);
TableColumn<Model, String> column = new TableColumn<>("Column");
column.setCellFactory(CustomTableCell.forTableColumn(i -> table.getItems().get(i).getNameProperty()));
table.getColumns().add(column);
final Scene scene = new Scene(table);
primaryStage.setScene(scene);
primaryStage.show();
}
public class Model
{
private SimpleStringProperty name;
public Model()
{
name = new SimpleStringProperty();
}
/**
* @return the string
*/
public SimpleStringProperty getNameProperty()
{
return name;
}
}
}
CustomTableCell.java
package test;
import java.util.Objects;
import java.util.function.IntFunction;
import javafx.application.Platform;
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>
{
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);
}
@Override
public void updateSelected(boolean selected)
{
super.updateSelected(selected);
if (selected && !isEmpty())
{
Platform.runLater(() -> 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())
{
Platform.runLater(() -> 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);
}
});
}
}
}
【问题讨论】:
-
我似乎无法重现该问题。您使用的是什么版本的 JavaFX?我使用 12.0.2 进行了测试。
-
我使用的是 JavaFX8
-
嗯。刚刚下载并尝试了包含 JavaFX 的 Java 8u222 的 Zulu 发行版,但仍然无法重现。如果您使用的是旧版本,那么可能已经修复了一个错误。按住向下箭头键,然后同时按住向上箭头键时应该会出现问题,对吗?因为也许我做错了。
-
我测试了它,它发生在221上---你能不能按住“向下”键直接走到桌子底部,然后按住向上键走所有到达顶部?我就是这样做的。我确实想通了,“清除和选择”必须包含在 Platform.runlater...
-
设法重现了 8u222 上的问题。似乎在选择一系列项目时卡住了。例如,它会选择项目 54,然后是 55,然后是 56,一直到 63,然后又回到 54——一遍又一遍;即使在释放密钥后也会发生这种情况。我不知道为什么会出现这个问题,但
Platform.runLater解决方案似乎确实有效。