【问题标题】:JavaFx: ComboBox Table Cell double clickJavaFx:组合框表格单元格双击
【发布时间】:2019-01-21 14:36:36
【问题描述】:

问题如下:

我有一个 TableViewComboBoxes 我可以选择的每个 TableCell 组合框中的值。问题是,如果我有很多行和列,我必须点击很多才能在每个组合框中选择适当的值。 要在组合框中选择一个值,我必须单击四次才能选择该值。一次选择单元格,一次设置图形组合框,再次打开组合框的弹出窗口,我可以在其中选择值,最后选择值。

我想使用 doubleClick,所以我可以快速打开组合框,然后选择值。如果我有很多值可供选择,这将节省一次点击和大量时间。

我试图解决它,但没有一个解决方案能正常工作,

我在这里添加它们,也许您可​​以看到我哪里出错并纠正它。

我尝试了两种类似的方法:

  1. 忽略 startEdit() 并向单元格添加鼠标单击侦听器,并在双击时弹出组合框。 如果我单击另一个单元格,则会出现问题,即使我将setGrapichs(null) 放在cancelEditcommitEdit 中,上一个单元格也不会将图形设置为空。另一个问题是有时不会将值提交给模型。

  2. 第二种方法是在 startEdit() 中处理它,因此只需在此处调用 .show() 并在提交和取消编辑中调用 .hide() 它,具体取决于操作。这给了我一个 NPE,如果我将 TableView 包装在一个 TitledPane 中并且在我折叠/展开它之后,我尝试选择一个值,双击后它会给出 NPE:

    Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
    at com.sun.javafx.scene.control.skin.ComboBoxPopupControl.positionAndShowPopup(ComboBoxPopupControl.java:197)
    at com.sun.javafx.scene.control.skin.ComboBoxPopupControl.show(ComboBoxPopupControl.java:170)
    at com.sun.javafx.scene.control.skin.ComboBoxBaseSkin.handleControlPropertyChanged(ComboBoxBaseSkin.java:127)
    at com.sun.javafx.scene.control.skin.ComboBoxListViewSkin.handleControlPropertyChanged(ComboBoxListViewSkin.java:159)
    at com.sun.javafx.scene.control.skin.BehaviorSkinBase.lambda$registerChangeListener$61(BehaviorSkinBase.java:197)
    at com.sun.javafx.scene.control.MultiplePropertyChangeListenerHandler$1.changed(MultiplePropertyChangeListenerHandler.java:55)
    at javafx.beans.value.WeakChangeListener.changed(WeakChangeListener.java:89)
    at com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(ExpressionHelper.java:182)
    at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81)
    at javafx.beans.property.ReadOnlyBooleanPropertyBase.fireValueChangedEvent(ReadOnlyBooleanPropertyBase.java:72)
    at javafx.beans.property.ReadOnlyBooleanWrapper.fireValueChangedEvent(ReadOnlyBooleanWrapper.java:103)
    at javafx.beans.property.BooleanPropertyBase.markInvalid(BooleanPropertyBase.java:110)
    at javafx.beans.property.BooleanPropertyBase.set(BooleanPropertyBase.java:144)
    at javafx.scene.control.ComboBoxBase.setShowing(ComboBoxBase.java:185)
    at javafx.scene.control.ComboBoxBase.show(ComboBoxBase.java:391)
    at stackoverflow.combo.ComboTableCell.startEdit(ComboTableCell.java:47)
    at javafx.scene.control.TableCell.updateEditing(TableCell.java:556)
    at javafx.scene.control.TableCell.lambda$new$26(TableCell.java:142)
    at javafx.beans.WeakInvalidationListener.invalidated(WeakInvalidationListener.java:83)
    at com.sun.javafx.binding.ExpressionHelper$Generic.fireValueChangedEvent(ExpressionHelper.java:349)
    at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81)
    at javafx.beans.property.ReadOnlyObjectPropertyBase.fireValueChangedEvent(ReadOnlyObjectPropertyBase.java:74)
    at javafx.beans.property.ReadOnlyObjectWrapper.fireValueChangedEvent(ReadOnlyObjectWrapper.java:102)
    at javafx.beans.property.ObjectPropertyBase.markInvalid(ObjectPropertyBase.java:112)
    at javafx.beans.property.ObjectPropertyBase.set(ObjectPropertyBase.java:146)
    at javafx.scene.control.TableView.setEditingCell(TableView.java:1145)
    at javafx.scene.control.TableView.edit(TableView.java:1459)
    at com.sun.javafx.scene.control.behavior.TableCellBehavior.edit(TableCellBehavior.java:108)
    at com.sun.javafx.scene.control.behavior.TableCellBehavior.edit(TableCellBehavior.java:38)
    at com.sun.javafx.scene.control.behavior.CellBehaviorBase.handleClicks(CellBehaviorBase.java:271)
    at com.sun.javafx.scene.control.behavior.TableCellBehaviorBase.simpleSelect(TableCellBehaviorBase.java:218)
    at com.sun.javafx.scene.control.behavior.TableCellBehaviorBase.doSelect(TableCellBehaviorBase.java:148)
    at com.sun.javafx.scene.control.behavior.CellBehaviorBase.mouseReleased(CellBehaviorBase.java:159)
    at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(BehaviorSkinBase.java:96)
    at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(BehaviorSkinBase.java:89)
    at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
    at javafx.event.Event.fireEvent(Event.java:198)
    at javafx.scene.Scene$MouseHandler.process(Scene.java:3757)
    at javafx.scene.Scene$MouseHandler.access$1500(Scene.java:3485)
    at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1762)
    at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2494)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:381)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:295)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$354(GlassViewEventHandler.java:417)
    at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:416)
    at com.sun.glass.ui.View.handleMouseEvent(View.java:555)
    at com.sun.glass.ui.View.notifyMouse(View.java:937)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191)
    at java.lang.Thread.run(Thread.java:745)
    

这里是你可以检查的代码:

表格单元:

public class ComboTableCell<T,S> extends TableCell<T,S> {

    private ComboBox<S> combo;

    public ComboTableCell(Collection<S> items) {
        combo = new ComboBox<>();
        combo.setItems(FXCollections.observableArrayList(items));
        combo.prefWidthProperty().bind(widthProperty());
        combo.valueProperty().addListener((observable, oldValue, newValue) -> commitEdit(newValue));
//      1. Solution with mouse event
//      this.setOnMouseClicked(event -> {
//          if(event.getClickCount() == 2){
//              combo.getSelectionModel().select(getItem());
//              setText(null);
//              setGraphic(combo);
//              if(!combo.isShowing()){
//                  combo.show();
//              }
//          }
//      });
    }

//  2. Solution with startEdit
    @Override
    public void startEdit() {
        combo.getSelectionModel().select(getItem());
        super.startEdit();
        setText(null);
        setGraphic(combo);
        if(!combo.isShowing()){
            combo.show();
        }
    }

    @Override
    protected void updateItem(S item, boolean empty) {
        super.updateItem(item, empty);
        if(empty){
            setText(null);
            setGraphic(null);
            return;
        }
        setText(getItem().toString());
        setGraphic(null);
    }

    @Override
    public void cancelEdit() {
        super.cancelEdit();
        setText(getItem().toString());
        setGraphic(null);
        if(combo.isShowing()){
            combo.hide();
        }
    }

    @Override
    public void commitEdit(S newValue) {
        super.commitEdit(newValue);
        setGraphic(null);
        setText(getItem().toString());
        if(combo.isShowing()){
            combo.hide();
        }
        setGraphic(null);
        setText(getItem().toString());
    }
}

控制器:

public class Controller implements Initializable {

    @FXML
    private TableView<Model> table;
    @FXML
    private TableColumn<Model,String> col;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        table.setEditable(true);

        col.setCellValueFactory(data -> data.getValue().text);
        col.setCellFactory(factory -> new ComboTableCell<>(Arrays.asList("a","b","c")));

        table.setItems(FXCollections.observableArrayList(Arrays.asList(new Model("a"),new Model("b"))));
    }

     static class Model{

        private StringProperty text;

        public Model(String text) {
            this.text = new SimpleStringProperty(text);
        }

        public String getText() {
            return text.get();
        }

        public StringProperty textProperty() {
            return text;
        }
    }

}

Fxml:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TitledPane?>
<AnchorPane xmlns="http://javafx.com/javafx"
            xmlns:fx="http://javafx.com/fxml"
            fx:controller="stackoverflow.combo.Controller">
    <TitledPane text="Table">
        <TableView fx:id="table">
            <columns>
                <TableColumn fx:id="col" prefWidth="200"/>
            </columns>
        </TableView>
    </TitledPane>
</AnchorPane>

我更喜欢任何能够为我提供预期结果的解决方案,您甚至可以向我建议一个不同的解决方案,该解决方案使用较少的变通方法或修复我建议的“解决方案”之一。

JDK版本1.8.0_121

【问题讨论】:

  • 当前未注释的代码工作表 - fx11。不过,没想到,因为“正常”的地方有很多调整..
  • 另一个注意事项:扩展 ComboBoxTableCell,覆盖 startEdit 并在调用 super 后打开弹出窗口似乎也可以。所以可能是版本问题,可能是修复了一些错误(单元格和编辑周围有很多;)
  • 我正在使用 jfx8,所以可能稍后会修复它,但我明天会检查您的建议。
  • 嗯...我已经尝试过使用 jfx11 并且确实它可以正常工作(对我来说是第二种方法),但不幸的是我无法使用它。
  • 对于 NPE:这是 ComboBoxPopupControl(ComboBoxListViewSkin 的超级皮肤)bugs.openjdk.java.net/browse/JDK-8196827 中的一个错误 - 在 fx11 中已修复。如果您被允许扩展内部类(就像 fx8 中的皮肤一样),那么快速解决方法是对皮肤进行子类化,在调用 super 之前覆盖 show 以检查空场景。

标签: java javafx combobox javafx-8


【解决方案1】:

我在方法(2)中遇到了同样的问题,点击时我的情况如下。组合框出现,但不会展开。进一步点击不会触发任何事情。

最后,我在组合框上实现了一个焦点监听器,当它有焦点时会显示弹出窗口。我在startEdit 的末尾打电话给requestFocus

// In the creation of comboBox, add focus listener
combo.focusProperty().addListener((observable, oldValue, isFocused) -> if (isFocused) combo.show());

@Override
public void startEdit() {
    combo.getSelectionModel().select(getItem());
    super.startEdit();
    setText(null);
    setGraphic(combo);
    // Creating a JavaFX task to make a small delay and then request focus.
    // Code below is in Kotlin and TornadoFX, but you get the point.
    runAsync {
        Thread.sleep(50)
    } ui {
        combo.requestFocus()
    }
} 

上面的延迟是组合框正确绘制其布局所必需的。否则,您将随机出现以下情况,具体取决于布局是否已更新。

只要有这么小的延迟,你就会一直得到正确的。

附:如果你在想,为什么不直接调用 comboBox.show() 并在一小段延迟后移除 focusListener 呢?好吧,在我的测试中,当您创建新行或调用 table.refresh() 时,组合框无法随机显示(类似于第一张图像)。我想这与virtualFlow 和为同一实例创建的多个tableCell 实例有关。见here

【讨论】:

  • 感谢您的努力,但由于这是一个非常老套的解决方案(不仅是您的,而且是快速打开组合框的想法)我决定不使用它,但始终显示组合框(setGraphics(组合))因此用户可以直接更改值,不需要在单元格上单击 2-3x。已经实现了这样的东西:stackoverflow.com/questions/55471652/….
  • 也与答案有关,我个人不太喜欢Thread.sleeps,因为它们不可靠
  • 我当然明白。来自Android背景,JavaFX真的很差,尤其是TableView。我正在构建一个工程软件,而 TableView 恰好是核心 UI。有很多黑客可以使它可用,所以任何额外的黑客都不会对我产生影响;-) 无论如何,在触发另一个 UI 更改之前使用延迟在 Android 中很常见,因此这个想法。我发布这个答案供我自己参考。
  • 另外,请解释一下为什么Thread.sleep 不可靠?也有不睡觉的情况?
  • 一般规则是永远不要(如在 reeeaaally never!)使 ui 线程休眠 - 虽然它可能看起来正在工作,但它阻止了事件的正常流动,总而言之,是难以测试和调试.. 最后你选择了一堆不可预知的问题。根本不要,从长远来看,这是不值得的。只是说,不过,我很清楚 TableView 周围的几乎所有东西都是有问题的您的设置可能有问题
猜你喜欢
  • 2015-11-28
  • 2016-05-09
  • 1970-01-01
  • 2014-07-22
  • 1970-01-01
  • 2012-09-12
  • 1970-01-01
  • 2016-01-27
  • 1970-01-01
相关资源
最近更新 更多