Search code examples
javaswingmodel-view-controllerlistenerpropertychangelistener

ListSelectionListener does not fire events when calling setSelected methods


First question here, hopefully I do this correctly.

Below is a minimal example of my problem that I quickly whipped up representative of my project. I have created a custom renderer for a JList containing some objects (in the example I've used Strings for illustrative purposes). My issue is that, as far as I can tell, if I add a ListSelectionListener to the list, the family of setSelected...() methods do not fire events that trigger the conditional if(e.getValueIsAdjusting()).

In the example below, the problem is immediately obvious upon starting the program: even though list.setSelectedIndex(2); is called after assigning the JLabel's selected text as "none", it does not change until you, the user, click the list items. In fact, it must be a different item to the one currently selected.

I would like this functionality so that the flow of my program will be that, after a user adds/removes items from the list, the "view" is immediately updated once the selected list item is changed.

Have I done something wrong, or am I approaching this incorrectly?

Thank you. :)

import java.awt.BorderLayout;
import java.beans.*;
import javax.swing.*;

public class Example implements PropertyChangeListener {
    String ITEM_SELECTED = "Item Selected";
    String DELETE_SELECTED = "Delete Selected";
    PropertyChangeSupport pcs = new PropertyChangeSupport(this);
    JLabel selected;

    JList<String> list;
    DefaultListModel<String> model;

    public static void main(String[] args) {
        new Example();
    }

    public Example() {
        JFrame frame = new JFrame();
        JPanel mainPanel = new JPanel(new BorderLayout());

        model = new DefaultListModel<>();
        model.addElement("Entry 1");
        model.addElement("Entry 2");
        model.addElement("Entry 3");
        model.addElement("Entry 4");
        model.addElement("Entry 5");

        list = new JList<>(model);

        selected = new JLabel("Currently selected: none");
        list.setSelectedIndex(2);

        list.addListSelectionListener(e -> {
                if(e.getValueIsAdjusting()) {
                    pcs.firePropertyChange(ITEM_SELECTED, null, null);
                }
            }
        );

        JButton removeItem = new JButton("Remove item");

        removeItem.addActionListener(e -> {
                pcs.firePropertyChange(DELETE_SELECTED, null, null);
            }
        );

        mainPanel.add(new JScrollPane(list), BorderLayout.WEST);
        mainPanel.add(removeItem, BorderLayout.NORTH);

        mainPanel.add(selected, BorderLayout.CENTER);       

        pcs.addPropertyChangeListener(this);

        frame.add(mainPanel);
        frame.setSize(300, 400);
        frame.setLocationRelativeTo(null);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        String event = (String) evt.getPropertyName();

        if (event.equals(ITEM_SELECTED)) {
            selected.setText("Currently selected: " + list.getSelectedValue());
        }

        if (event.equals(DELETE_SELECTED)) {
            int selectedIndex = list.getSelectedIndex();
            model.removeElement(list.getSelectedValue());

            if (selectedIndex > 0)
                list.setSelectedIndex(selectedIndex-1);
            else if (selectedIndex == 0)
                list.setSelectedIndex(selectedIndex);
        }
    }
}

Solution

  • The e.getValueIsAdjusting() method checks if the value is still being adjusted. In your case it looks like the event fired by list.setSelectedIndex(2) would have e.getValueIsAdjusting() return false.

    You can probably fix this by doing the opposite check : !e.getValueIsAdjusting().

    This will then check that any change responded to is the final one for that chain of events.