【问题标题】:TreeView/TreeTableView - KeyEvent F2 causes JavaFX internal NPETreeView/TreeTableView - KeyEvent F2 导致 JavaFX 内部 NPE
【发布时间】:2020-07-12 04:57:13
【问题描述】:

通过这样做在 TreeTableView 的第 0 列上放置了一个“标准”编辑器:

treeTableView.columns.get(0).setCellFactory( TextFieldTreeTableCell.forTreeTableColumn());

...很高兴发现有几件事开始编辑TreeItem:一个是单击单元格,另一个是按 F2。

然而,令我有些沮丧的是,如果我启动应用程序并且没有使用鼠标选择 TreeItem,而是以编程方式选择了根 TreeItem 的第一个子项,如果我当时(可能在导航后仅使用键盘键)按 F2,JavaFX 内部抛出一个 NPE,如下所示:

java.lang.NullPointerException: null
    at com.sun.javafx.scene.control.behavior.TableViewBehaviorBase.activate(TableViewBehaviorBase.java:890)
    at com.sun.javafx.scene.control.inputmap.InputMap.handle(InputMap.java:274)
    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.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$KeyHandler.process(Scene.java:4058)
    at javafx.scene.Scene$KeyHandler.access$1500(Scene.java:4004)
    at javafx.scene.Scene.processKeyEvent(Scene.java:2121)
    at javafx.scene.Scene$ScenePeerListener.keyEvent(Scene.java:2595)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:217)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:149)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleKeyEvent$1(GlassViewEventHandler.java:248)
    at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:390)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleKeyEvent(GlassViewEventHandler.java:247)
    at com.sun.glass.ui.View.handleKeyEvent(View.java:547)
    at com.sun.glass.ui.View.notifyKey(View.java:971)
    at com.sun.glass.ui.gtk.GtkApplication._runLoop(Native Method)
    at com.sun.glass.ui.gtk.GtkApplication.lambda$runLoop$11(GtkApplication.java:277)

不可能鼠标直接把焦点放在TreeItem上,因为TreeItem的超类是Object。我在Scene 上放置了一个焦点所有者监听器,它确认当您单击TreeTableView 时没有任何与焦点相关的变化。然而,从质量上来说,确实发生了一些变化。

也许这与 BehaviorInputMap、“皮肤”或我不知道的许多神秘 JavaFX 事物中的任何一种有关。

在这种情况下,在我看来,一种选择可能是以某种方式拦截 F2 击键,并阻止默认功能,并以编程方式发起对已选择但没有焦点的 TreeItem 的编辑。

注意,我在TreeTableView 上放置了一个“按下键”和“释放键”事件监听器。其中包括以下行:

TreeTablePosition pos = treeTableView.getFocusModel().getFocusedCell();

我所描述的“异常”事件(JavaFX 内部抛出 NPE)的特点是,在“密钥释放”侦听器中,pos.col == -1pos.getTableColumn() == null。我还可以从日志记录中看出,NPE 发生在“密钥释放”处理程序响应之前。

有关信息,当我按下 F2 时,“按键”侦听器从不记录任何内容,而没有先用鼠标单击 TreeItem(毫无疑问,JavaFX 内部的 Exception 会扼杀对侦听器的正常“广播”) ,导致我假设 JavaFX 框架也位于任何用户添加的按键侦听器之前。

【问题讨论】:

    标签: java javafx treeview edit treetableview


    【解决方案1】:

    如果您使用TableSelectionModel#select(int,TableColumnBase) 选择您的单元格,则不会发生 NPE。

    选择给定行/列交叉处的单元格。如果表格控件处于其“单元格选择”模式(可以选择单个单元格,而不是整行),并且如果 column 参数为空,则此方法应选择给定行中的所有单元格。

    例如:

    TreeTableColumn col = treeTableView.getColumns().get(0);
    treeTableView.getSelectionModel().select(rowIndex, col);
    

    有了以上内容,您就不必担心焦点模型了。这样做的原因是因为默认选择模型实现将为您关注给定的行+列(即单元格)(不确定这是否是有保证的行为,但它肯定是礼貌的行为)。

    基本上,您当前的代码导致选择/聚焦一行,但不是该行中的特定单元格。因此,当您尝试进入“编辑模式”时,无法确定要定位哪个单元格。我认为 NPE 是一个错误。不确定当没有选择/关注任何列时会发生什么意味着,但控件的行为应该可以优雅地处理这种情况。

    如果有兴趣,下面是我的调试过程。


    这是负责 NPE 的代码:

    protected void activate(KeyEvent e) {
        TableSelectionModel sm = getSelectionModel();
        if (sm == null) return;
    
        TableFocusModel fm = getFocusModel();
        if (fm == null) return;
    
        TablePositionBase<TC> cell = getFocusedCell();
        sm.select(cell.getRow(), cell.getTableColumn());
        setAnchor(cell);
    
        // check if we are editable
        boolean isEditable = isControlEditable() && cell.getTableColumn().isEditable();
    
        // edit this row also
        if (isEditable && cell.getRow() >= 0) {
            editCell(cell.getRow(), cell.getTableColumn());
            e.consume();
        }
    }
    

    具体来说,这是有问题的行:

    boolean isEditable = isControlEditable() && cell.getTableColumn().isEditable();
    

    这意味着 cell 为 null 或 getTableColumn() 返回 null。在JEP 358: Helpful NullPointerExceptions 的帮助下进行了一些调试后,我们知道是后者:

    Exception in thread "JavaFX Application Thread" java.lang.NullPointerException: Cannot invoke "javafx.scene.control.TableColumnBase.isEditable()" because the return value of "javafx.scene.control.TablePositionBase.getTableColumn()" is null
        at javafx.controls/com.sun.javafx.scene.control.behavior.TableViewBehaviorBase.activate(TableViewBehaviorBase.java:898)
    

    如果getTableColumn() 返回空值,那么这意味着,就焦点模型而言,没有聚焦的列,因此没有聚焦特定的单元格。解决方法是选择/聚焦特定的行+列(即单元格),而不仅仅是整行。

    【讨论】:

    • 太棒了。非常感谢您对此感兴趣。只是对您的代码进行一些建议的更改。为了记录,我是这样选择的:ttv.getSelectionModel().selectFirst();
    • 这只解决了部分问题:如果您在最后一列之后的一行中单击鼠标,仍然会发生异常......
    【解决方案2】:

    给出的答案仅涵盖问题的一部分:例如如果您在最后一列后面用鼠标单击并按 F2,那么 startEdit 仍然会抛出 NPE。

    我通过安装一个焦点侦听器在我的项目中解决了这个问题,只要没有有效的焦点列(无论出于何种原因),它就会选择默认列:

    //guarantee that always a valid column is selected to avoid NPE during startEdit
    tableView.getFocusModel().focusedCellProperty().addListener((observable, oldValue, newValue) -> {
      if (newValue.getTableColumn() == null){
        tableView.getSelectionModel().select(newValue.getRow(), defaultColumn);
      }
    });
    

    例如如果 defaultColumn 应该是您可以设置的第一列

    defaultColumn = tableView.getColumns.get(0)
    

    【讨论】:

      【解决方案3】:

      找到答案(或答案)。事实上,TreeTableView 内部有一个焦点系统...参见getFocusModel()Stage 的焦点所有者机制似乎没有注意到这方面的变化:

      stage.getScene().focusOwnerProperty().addListener( new ChangeListener(){
      ...
      

      所以你要做的就是在选择改变时改变内部焦点模型:

      treeTableView.getSelectionModel().getSelectedItems().addListener(new ListChangeListener() {
          void onChanged(ListChangeListener.Change c) {
              TreeItem newSelectedItem = c.list.get(0);
              int row = treeTableView.getRow( newSelectedItem );
              treeTableView.getFocusModel().focus( row, treeTableView.getColumns().get(0) );
          }
      }
      

      对此有一两点需要说明:

      1. 我不完全确定 c.list.get( 0 ) 总是能保证交付新选择的项目...如果选择模式是多选,则尤其如此
      2. 我有点惊讶地发现,即使您单击表格列之一(即列 1、2、3...)中的单元格,而不是树列(列 0),您总是似乎从第 0 列返回了 TreeItem。在普通表格中,您当然可以随机选择一个单元格块。我现在想知道您是否可以使用此控件执行此操作,或者所有选择是否真的是“行选择”。
      3. 我原以为上述功能应该包含在该类的默认实现中:如果讨厌使用或不能使用鼠标的人的简单键盘操作引发 JavaFX 异常(也意味着编辑操作不会发生)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-04-26
        • 2022-11-23
        • 1970-01-01
        • 2019-12-27
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多