Path: csiph.com!usenet.pasdenom.info!weretis.net!feeder1.news.weretis.net!news.swapon.de!fu-berlin.de!uni-berlin.de!individual.net!not-for-mail From: blmblm@myrealbox.com Newsgroups: comp.lang.java.programmer Subject: Re: problems with editable JList Date: 18 Feb 2014 12:39:38 GMT Organization: None Lines: 296 Message-ID: References: X-Trace: individual.net jrVto07A8GNbcYBk+se4LwtNlNxhiIDQlMHBlaU0Bw1nRz8hVp X-Orig-Path: not-for-mail Cancel-Lock: sha1:Vck4sVIUgVD1xk8xOBHdWQuB65k= X-Newsreader: trn 4.0-test76 (Apr 2, 2001) Xref: csiph.com comp.lang.java.programmer:27544 In article , markspace wrote: > On 2/17/2014 12:02 PM, blmblm@myrealbox.com wrote: > > > markspace wrote: > >> > >> I think your event listeners are tripping you up. They're probably > > > > > I'd have said not -- based on output of the even-more-debug-prints > > version I'm reasonably confident that I'm not getting that kind of > > recursion/loop, and anyway I only modify the selection in code to > > process add/remove events, rather than in .... > > > I didn't read your code carefully to find the bug -- there was quite a > lot of it. Yes. Sorry about that. I was trying for an SSCCE but the "short" part eluded me. > Rather I just focused on your idea that there might be a bug > in JList or DefaultModelListener. So I purposely started over from > scratch, since that should duplicate the bug if there was one. Yeah, maybe .... Well, or at least it would show whether what I had in mind was possible. My thinking is that the library code shouldn't be setting a selection index outside the bounds of the underlying model, but maybe it doesn't count as a bug if it only happens when the user (me) does something wrongheaded. > When it worked, I basically stopped there and posted the code I had. No > sense in belaboring something that's working. :-) > Anyway, glad you got yourself straightened out. Good luck. Yes. Thanks again. Like I said, the big difference between what you did (which works) and what I was doing (which doesn't) is that you put the code to set the selection in with the code to change the data model, which -- well, the more I think about it the more I wonder why I didn't do it that way myself. For the record-or-whatever, my revised (and working) code is below. I've folded this change back into my "real" code [*] (which is itself a prototype for something else), and now it works too. Whew! [*] Which is not actually Java code but Scala. But there apparently isn't a Scala Usenet group (only a Google group), and anyway it seemed like it might be useful to know whether the problem was with Scala's wrappers around Swing or could be reproduced in straight Java, which in this case it could. ==== start of code ==== /* Attempt at an editable JList, supporting allow adding, removing, and changing elements. */ 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); if (model.getSize() == 0) { list.clearSelection(); } else { int newSelect = Math.max(0, si-1); list.setSelectedIndex(newSelect); list.ensureIndexIsVisible(newSelect); } } 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); int newSelect = model.getSize()-1; list.setSelectedIndex(newSelect); list.ensureIndexIsVisible(newSelect); } }; 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); 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 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.