JavaFx: ComboBox Table Cell double click

送分小仙女□ 提交于 2021-02-10 10:22:50

问题


The problem is the following:

I have a TableView with ComboBoxes where for every TableCell I can select a value from the comboboxes. The problem is, if I have a lot of rows and columns I have to click a lot to select the appropriate value in every comboBox. To select a value in a combobox I have to click four times to select the value. Once to select the cell, once to set the graphics the comboBox, again to open the popup for the combobox where I can select the value and finally to select the value.

I would like to use doubleClick, so I can open fast the comboBox, then I select the value. This would save a click and a lot of time if I have a lot of values to select.

I tried to solve it ,but none of the solutions worked correctly,

I add them here, maybe you can see where I went wrong and correct it.

I tried two similar ways:

  1. ignore startEdit() and add a mouseclick listener to the cell, and pop the comboBox on double click. This has the problem if I click another cell, the previous doesn't set the graphics to null, even if I put the setGrapichs(null) both in cancelEdit and commitEdit. Another problem is that sometimes doesn't commits the value to the model.

  2. The second approach was to handle it in startEdit(), so simply call the .show() there and .hide() it in both commit and cancel edit, depending on action. This gives me a NPE, if I wrap the TableView in a TitledPane and after I collapse/expand it, I try to select a value, after double click it gives 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)
    

Here is the code where you can check:

TableCell:

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());
    }
}

Controller:

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>

I would prefer any solution which provides me the expected result, you can suggest me even a different solution, which uses less workarounds or a fix for one of my suggested "solutions".

JDK version 1.8.0_121


回答1:


I encountered the same issue with Method (2), with mine being in the condition below when clicked. The comboBox appears, but just won't expand. Further click will not trigger anything.

In the end, I implemented a focus listener on the comboBox which will show the popup when it has focus. I called requestFocus at the end of startEdit.

// 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()
    }
} 

The delay above is required for the comboBox to draw its layout correctly. Or else, you will end up with the following condition randomly, depending on whether the layout already updates or not.

With that small delay, you will get it correct all the time.

P.S. If you are thinking, why not just directly call comboBox.show() after a small delay and remove the focusListener? Well, in my test, the comboBox just fail to show randomly (similar to 1st image) when you create new row or call table.refresh(). I guess it has something to do with virtualFlow and multiple instances of tableCell being created for the same instance. See here.



来源:https://stackoverflow.com/questions/54292210/javafx-combobox-table-cell-double-click

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!