Path: csiph.com!usenet.pasdenom.info!news.albasani.net!fu-berlin.de!uni-berlin.de!individual.net!not-for-mail From: blmblm@myrealbox.com Newsgroups: comp.lang.java.programmer Subject: problems with editable JList Date: 17 Feb 2014 15:53:16 GMT Organization: None Lines: 365 Message-ID: X-Trace: individual.net WDvlnaNVXVBH4Mhkx5921wOtjxmQZVmzd07DEGmFob0ZOO/v6M X-Orig-Path: not-for-mail Cancel-Lock: sha1:9QlRpQ0F0qFGlLN8ysa28iEDuyY= X-Newsreader: trn 4.0-test76 (Apr 2, 2001) Xref: csiph.com comp.lang.java.programmer:27533 Maybe one of the experts here can help .... Apologies in advance for the length of this post; I've tried to make it as short as I could, but oh well. I'm trying to work up a prototype for a JList that supports some simple editing operations: add a new element, replace an existing element, remove a selected element, and do some simple reordering. My code is working pretty well, *except* that when I try to set the selection after an "add" or "remove", it doesn't quite work the way I want. What I want: After adding a new element, the new element is selected. After removing an element, the preceding element is selected, unless the removed element was the first one, in which case I want the first one to be selected, or the only one, in which case I want nothing selected. How I'm trying to accomplish this: In my extension of JList, I attach a ListDataListener to the list data model, and in the listener's methods that deal with addition and removal events I use setSelectedIndex() or clearSelection() to change the selection to what I want. And it seems to work, based on what's reported by debug prints -- *EXCEPT* that something [*] subsequently changes the selection in a way that I don't want: After an "add", I end up with a selected index of getModel().getSize(), which of course is not valid. After a "remove" of the first element, I end up with nothing selected, even if there are elements remaining. [*] Based on attempts to find out what's going on by adding a lot of debug-print code and poring over source code for various javax.swing classes, I *think* something in the ListDataListener in plaf.basic.BasicListUI is responsible. I've stripped down the prototype to what I think is just about the smallest program that demonstrates the problem (well, okay, plus some debug-print code), which appears below. Other than not being very short, it's an SSCCE. Am I going about this wrong? Have I tripped over a bug? Initial development was done with Java 1.6.something, but the code (mis)behaves the same way with 1.7.something. Help appreciated!! the problem's not a show-stopper for my use case, but it *is* annoying, and puzzling .... /* Attempt at an editable JList, supporting allow adding, removing, and changing elements. Works as desired except for (some) attempts to modify programmatically what element is selected: "add" should result in new item being selected (but does not). "remove" should result in preceding element being selected if there is one (and that works), or element 0 being selected if the removed element was the one at index 0 (and that does not work). */ import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; public class TryListMain extends JFrame { // ---- variables ---- private static boolean DEBUG = true; private DefaultListModel model; private TryList list; private static int counter = 0; // ---- constructor ---- public TryListMain() { super(); model = new DefaultListModel(); list = new TryList(model); setLayout(new BorderLayout()); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); add(new ButtonPanel(), BorderLayout.NORTH); add(list, BorderLayout.CENTER); } // ---- methods ---- public TryList getList() { return list; } public DefaultListModel getModel() { return model; } public static void debugPrint(String msg) { if (DEBUG) { if (msg.length() > 0) { System.out.println("DBG "+msg); } else { System.out.println(); } } } public static void debugPrint() { debugPrint(""); } private String generateItem() { ++counter; return "Element "+counter; } // ---- main ---- public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { UIManager.put("Button.defaultButtonFollowsFocus", true); TryListMain theFrame = new TryListMain(); System.out.println("data listeners:"); for (ListDataListener ll : theFrame.getModel().getListDataListeners()) { System.out.println(ll.getClass().getName()); } System.out.println(); System.out.println("list selection listeners:"); for (ListSelectionListener ll : theFrame.getList().getListSelectionListeners()) { System.out.println(ll.getClass().getName()); } System.out.println(); theFrame.setSize(new Dimension(400,200)); theFrame.setVisible(true); } }); } // ---- classes ---- private class ButtonPanel extends JPanel { private Action removeAction; private Action replaceAction; private Action newAction; private Action showAction; private void setActionsEnabled() { int si = list.selectedIndex(); removeAction.setEnabled(si >= 0); replaceAction.setEnabled(si >= 0); } public ButtonPanel() { super(new FlowLayout()); removeAction = new AbstractAction("Remove") { @Override public void actionPerformed(ActionEvent e) { int si = list.selectedIndex(); TryListMain.debugPrint(); TryListMain.debugPrint("== remove item at "+si); if (si >= 0) { model.remove(si); } else { // FIXME should never happen System.err.println("no selected item to remove"); } } }; replaceAction = new AbstractAction("Replace") { @Override public void actionPerformed(ActionEvent e) { int si = list.selectedIndex(); if (si >= 0) { String item = generateItem(); TryListMain.debugPrint(); TryListMain.debugPrint("== replace item at "+si); model.set(si, item); } else { // FIXME should never happen System.err.println("no selected item to replace"); } } }; newAction = new AbstractAction("New") { @Override public void actionPerformed(ActionEvent e) { String item = generateItem(); TryListMain.debugPrint(); TryListMain.debugPrint("== add new item"); model.addElement(item); } }; showAction = new AbstractAction("Show selected") { @Override public void actionPerformed(ActionEvent e) { int si = list.selectedIndex(); if (si < 0) { System.out.println("No selected value"); } else { System.out.println( String.format("Selected value (%d of %d) '%s'", si+1, model.getSize(), model.get(si))); } } }; list.addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { setActionsEnabled(); } }); setActionsEnabled(); add(new JButton(removeAction)); add(new JButton(replaceAction)); add(new JButton(newAction)); add(new JButton(showAction)); } } } class TryList extends JList { // ---- constructor ---- public TryList(DefaultListModel lModel) { super(lModel); getSelectionModel().setSelectionMode( ListSelectionModel.SINGLE_SELECTION); getModel().addListDataListener(new ListDataListener() { @Override public void intervalAdded(ListDataEvent e) { printEvent("add", e); setSelectedIndex(e.getIndex1()); ensureIndexIsVisible(e.getIndex1()); printNewSelection("add"); /* HERE things seem okay -- desired element selected -- but then something changes the selection to getModel().getSize(), which of course(?) is not valid */ TryListMain.debugPrint(); } @Override public void intervalRemoved(ListDataEvent e) { printEvent("remove", e); if (getModel().getSize() == 0) { clearSelection(); } else { int newSelect = Math.max(0, e.getIndex1()-1); setSelectedIndex(newSelect); ensureIndexIsVisible(newSelect); } printNewSelection("remove"); /* HERE things seem okay -- desired element selected -- but then if the removed element was the first one something changes the selection from (0) to () */ TryListMain.debugPrint(); } @Override public void contentsChanged(ListDataEvent e) { printEvent("change", e); setSelectedIndex(e.getIndex1()); ensureIndexIsVisible(e.getIndex1()); printNewSelection("change"); TryListMain.debugPrint(); } }); addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { TryListMain.debugPrint(String.format( "selection changed to %s, source %s", selectedIndicesToString(), e.getSource().getClass().getName())); } }); } // ---- methods ---- // return -1 if nothing selected, or out of range public int selectedIndex() { int[] si = getSelectedIndices(); if ((si.length == 1) && (si[0] < getModel().getSize())) { return si[0]; } else { return -1; } } private void printEvent(String name, ListDataEvent e) { TryListMain.debugPrint(); TryListMain.debugPrint( String.format("%s (%s, %d, %d), %d items (selected %s)", name, e.getSource().getClass().getName(), e.getIndex0(), e.getIndex1(), getModel().getSize(), selectedIndicesToString())); } private void printNewSelection(String name) { TryListMain.debugPrint( String.format("after %s, selection %s", name, selectedIndicesToString())); TryListMain.debugPrint(); } private String selectedIndicesToString() { StringBuffer sb = new StringBuffer(); for (int i : getSelectedIndices()) { if (sb.length() > 0) sb.append(' '); sb.append(i); } return "("+sb.toString()+")"; } } -- B. L. Massingill ObDisclaimer: I don't speak for my employers; they return the favor.