How to edit a JXTreeTable cell in only one mouse click?
Asked Answered
G

2

7

I want to use a JComboBox as a cell editor in a JXTreeTable. It works fine with a standard DefaultCellEditor (i.e. with a click count to start equal to 2).

Now I want the column to be editable on only one click. So I added a cellEditor.setClickCountToStart(1); statement to my code.

Here is my SSCCE:

import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.DefaultCellEditor;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import org.jdesktop.swingx.JXTreeTable;
import org.jdesktop.swingx.treetable.DefaultMutableTreeTableNode;
import org.jdesktop.swingx.treetable.DefaultTreeTableModel;

public class TestCellEditorForJXTreeTable {

    /** The JXTreeTable */
    JXTreeTable treeTable;
    /** The model */
    DefaultTreeTableModel treeTableModel;

    /** Constructor */
    public TestCellEditorForJXTreeTable() {

        treeTable = new JXTreeTable();
        treeTableModel = new DefaultTreeTableModel() {
            @Override
            public String getColumnName(int column) {
                switch (column) {
                    case 0:
                        return "A";
                    case 1:
                        return "B";
                }
                return null;
            }
            @Override
            public Object getValueAt(Object node, int column) {
                switch (column) {
                    case 0:
                        return ((DefaultMutableTreeTableNode) node).getUserObject();
                    case 1:
                        return "Value in B";
                }
                return null;
            }
            @Override
            public int getColumnCount() {
                return 2;
            }
            @Override
            public boolean isCellEditable(Object node, int column) {
                return column == 1;
            }
        };
        treeTable.setTreeTableModel(treeTableModel);

    }

    public static void main(String[] args) {
        TestCellEditorForJXTreeTable test = new TestCellEditorForJXTreeTable();

        // Root node
        DefaultMutableTreeTableNode root = new DefaultMutableTreeTableNode("root");
        test.treeTableModel.setRoot(root);

        // New nodes/rows
        DefaultMutableTreeTableNode node = new DefaultMutableTreeTableNode("child_node");
        test.treeTableModel.insertNodeInto(node, root, 0);
        DefaultMutableTreeTableNode node2 = new DefaultMutableTreeTableNode("child_node2");
        test.treeTableModel.insertNodeInto(node2, root, 1);

        // Showing the frame
        showTable(test.treeTable);

        // Setting the cell editor
        DefaultCellEditor cellEditor = new DefaultCellEditor(new JComboBox(new String[]{"1", "2", "3"}));
        cellEditor.setClickCountToStart(1);
        test.treeTable.getColumn(1).setCellEditor(cellEditor);

    }

    /** Shows a JXTreeTable in a frame */
    private static void showTable(JXTreeTable table) {
        JFrame frame = new JFrame("Testing cell editor for JXTreeTable");
        frame.setPreferredSize(new Dimension(640, 480));
        frame.setLayout(new BorderLayout());
        frame.add(table, BorderLayout.CENTER);
        frame.pack();
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
}

But now it looks pretty ugly:

When I click on an editable cell it opens the JComboBox popup menu (Great! It's what I was expecting!), but this popup menu is immediately closed (Erf!). It flashes. I have to click a second time on the selected cell to get it definitively opened.

The problem repeats each time I select another cell in the editable column.

How could I get the JComboBox popup menu really opened after the first click?

Thanks.

Edit 2014-01-24

Here is the same example, but using JTable. The JComboBox popup menu does not flash.

import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.DefaultCellEditor;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;

public class TestCellEditorForJTable {

    /** The JTable */
    JTable table;
    /** The model */
    DefaultTableModel tableModel;

    /** Constructor */
    public TestCellEditorForJTable() {
        table = new JTable();
        tableModel = new DefaultTableModel(new String[] {"A", "B"}, 0) {
            @Override
            public boolean isCellEditable(int row, int column) {
                return column == 1;
            }
        };
        table.setModel(tableModel);

    }

    public static void main(String[] args) {
        TestCellEditorForJTable test = new TestCellEditorForJTable();

        // New rows
        test.tableModel.insertRow(0, new String[] {"Value1 in A", "Value1 in B"});
        test.tableModel.insertRow(1, new String[] {"Value2 in A", "Value2 in B"});

        // Showing the frame
        showTable(test.table);

        // Setting the cell editor
        DefaultCellEditor cellEditor = new DefaultCellEditor(new JComboBox(new String[]{"1", "2", "3"}));
        cellEditor.setClickCountToStart(1);
        test.table.getColumnModel().getColumn(1).setCellEditor(cellEditor);

    }

    /** Shows a table in a frame */
    private static void showTable(JTable table) {
        JFrame frame = new JFrame("Testing cell editor for JTable");
        frame.setPreferredSize(new Dimension(640, 480));
        frame.setLayout(new BorderLayout());
        frame.add(table, BorderLayout.CENTER);
        frame.pack();
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
}

And I forgot to mention that I'm using Java 1.6.

Edit 2014-01-24 (2)

Using the ContainerListener and the FocusListener of the kleopatra's answer, and running the same execution flow, I get the following output with the JXTreeTable SSCCE:

// first click
24.01.2014 13:10:59 my.pkg.TestCellEditorForJXTreeTable$2 componentAdded
INFO: java.awt.event.ContainerEvent[COMPONENT_ADDED...JXTreeTable...
24.01.2014 13:10:59 my.pkg.TestCellEditorForJXTreeTable$3 focusGained
INFO: java.awt.FocusEvent[FOCUS_GAINED...JXTreeTable...
24.01.2014 13:10:59 my.pkg.TestCellEditorForJXTreeTable$2 componentRemoved
INFO: java.awt.event.ContainerEvent[COMPONENT_REMOVED...JXTreeTable...
24.01.2014 13:10:59 my.pkg.TestCellEditorForJXTreeTable$3 focusLost
INFO: java.awt.FocusEvent[FOCUS_LOST...JXTreeTable...

// second click
24.01.2014 13:11:02 my.pkg.TestCellEditorForJXTreeTable$2 componentAdded
INFO: java.awt.event.ContainerEvent[COMPONENT_ADDED...JXTreeTable...
24.01.2014 13:11:02 my.pkg.TestCellEditorForJXTreeTable$3 focusGained
INFO: java.awt.FocusEvent[FOCUS_GAINED...JXTreeTable...
24.01.2014 13:11:02 my.pkg.TestCellEditorForJXTreeTable$2 componentRemoved
INFO: java.awt.event.ContainerEvent[COMPONENT_REMOVED...JXTreeTable...
24.01.2014 13:11:02 my.pkg.TestCellEditorForJXTreeTable$3 focusLost
INFO: java.awt.FocusEvent[FOCUS_LOST...JXTreeTable...
Gavrila answered 23/1, 2014 at 13:27 Comment(6)
Interesting fact: If you pick a value from the combobox then the editor works as expected (next click on an editable cell will open the combobox of that cell as you wish), but if you close the combobox without selecting a value then it behaves as you describe.Nertie
unfortunately (or fortunately for me, as I can point fingers :-), it's not specific for J/X/Tree/Table - same/similar behaviour as a plain table. The editor with a combo is not well implemented ... from the top of my head, don't remember any fix/workaround.Natale
@Natale could you take a look to my answer and let me know if this is the way to go through? Thanks in advance.Nertie
@Natale I get the same behavior using a JTextField instead of a JComboBox (i.e. replacing new DefaultCellEditor(new JComboBox(new String[]{"1", "2", "3"})); by new DefaultCellEditor(new JTextField("test edition"));). The editor component still flashes. BTW, I just tested with a simple JTable and it worked. (at least the JComboBox popup menu was not flashing). See my edit.Gavrila
don't know what you mean by it worked - shows the same misbehaviour: the popup isn't opened ;-) Or occasionally flashes, that is shortly opens and hides again. Happens only if you click in the same column. Don't see any "flash" (nor other problem) with a textField. Can't see any difference in behaviour between 6/7.Natale
See my comment under your answer.Gavrila
N
4

Tricky bugger - and I think it's indeed a core issue.

Let's first define exactly what/when it is happening: take the plain table example (btw: +1 for the nice and concise SSCCE!)

  • run
  • click into cell (1, 1), that is last row, second column: table starts editing, combo's popup is showing
  • while still editing (note that is important to not click anywhere else in between), click into cell (0, 1): table starts editing that cell, combo's popup is hidden

Digging reveals the probable reason: it's an out-of-order focusLost received after the combo was added again as editing component. To see, register a containerListener to the table and a focusListener to the combo and print the events

ContainerListener containerL = new ContainerListener() {

    @Override
    public void componentRemoved(ContainerEvent e) {
        LOG.info("" + e);
    }

    @Override
    public void componentAdded(ContainerEvent e) {
        LOG.info("" + e);
    }
};
table.addContainerListener(containerL);
FocusListener focusL = new FocusListener() {

    @Override
    public void focusGained(FocusEvent e) {
        LOG.info("" + e);
       // following line is a hack around: force the popup open
       // ((JComboBox) cellEditor.getComponent()).setPopupVisible(true);
    }

    @Override
    public void focusLost(FocusEvent e) {
        LOG.info("" + e);
    }

};
cellEditor.getComponent().addFocusListener(focusL);

The output:

// first click
24.01.2014 12:13:44 org.jdesktop.swingx.table.TestCellEditorForJTable$2 componentAdded
INFO: java.awt.event.ContainerEvent[COMPONENT_ADDED,child=null] on javax.swing.JTable...
24.01.2014 12:13:44 org.jdesktop.swingx.table.TestCellEditorForJTable$3 focusGained
INFO: java.awt.FocusEvent[FOCUS_GAINED,permanent,opposite=javax.swing.JTable

// second click
24.01.2014 12:13:49 org.jdesktop.swingx.table.TestCellEditorForJTable$2 componentRemoved
INFO: java.awt.event.ContainerEvent[COMPONENT_REMOVED,child=null] on javax.swing.JTable
24.01.2014 12:13:49 org.jdesktop.swingx.table.TestCellEditorForJTable$2 componentAdded
INFO: java.awt.event.ContainerEvent[COMPONENT_ADDED,child=null] on javax.swing.JTable
// here's the problem: focusLost _after_ added again
24.01.2014 12:13:49 org.jdesktop.swingx.table.TestCellEditorForJTable$3 focusLost
INFO: java.awt.FocusEvent[FOCUS_LOST,permanent,opposite=javax.swing.JTable
24.01.2014 12:13:49 org.jdesktop.swingx.table.TestCellEditorForJTable$3 focusGained
INFO: java.awt.FocusEvent[FOCUS_GAINED,permanent,opposite=javax.swing.JTable

A quick hack could be to force the popup open in the focusListener. Didn't check for side-effects, though.

Natale answered 24/1, 2014 at 11:22 Comment(4)
Indeed! I just added the ((JComboBox) cellEditor.getComponent()).setPopupVisible(true); statement into the focusGained method and it worked exactly as I want, but only with the plain table (JTable) example. With my JXTreeTable example, combo's popup is still showing then immediately hidden. See my "Edit 2014-01-24 (2)" for the logs.Gavrila
Hmm, your treetable example worksforme (with the hack), both 6/7. Also, the sequence of events is the same for tree/table. What's your OS?Natale
Found! Your comment reminded me to have a look at the SwingX version I'm using. I'm using the 0.9 which has this flash bug, it disappears in the 1.6. I think I should have done that check first. Then it works like I want using the 1.6 version and your focusGained hack. Thank you and sorry for missing that first step.Gavrila
outch .. 0.9 is pre-history, happy update :-) The most recent is 1.6.5-1Natale
N
3

Interesting fact

If you pick a value from the combobox then the editor works as expected (next click on an editable cell will open the combobox of that cell as you wish), but if you close the combobox without selecting a value then it behaves as you describe. So the issue seems to be the combobox doesn't stop editing until you select a value or focus in some other component. Consequently the first click to another cell makes its own editor request focus instead of start editing.

Explanation

Looking closer at DefaultCellEditor implementation the problem is only an ActionListener is attached to the combobox causing a fireEditingStopped() call through the EditorDelegate when an item is selected but nothing happens when the combobox is closed or cancelled without selecting a value:

public DefaultCellEditor(final JComboBox comboBox) {
    editorComponent = comboBox;
    comboBox.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);
    delegate = new EditorDelegate() {...}
    comboBox.addActionListener(delegate); // delegate is the ActionListener
}

protected class EditorDelegate implements ActionListener, ItemListener, Serializable {

    ...

    public void actionPerformed(ActionEvent e) {
        DefaultCellEditor.this.stopCellEditing(); // This will finally call  fireEditingStopped();
    }
}

Solution

Make your own TableCellEditor using a combobox as editor and attach a PopupMenuListener to call fireEditingStopped() or fireEditingCanceled() as needed. For instance:

class ComboBoxEditor extends AbstractCellEditor implements TableCellEditor {

    private JComboBox editor;
    private int clickCountToStart = 2;
    private Object selectedValue;

    public ComboBoxEditor(Object[] selectableValues) {

        editor = new JComboBox(selectableValues);

        editor.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                selectedValue = editor.getSelectedItem();
                ComboBoxEditor.this.fireEditingStopped();
            }
        });

        editor.addPopupMenuListener(new PopupMenuListener() {
            @Override
            public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
                // Nothing to do here, it's not relevant to your purpose
            }

            @Override
            public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
                ComboBoxEditor.this.fireEditingStopped();
            }

            @Override
            public void popupMenuCanceled(PopupMenuEvent e) {
                ComboBoxEditor.this.fireEditingCanceled();
            }
        });
    }

    public void setClickCountToStart(int clickCountToStart) {
        this.clickCountToStart = clickCountToStart;
    }

    public int getClickCountToStart() {
        return clickCountToStart;
    }

    // Rest of implementation is up to you, look into DefaultCellEditor implementation
}
Nertie answered 23/1, 2014 at 15:7 Comment(3)
Thank you for your answer. But it does not work for me. The combo box is still flashing but even if the row is already selected (while the combo box of my example is correctly opened in that case). Maybe it's because I run Java 1.6 then the behavior is different from 1.7.Gavrila
nice idea, but not the reason, IMO :-) The notification from the cellEditor seems to be fine.Natale
@Natale thanks for the feedback :) I didn't mention I've tried implementing the editor in this way and it solves this creepy behavior (at least for me). That's why I've concluded the explained reason. Anyway the reason you've posted makes sense and it's testable so it's widely better than mine.Nertie

© 2022 - 2024 — McMap. All rights reserved.