tags:

views:

38

answers:

3

I have a list of Map.Entry<String,Integer>s that I am looping through, and for each one, making a JLabel/JSpinner representing that particular entry.

How can I make it so that when the ChangeListener fires on the JSpinner, it updates that entry to reflect the new value?

My code looks like

for (Map.Entry<String,Integer> entry : graph.getData()) {
    SpinnerNumberModel model = new SpinnerNumberModel(
      entry.getValue(),(Integer)0,(Integer)100,(Integer)5);
    JSpinner spinner = new JSpinner(model);

    JLabel label = new JLabel(entry.getKey());

    spinner.addChangeListener(new ChangeListener() {
        public void stateChanged(ChangeEvent e) {
            entry.setValue((Integer)((JSpinner)e.getSource()).getValue());
        }
    });

    // more stuff
}

However, this does not work, because entry either needs to be final or object-level, and it is neither.

Is there an easier way, like C#'s dataSource that directly binds the object to the spinner? Or how can I make this work?

+1  A: 

Particularly if the number of Map entries is large, consider using a JTable, with your Map as the core of the data model and a custom render/editor extending JSpinner. There's a related example here.

trashgod
+2  A: 

There are several options if you want to stick with individual JLabel and JSpinner. Use @trashgod's answer if the map could get large.

  1. WRONG Per @Suresh Kumar's comment: final Map.Entry<String,Integer> entry
  2. WRONG Add spinner.putClientProperty("MyEntryKey", entry), then in your ChangeListener get the entry with spinner.getClientProperty.
  3. Add spinner.putClientProperty("MyEntryKey", entry.getKey()), then in your ChangeListener graph.put(spinner.getClientProperty(), ...).
  4. From @Michael's answer (not sure if this is ethical): Add final String key = entry.getKey(), then in your ChangeListener graph.put(key, ...).
  5. Let your enclosing class implement ChangeListener to avoid one ChangeListener per JSpinner:

    public void stateChanged(ChangeEvent e) { JSpinner spinner = (JSpinner)e.getSource(); graph.put((String)spinner.getClientProperty("MyEntryKey"), (Integer)spinner.getValue()); }

BTW graph.getData() looks a bit odd. If graph implements Map, you should use graph.entrySet().

Geoffrey Zheng
`Map.Entry` refs are invalid after the iterator is complete, so only (original) solution #3 will work reliably.
Michael Brewer-Davis
Oops, sorry for such n00b error. I guess I should really get `-2+1=-1` votes.
Geoffrey Zheng
Well, no graph isn't a `Map`, but it does contain one. It's also not a Graph like with edges and vertices. It is a VERY simple way of representing data to be displayed in a pie/bar/line/etc graph.
Austin Hyde
+1  A: 

@Suresh is right about using final in the for loop, but since Map.Entry values aren't viable outside of the iterator, this would lead to trouble. Another possibility for this one-way binding would be

for (Map.Entry<String, Integer> entry : graph.getData()) {
    ...
    final String key = entry.getKey();

    spinner.addChangeListener(new ChangeListener() {
        public void stateChanged(ChangeEvent e) {
            graph.put(key, (Integer)((JSpinner)e.getSource()).getValue());
        }
    });

For a more complete data binding, you may want a more complex object to bind against. See the jgoodies binding API's ValueModel and SpinnerAdapterFactory, for example.

Michael Brewer-Davis