I'm having a table row selection problem with JXTable.
If I run the following program, I get two frames that are viewing the same EventList<Item>
. One has a JTable and one has a JXTable.
Every 1000 milliseconds I update all the items with names that start with foo
(this happens in tweakList()
). This update seems to interfere with selection-by-mouse in the JXTable, but not in the JTable: if I click and drag downwards in the JXTable to select multiple rows, the selection anchor disappears when the update happens. (click and drag upwards works fine)
Why does it do this, and how do I fix this?
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.HashSet;
import java.util.Set;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.Timer;
import org.jdesktop.swingx.JXTable;
import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.SortedList;
import ca.odell.glazedlists.gui.TableFormat;
import ca.odell.glazedlists.swing.EventTableModel;
public class TableSelectionExample {
static public enum ItemKey {
NAME("name") {
@Override public String getStringFromItem(Item item) {
return item.getName();
}
},
NUMBER("#") {
@Override public String getStringFromItem(Item item) {
return Integer.toString(item.getNumber());
}
},
PARENT("parent") {
@Override public String getStringFromItem(Item item) {
Item p = item.getParent();
return (p == null) ? null : p.getName();
}
};
final private String name;
ItemKey(String name) { this.name = name; }
public String getName() { return this.name; }
abstract public String getStringFromItem(Item item);
static private ItemKey[] columns = { NAME, NUMBER, PARENT };
static public ItemKey[] getColumns() { return columns; }
}
static public class ItemTableFormat implements TableFormat<Item> {
@Override public int getColumnCount() {
return ItemKey.getColumns().length;
}
@Override public String getColumnName(int col) {
return ItemKey.getColumns()[col].getName(); }
@Override public Object getColumnValue(Item item, int col) {
return ItemKey.getColumns()[col].getStringFromItem(item);
}
}
static class Item {
final private String name;
private int number;
final private Item parent;
private Item(String name, int number, Item parent) {
this.name=name; this.number=number; this.parent=parent;
}
static public Item create(String name, int number, Item parent) {
return new Item(name, number, parent);
}
public String getName() { return this.name; }
public int getNumber() { return this.number; }
public void setNumber(int number) { this.number = number; }
public Item getParent() { return this.parent; }
}
static public void main(String[] args)
{
final EventList<Item> items = new BasicEventList<Item>();
Item x1,x2,x3;
x1 = Item.create("foo", 1, null);
x2 = Item.create("bar", 2, x1);
x3 = Item.create("baz", 3, x1);
items.add(x1);
items.add(x2);
items.add(x3);
for (int i = 0; i < 20; ++i)
{
items.add(Item.create(String.format("bar%02d",i), 100+i, x2));
items.add(Item.create(String.format("baz%02d",i), 200+i, x3));
if (isprime(i))
items.add(Item.create(
String.format("foo%02d", i), 300+i, x1));
}
EventList<Item> sortedItems = new SortedList<Item>(items, null);
doit(sortedItems, new JTable(), "JTable selection example");
doit(sortedItems, new JXTable(), "JXTable selection example");
Timer timer = new Timer(1000, new ActionListener() {
@Override public void actionPerformed(ActionEvent e) {
tweakList(items);
}
});
timer.start();
}
private static void doit(EventList<Item> displayItems,
JTable table, String title) {
TableFormat<Item> tf = new ItemTableFormat();
EventTableModel<Item> etm =
new EventTableModel<Item>(displayItems, tf);
table.setModel(etm);
if (table instanceof JXTable)
{
((JXTable)table).setColumnControlVisible(true);
}
JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
panel.add(new JScrollPane(table), BorderLayout.CENTER);
JFrame frame = new JFrame(title);
frame.getContentPane().add(panel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
frame.pack();
}
private static boolean isprime(int i) {
// works for i < 400
if (i < 2)
return false;
else if (i > 2 && (i % 2) == 0)
return false;
else if (i > 3 && (i % 3) == 0)
return false;
else if (i > 5 && (i % 5) == 0)
return false;
else if (i > 7 && (i % 7) == 0)
return false;
else if (i > 11 && (i % 11) == 0)
return false;
else if (i > 13 && (i % 13) == 0)
return false;
else if (i > 17 && (i % 17) == 0)
return false;
else if (i > 19 && (i % 19) == 0)
return false;
else
return true;
}
protected static void tweakList(EventList<Item> items) {
int L = items.size();
Set<Integer> modifiedItems = new HashSet<Integer>();
for (int i = 0; i < L; ++i)
{
Item item = items.get(i);
if (item.getName().startsWith("foo"))
{
item.setNumber(item.getNumber()+1000);
modifiedItems.add(i);
}
}
refreshModifiedItems(items, modifiedItems);
}
private static void refreshModifiedItems(EventList<Item> items,
Set<Integer> modifiedItems) {
for (int i : modifiedItems)
{
items.set(i, items.get(i));
}
}
}
Never mind, this is SwingX Issue 370 and it got fixed in SwingX 1.6.
(I was running SwingX 1.0; I downloaded SwingX 1.6.2 and the problem went away)