Skip to main content

JXTreeTable and cell editing

15 replies [Last post]
uvoigt
Offline
Joined: 2006-01-26
Points: 0

When I run the following demo application I have a problem with the cell editor in the second column.

The JComboBox should open on the first click but I see only some short flicker of the combobox on the first click. It is opened on the second click.

The ComboEditItemListener should provide the functionality that the combobox opens on the first click and not on the second click.
With a JXTable the same editor code works (most of the time).

Any hints what I am doing wrong?

<br />
import java.awt.BorderLayout;<br />
import java.awt.Component;<br />
import java.awt.HeadlessException;<br />
import java.awt.event.ActionEvent;<br />
import java.awt.event.ActionListener;<br />
import java.awt.event.ItemEvent;<br />
import java.awt.event.ItemListener;<br />
import java.util.ArrayList;<br />
import java.util.Arrays;<br />
import java.util.List;</p>
<p>import javax.swing.*;<br />
import javax.swing.table.TableCellEditor;</p>
<p>import org.jdesktop.swingx.JXTreeTable;<br />
import org.jdesktop.swingx.combobox.ListComboBoxModel;<br />
import org.jdesktop.swingx.treetable.AbstractMutableTreeTableNode;<br />
import org.jdesktop.swingx.treetable.DefaultTreeTableModel;<br />
import org.jdesktop.swingx.treetable.MutableTreeTableNode;<br />
import org.jdesktop.swingx.treetable.TreeTableNode;</p>
<p>public class TestTreeTableFrame<br />
    extends JFrame<br />
{<br />
    public TestTreeTableFrame()<br />
        throws HeadlessException<br />
    {<br />
        super();</p>
<p>        JXTreeTable tb = new JXTreeTable();<br />
        final TestTreeTableModel dm = new TestTreeTableModel();<br />
        tb.setTreeTableModel(dm);<br />
        tb.setDefaultEditor(String.class, new StringCellEditor());<br />
        tb.setRootVisible(false);</p>
<p>        JScrollPane sp = new JScrollPane();<br />
        sp.setViewportView(tb);<br />
        setLayout(new BorderLayout());<br />
        add(sp, BorderLayout.CENTER);</p>
<p>        JButton addButton = new JButton("Add data");<br />
        addButton.addActionListener(new ActionListener()<br />
        {<br />
            @Override<br />
            public void actionPerformed(ActionEvent e)<br />
            {<br />
                dm.addRow();<br />
            }<br />
        });<br />
        add(addButton, BorderLayout.NORTH);<br />
    }</p>
<p>    static class TestData<br />
    {<br />
        int number;<br />
        String text;</p>
<p>        public TestData(int number, String text)<br />
        {<br />
            super();<br />
            this.number = number;<br />
            this.text = text;<br />
        }<br />
    }</p>
<p>    static class StringCellEditor<br />
        extends AbstractCellEditor<br />
        implements TableCellEditor<br />
    {<br />
        class ComboEditItemListener<br />
            implements ItemListener<br />
        {<br />
            private boolean enabled = false;</p>
<p>            public void setEnabled(boolean enabled)<br />
            {<br />
                this.enabled = enabled;<br />
            }</p>
<p>            @Override<br />
            public void itemStateChanged(ItemEvent e)<br />
            {<br />
                if (this.enabled && e.getStateChange() == ItemEvent.SELECTED)<br />
                {<br />
                    fireEditingStopped();<br />
                }<br />
            }<br />
        }</p>
<p>        private final ArrayList values;<br />
        private final JComboBox comboBox;<br />
        private final ComboEditItemListener itemListener;</p>
<p>        public StringCellEditor()<br />
        {<br />
            this.values = new ArrayList();<br />
            this.values.addAll(Arrays.asList("Value 1", "Value 2", "Value 3"));<br />
            this.comboBox = new JComboBox(new ListComboBoxModel(this.values));<br />
            this.itemListener = new ComboEditItemListener();<br />
            this.itemListener.setEnabled(false);<br />
            this.comboBox.addItemListener(this.itemListener);<br />
        }</p>
<p>        @Override<br />
        public Component getTableCellEditorComponent(JTable table, Object value,<br />
            boolean isSelected, int row, int column)<br />
        {<br />
            this.comboBox.setEnabled(false);<br />
            this.comboBox.setSelectedItem(String.valueOf(value));<br />
            this.comboBox.setEnabled(true);<br />
            return this.comboBox;<br />
        }</p>
<p>        @Override<br />
        public Object getCellEditorValue()<br />
        {<br />
            return this.comboBox.getSelectedItem();<br />
        }</p>
<p>    }</p>
<p>    static class TestTreeNode<br />
        extends AbstractMutableTreeTableNode<br />
    {<br />
        public TestTreeNode()<br />
        {<br />
            super();<br />
        }</p>
<p>        public TestTreeNode(TestData data)<br />
        {<br />
            super();<br />
            setUserObject(data);<br />
        }</p>
<p>        @Override<br />
        public int getColumnCount()<br />
        {<br />
            return 2;<br />
        }</p>
<p>        @Override<br />
        public Object getValueAt(int column)<br />
        {<br />
            if (getUserObject() == null)<br />
            {<br />
                switch (column)<br />
                {<br />
                case 0:<br />
                    return 0;<br />
                case 1:<br />
                    return "root";<br />
                }<br />
            }<br />
            TestData data = (TestData)getUserObject();<br />
            switch (column)<br />
            {<br />
            case 0:<br />
                return data.number;<br />
            case 1:<br />
                return data.text;<br />
            }<br />
            return null;<br />
        }</p>
<p>        @Override<br />
        public void setValueAt(Object aValue, int column)<br />
        {<br />
            TestData data = (TestData)getUserObject();<br />
            switch (column)<br />
            {<br />
            case 0:<br />
                data.number = Integer.valueOf(String.valueOf(aValue));<br />
                break;<br />
            case 1:<br />
                data.text = String.valueOf(aValue);<br />
                break;<br />
            default:<br />
                return;<br />
            }<br />
        }</p>
<p>    }</p>
<p>    static class TestTreeTableModel<br />
        extends DefaultTreeTableModel<br />
    {<br />
        private final List data = new ArrayList();</p>
<p>        public TestTreeTableModel()<br />
        {<br />
            super(new TestTreeNode());<br />
        }</p>
<p>        @Override<br />
        public int getColumnCount()<br />
        {<br />
            return ((TreeTableNode)this.root).getColumnCount();<br />
        }</p>
<p>        @Override<br />
        public Class<?> getColumnClass(int columnIndex)<br />
        {<br />
            switch (columnIndex)<br />
            {<br />
            case 0:<br />
                return Integer.class;<br />
            case 1:<br />
                return String.class;<br />
            }<br />
            return super.getColumnClass(columnIndex);<br />
        }</p>
<p>        public void addRow()<br />
        {<br />
            TestData newRow = new TestData(1, "Value 1");<br />
            this.data.add(newRow);</p>
<p>            MutableTreeTableNode rootNode = (MutableTreeTableNode)this.root;<br />
            TestTreeNode tn = new TestTreeNode(newRow);<br />
            int n = rootNode.getChildCount();<br />
            insertNodeInto(tn, rootNode, n);<br />
        }</p>
<p>        @Override<br />
        public boolean isCellEditable(Object node, int column)<br />
        {<br />
            return node != this.root;<br />
        }<br />
    }</p>
<p>    public static void main(String[] args)<br />
    {<br />
        SwingUtilities.invokeLater(new Runnable()<br />
        {<br />
            public void run()<br />
            {<br />
                JFrame f = new TestTreeTableFrame();</p>
<p>                f.setVisible(true);<br />
                f.pack();<br />
            }<br />
        });</p>
<p>    }</p>
<p>}</p>
<p>

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
ramswaroop123
Offline
Joined: 2011-11-29
Points: 0

Hi,

I have problem in editing JXTreeTable's Leaf. After i edit a leaf of JXTreeTable I found what I updated is lost, How can I persist all changes I made to these edited leaves.

In JTree this can be done using "tree.setInvokesStopCellEditing(true);"

I tried googling but could not find how to implement the same in JXTreeTable. Please help me to get out of this.

thnx in advance.

Regards

swaroop

aephyr
Offline
Joined: 2009-11-20
Points: 0

I was only able to uncover the following after I investigated how you went about the fix which was fine tuning the hacker's completeEditing method. But it was noted that it would still be a problem if the editor was in the hierarchy column. I had a hint that maybe the method was being called unnecessarily as the original behavior was to misuse (abuse) the editor as a means to do the tree expansion/collapsion [real word, check the urban dictionary if you don't believe ;)]. The weeds of that implementation were never taken out as far as I know.

Anyway, I had the handy IDE do an "open call hierarchy" on that completeEditing method in the hacker and noticed it was called from one place: JXTreeTable.setExpandedState(TreePath path, boolean state). So I commented out the call and tested the behavior. Well, it turns out I was partially wrong. The method does need to be invoked before an expansion change - seems obvious now that I consider it. However, it still is being called unnecessarily as setExpandedState(...) may not actually change the expanded state of the path. So changing the method as follows seems to fix the problem without changing anything to the completeEditing method and should work for the hierarchy column as well - just a bold assumption on my part :)

[code]
@Override
protected void setExpandedState(TreePath path, boolean state) {
if (isExpanded(path) != state) {
treeTable.getTreeTableHacker().completeEditing();
super.setExpandedState(path, state);
treeTable.getTreeTableHacker().expansionChanged();
}
}
[/code]

Edit: Worth mentioning is what became obvious but left unsaid: The reason completeEditing needs to be invoked is because JTable/JXTable(I think) doesn't stop edits on model changes. So that bug I had posted awhile back regarding adding/deleting rows while a cell is being edited is reintroduced into JXTreeTable with the change as applied to the completeEditing method (reintroduced for non-hierarchy columns anyway).

kleopatra
Offline
Joined: 2003-06-11
Points: 0

>
> Edit: Worth mentioning is what became obvious but
> left unsaid: The reason completeEditing needs to be
> invoked is because JTable/JXTable(I think) doesn't
> stop edits on model changes. So that bug I had posted
> awhile back regarding adding/deleting rows while a
> cell is being edited is reintroduced into JXTreeTable
> with the change as applied to the completeEditing
> method (reintroduced for non-hierarchy columns
> anyway).

f*** ... how I hate these hacks ... completely unmanageable ..

need to reopen #1126 and think of something else. Problem is a missing isExpandedState() in core JTree: isExpanded is not a complete overlap, if I remember correctly.

Thanks
Jeanette

ramswaroop123
Offline
Joined: 2011-11-29
Points: 0

Hi,
I have problem in editing JXTreeTable's Leaf. After i edit a leaf of JXTreeTable I found what I updated is lost, How can I persist all changes I made to these edited leaves.
In JTree this can be done using "tree.setInvokesStopCellEditing(true);"
I tried googling but could not find how to implement the same in JXTreeTable. Please help me to get out of this.
thnx in advance.
Regards
swaroop

kschaefe
Offline
Joined: 2006-06-08
Points: 0

Can you please supply a small, runnable demo?

Karl

ramswaroop123
Offline
Joined: 2011-11-29
Points: 0

// In the below example if i click a leaf node for two - three times it allows me to edit BUT as soon as i select some other node the changes i made are getting lost.
//please help me to know how to handle this problem
package ru.kc.util.swingx;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.geom.Point2D;
import java.io.IOException;
import javax.swing.*;
import org.jdesktop.swingx.JXTable;
import org.jdesktop.swingx.JXTreeTable;
import org.jdesktop.swingx.decorator.*;
import org.jdesktop.swingx.decorator.HighlightPredicate.ColumnHighlightPredicate;
import org.jdesktop.swingx.painter.CheckerboardPainter;
import org.jdesktop.swingx.renderer.DefaultTreeRenderer;
import org.jdesktop.swingx.renderer.IconValue;
import org.jdesktop.swingx.renderer.StringValue;
import org.jdesktop.swingx.treetable.*;
public class JXTreeTableTest extends JFrame {
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
//Do nothing
}
JXTreeTable treeTable = new JXTreeTable();
treeTable.setTreeTableModel(new RandomTextTreeTableModel(5));
treeTable.setColumnSelectionAllowed(true);
treeTable.setRowSelectionAllowed(true);
treeTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
treeTable.setDragEnabled(true);
treeTable.setEditable(true);
treeTable.setTreeCellRenderer(new DefaultTreeRenderer());
treeTable.setTransferHandler(new TreeTableTransferHandler());
treeTable.setSelectionBackground(new Color(176, 197, 227));
treeTable.setSelectionForeground(new Color(0, 0, 128));
treeTable.setForeground(new Color(0, 0, 128));
treeTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
// Commented out. below changes highlight behavior
//treeTable.setDropMode(DropMode.INSERT);
//treeTable.setDropMode(DropMode.ON_OR_INSERT_ROWS);
// Add multiple highlighters
BorderHighlighter border = new BorderHighlighter(new ColumnHighlightPredicate(1, 2),
BorderFactory.createEmptyBorder(0, 6, 0, 6), false, false);
treeTable.addHighlighter(border);
// Add a silly drop painter
GradientPaint gp = new GradientPaint(
new Point2D.Double(0, 0),
Color.BLACK,
new Point2D.Double(0, 4),
Color.GRAY);
CheckerboardPainter p = new CheckerboardPainter();
p.setDarkPaint(gp);
p.setLightPaint(Color.WHITE);
p.setSquareSize(4);
PainterHighlighter pp = new PainterHighlighter(new DropHighlightPredicate(), p);
treeTable.addHighlighter(pp);
treeTable.addHighlighter(HighlighterFactory.createAlternateStriping());
JFrame frame = new JFrame("Testing");
frame.setLayout(new BorderLayout());
frame.getContentPane().add(new JScrollPane(treeTable), BorderLayout.CENTER);
frame.setSize(400, 300);
frame.setVisible(true);
frame.setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
}
});
}
}
class DropHighlightPredicate implements HighlightPredicate {
public boolean isHighlighted(Component renderer, ComponentAdapter adapter) {
Object o = adapter.getComponent();
int r = adapter.row;
if (o instanceof JTable) {
JTable.DropLocation dl = ((JTable) o).getDropLocation();
if (dl == null) {
return false;
}
if (dl.getRow() != r) {
System.out.println("Not drop row" + r + " d:" + dl.getRow());
return false;
}
System.out.println("Drop pred:" + dl.getRow());
return true;
}
return false;
}
}
class TreeTableTransferHandler extends TransferHandler {
/**
* Perform the actual data import.
*/
@Override
public boolean importData(TransferHandler.TransferSupport info) {
String data = null;
//If we can't handle the import, bail now.
if (!canImport(info)) {
return false;
}
JXTreeTable treeTable = (JXTreeTable) info.getComponent();
//Fetch the data -- bail if this fails
try {
data = (String) info.getTransferable().getTransferData(DataFlavor.stringFlavor);
} catch (UnsupportedFlavorException ufe) {
System.out.println("importData: unsupported data flavor");
return false;
} catch (IOException ioe) {
System.out.println("importData: I/O exception");
return false;
}
if (info.isDrop()) { //This is a drop
JXTreeTable.DropLocation dl = (JXTreeTable.DropLocation) info.getDropLocation();
int row = dl.getRow();
int col = dl.getColumn();
//int index = dl.getIndex();
if (dl.isInsertRow()) {
//model.add(index, data);
System.out.println("Insert at:" + row + ":" + data);
return true;
} else {
System.out.println("Set at:" + row + ":" + data);
return true;
}
} else { //This is a paste
int row = treeTable.getSelectedRow();
int col = treeTable.getSelectedColumn();
System.out.println("Paste at:" + row + ":" + data);
return true;
}
}
/**
* Bundle up the data for export.
*/
protected Transferable createTransferable(JComponent c) {
JXTreeTable treeTable = (JXTreeTable) c;
int row = treeTable.getSelectedRow();
int col = treeTable.getSelectedColumn();
String value = treeTable.getValueAt(row, col).toString();
System.out.println("create at:" + row + ":" + value);
return new StringSelection(value);
}
/**
* The treeTable handles copy actions.
*/
public int getSourceActions(JComponent c) {
return COPY;
}
/**
* When the export is complete, remove the old treeTable entry if the
* action was a move.
*/
protected void exportDone(JComponent c, Transferable data, int action) {
if (action != MOVE) {
return;
}
JXTreeTable treeTable = (JXTreeTable) c;
//int index = treeTable.getSelectedIndex();
int row = treeTable.getSelectedRow();
int col = treeTable.getSelectedColumn();
System.out.println("Export done at:" + row);
//model.remove(index);
}
/**
*
*/
public boolean canImport(TransferHandler.TransferSupport support) {
JXTreeTable.DropLocation dl = (JXTreeTable.DropLocation) support.getDropLocation();
int row = dl.getRow();
int col = dl.getColumn();
support.setShowDropLocation(false);
if (row < 0) {
return false; // Only allow drop on column 0
}
if (col != 0) {
return false;
}
// we accept Strings
if (!support.isDataFlavorSupported(DataFlavor.stringFlavor)) {
return false;
}
support.setShowDropLocation(true);
return true;
}
}
class RandomTextTreeTableModel extends DefaultTreeTableModel {
DefaultMutableTreeTableNode Root = null;
RandomTextTreeTableModel(int i) {
Root = new DefaultMutableTreeTableNode();
this.setRoot(Root);
for (int x = 0; x < 5; x++) {
DefaultMutableTreeTableNode node = new DefaultMutableTreeTableNode("HI");
Root.add(node);
for (int j = 0; j < i; j++) {
node.add(new DefaultMutableTreeTableNode(j));
}
}
}
@Override
public int getColumnCount() {
return 2;
}
@Override
public Object getValueAt(Object arg0, int arg1) {
if (arg1 == 1) {
return new String("hi " + arg0.toString());
}
return arg0;
}

kleopatra
Offline
Joined: 2003-06-11
Points: 0

> Problem is a missing isExpandedState() in core JTree:
> isExpanded is not a complete overlap, if I remember
> correctly.

hmm ... seems to do the trick, though. Probably will apply tomorrow and then wait for complaints

Thanks
Jeanette

uvoigt
Offline
Joined: 2006-01-26
Points: 0

I tried the weekly build from 28th March and it seems to work.

Thanks!
Uli

kleopatra
Offline
Joined: 2003-06-11
Points: 0

okay - fixed as of build #2372: your example behaves the same way as a plain table. Please let me know how it behaves in your real context. Crossing fingers :-)

Thanks
Jeanette

uvoigt
Offline
Joined: 2006-01-26
Points: 0

Great! I will let you know with the next weekly build.

kleopatra
Offline
Joined: 2003-06-11
Points: 0

zu früh gefreut ;-)

Had to revert (see the other post) and re-open the issue. An intermediate workaround for your problem is to set the expandsSelectedPaths property to false.

Sorry
Jeanette

kleopatra
Offline
Joined: 2003-06-11
Points: 0

ehem ... yeah, we already have seen this - but not yet fixed (and no idea what's wrong, unfortunately):

https://swingx.dev.java.net/issues/show_bug.cgi?id=1126

so no need to file another, will add a reference to this thread

Thanks for the alert!
Jeanette

kleopatra
Offline
Joined: 2003-06-11
Points: 0

looks like a bug - which I thought we had fixed a while ago: at that time it manifested with a checkbox, afair :-( Could you please file an issue in the swingx issue tracker with a reference to this thread so we don't loose the example?

Thanks
Jeanette

uvoigt
Offline
Joined: 2006-01-26
Points: 0
kleopatra
Offline
Joined: 2003-06-11
Points: 0

mid-air collision :-)

Will close the new as duplicate. Sorry for having been late - neede my coffee first

Thanks again
Jeanette