Autar
Autar

Reputation: 1589

Swing JList with custom CellRenderer disappear after adding new element

I am coding a JList with checkBoxes using a custom CellRenderer, and a context menu to select/unselect, add or remove an element of the list.

Everything works fine, I can select items, open the context menu and remove/add elements through it. My problem is that when I add an element and there is then more elements in the list than at the beginning, the list disappear and I get a blank panel. This is what happen when adding a new element to the list whithout removing one before, for example.

In order to pinpoint the problem I have been trying to get the code to the minimum but I still don't understand why it doesn't work properly.

What I want to know is why my list goes blank and how can I prevent it from doing so.

Of course, any side-comment or advice is welcome :)

Thanks.


Here is the complete code if you want to give it a try (left-click to select items, right-click to open the context menu) :

CheckBoxList.java

package misc;

import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import java.awt.event.*;

public class CheckBoxList extends JList {

    private int selection = -1;

    protected static final Border noFocusBorder = new EmptyBorder(1, 1, 1, 1);

    public CheckBoxList(Model m) {
        this();
        this.setModel(m);
    }

    public CheckBoxList() {
        this.setCellRenderer(new CheckboxCellRenderer());
        this.addMouseListener(new MouseAdapter() {

            @Override
            public void mousePressed(MouseEvent e) {
                int index = locationToIndex(e.getPoint());
                selection = index;
                CheckBoxList cbl = (CheckBoxList) e.getSource();
                cbl.setSelectedIndex(index);
                if(index != -1) {
                    if(e.getButton() == MouseEvent.BUTTON1) {
                        Data v = (Data) getModel().getElementAt(index);
                        v.setSelected(!v.isSelected());
                        JCheckBox checkbox = new JCheckBox(v.getS(), v.isSelected());
                        checkbox.setSelected(!checkbox.isSelected());
                        repaint();
                    } else if(e.getButton() == MouseEvent.BUTTON3) {
                        ContextMenu pum = new ContextMenu(cbl);
                        pum.show(e.getComponent(), e.getX(), e.getY());
                    }
                }
            }
        });
        this.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    }

    protected class CheckboxCellRenderer implements ListCellRenderer {

        @Override
        public Component getListCellRendererComponent(JList list, Object value, int index,
                boolean isSelected, boolean cellHasFocus) {
            Data v = (Data) value;
            JCheckBox checkbox = new JCheckBox(v.getS(), v.isSelected());
            checkbox.setBackground(isSelected ? getSelectionBackground() : getBackground());
            checkbox.setBorderPainted(true);
            checkbox.setBorder(isSelected ? UIManager.getBorder("List.focusCellHighlightBorder")
                    : noFocusBorder);
            return checkbox;
        }
    }

    public int getSelection() {
        return this.selection;
    }

    public class ContextMenu extends JPopupMenu {

        private JMenuItem deleteItem;
        private JMenuItem addItem;
        private CheckBoxList cbl;

        public ContextMenu(CheckBoxList cbl) {
            this.cbl = cbl;
            this.deleteItem = new JMenuItem("Delete");
            this.deleteItem.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent e) {
                    JMenuItem jmi = (JMenuItem) e.getSource();
                    ContextMenu cm = (ContextMenu) jmi.getParent();
                    Model m = (Model) cm.cbl.getModel();
                    m.remove((Data) m.getElementAt(cm.cbl.getSelection()));
                    cm.cbl.repaint();
                }
            });
            this.add(deleteItem);

            this.addItem = new JMenuItem("Add new");
            this.addItem.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent e) {
                    JMenuItem jmi = (JMenuItem) e.getSource();
                    ContextMenu cm = (ContextMenu) jmi.getParent();
                    ((Model) cm.cbl.getModel()).add(new Data("Added :)"));
                    cm.cbl.repaint();
                }
            });
            this.add(addItem);
        }
    }
}

Model.java

package misc;

import java.util.ArrayList;
import java.util.List;

import javax.swing.ListModel;
import javax.swing.event.ListDataListener;

public class Model implements ListModel {

    private List<Data> data = new ArrayList<Data>();

    @Override
    public void addListDataListener(ListDataListener l) {}

    @Override
    public Object getElementAt(int index) {
        return data.get(index);
    }

    @Override
    public int getSize() {
        return data.size();
    }

    @Override
    public void removeListDataListener(ListDataListener l) {}

    public void add(Data string) {
        this.data.add(string);
    }

    public void remove(Data d) {
        data.remove(d);
    }
}

Data.java

package misc;

public class Data {

    private String s;
    private boolean selected = false;

    public Data(String s) {
        super();
        this.s = s;
    }

    public String getS() {
        return this.s;
    }

    public void setS(String s) {
        this.s = s;
    }

    public boolean isSelected() {
        return this.selected;
    }

    public void setSelected(boolean selected) {
        this.selected = selected;
    }

    @Override
    public String toString() {
        return "Data [s=" + this.s + ", isSelected=" + this.selected + "]";
    }

}

Main.java

package misc;

import java.awt.Dimension;

import javax.swing.JFrame;
import javax.swing.JScrollPane;

public class Main {

    public static void main(String[] args) {
        JFrame f = new JFrame();
        f.setPreferredSize(new Dimension(500,200));
        f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        Model m = new Model();
        m.add(new Data("test1"));
        m.add(new Data("test2"));
        CheckBoxList cbl = new CheckBoxList(m);
        JScrollPane jsp = new JScrollPane(cbl);
        f.add(jsp);
        f.pack();
        f.setVisible(true);
    }
}

Upvotes: 1

Views: 1805

Answers (2)

Guillaume Polet
Guillaume Polet

Reputation: 47608

The problem comes from your implementation of your ListModel where you don't implement addListDataListener and removeListDataListener, nor do you fire appropriate model events to notify them.

Simply put the following in your ListModel and it should work a lot better:

    private List<ListDataListener> listeners = new ArrayList<ListDataListener>();

    @Override
    public void addListDataListener(ListDataListener l) {
        listeners.add(l);
    }

    @Override
    public void removeListDataListener(ListDataListener l) {
        listeners.remove(l);
    }

    public void add(Data string) {
        this.data.add(string);
        ListDataEvent addition = new ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, data.size() - 1, data.size() - 1);
        for (ListDataListener l : listeners) {
            l.intervalAdded(addition);
        }
    }

    public void remove(Data d2) {
        data.remove(d2);
        ListDataEvent removal = new ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, data.size(), data.size());
        for (ListDataListener l : listeners) {
            l.intervalRemoved(removal);
        }
    }

Possibly consider extending or usind directly DefaultListModel which handles for you all this.

Upvotes: 4

Gene
Gene

Reputation: 46960

I think you must implement the data listeners in your model. Listeners can be added by Swing, and they expect to be notified when something has changed.

Upvotes: 2

Related Questions