Search code examples
javaswingjlistlistcellrenderer

Java ListCellRenderer and JList: handle selection


I got stuck with a project i have to do at school.

i was trying to create a custom JList for a "simple" UI: the use case should be displaying a list of orders and the user should be able to select one and set if read/done by pressing two buttons (read and done) which acts also as "unsetters" (i'm using 2 boolean in class Order which keep going trough t,f,t,f...). Once pressed Done, the selected row should turn green (and staying permanently on that color) until "Done" button is again pressed on that row (=unset). Same thing of button "Read" which should turn selected row permanently yellow relating to "is_read" flag in class Order.

After reading some related posts, I wrote this custom ListCellRenderer:

public class CookListCellRender extends JLabel implements ListCellRenderer<Order>{

public CookListCellRender(){
    setOpaque(true);
}

@Override
public Component getListCellRendererComponent(JList<? extends Order> list, Order value, int index, boolean isSelected, boolean cellHasFocus) {

    setLayout(new FlowLayout());
    setText(value.getNameEl());
    list.setVisibleRowCount(30);
    list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    setFont(new Font("Arial",Font.ITALIC,17));

    if (isSelected) {
        setBackground(Color.BLUE);
        setForeground(Color.BLUE);
    } else {
        setBackground(getBackground());
        setForeground(getForeground());
    }

    if(value.getOrderRead()){
        setBackground(Color.YELLOW);
    }else{
        setBackground(Color.WHITE);
    }

    if(value.getOrderDone()){
        setBackground(Color.GREEN);
    }else{
        setBackground(Color.WHITE);
    }
    return this;
}

}

My problem is, once running my test file, everything pops up properly but when I select a row and I press one of those 2 buttons nothing happens until I select a new row: then keeps turning green or yellow (depending by the button I pressed) the color of the row I'm selecting.

I'm pretty sure this is a problem related to my custom cell renderer, also because I'm still learning how to use it and I started getting pretty confused.

Here is the rest of my relevant code related to the UI, maybe it's useful. Buttons and their listeners are in a separate class (a custom JPanel), but those are working fine, so...

Class InitGraphics (directly called by my test class):

public class InitGraphics {

Cook c;
JList list;

public InitGraphics(Cook cuoco){
    c=cuoco;
    initUi();
  }

private void initUi() {
    JFrame frame = new JFrame("Cucina");
    list =new JList(c.getOrdersCopy().toArray());
    list.setCellRenderer(new CookListCellRender());
    JScrollPane panel = new JScrollPane(list);
    panel.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
    panel.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
    ButtonPanel buttonPanel = new ButtonPanel(c.getOrdersCopy(),list);
    buttonPanel.setLayout(new FlowLayout());
    frame.add(buttonPanel,BorderLayout.NORTH);
    frame.add(panel,BorderLayout.CENTER);
    frame.setVisible(true);
    frame.setSize(600, 400);
    frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
  }
}

Solution

  • This is killing you:

        } else {
            setBackground(getBackground());
            setForeground(getForeground());
        }
    

    It doesn't change anything at all. I think that you want:

        } else {
            setBackground(null);
            setForeground(null);
        }
    

    Note that to do any work at all on this code, I had to create my own SSCCE:

    import java.awt.*;
    import javax.swing.*;
    
    public class InitGraphics {
        public static void main(String[] args) {
            SwingUtilities.invokeLater(() -> {
                final DefaultListModel<Order> orderModel = new DefaultListModel<>();
                orderModel.addElement(new Order("One"));
                orderModel.addElement(new Order("Two"));
                orderModel.addElement(new Order("Three"));
                orderModel.addElement(new Order("Four"));
                orderModel.addElement(new Order("Five"));
    
                JList<Order> orderList = new JList<>(orderModel);
                orderList.setCellRenderer(new CookListCellRender());
    
                JPanel panel = new JPanel();
                panel.add(new JScrollPane(orderList));
    
                JOptionPane.showMessageDialog(null, panel);
            });
        }
    }
    
    class CookListCellRender extends JLabel implements ListCellRenderer<Order> {
    
        public CookListCellRender() {
            setOpaque(true);
        }
    
        @Override
        public Component getListCellRendererComponent(JList<? extends Order> list, Order value, int index,
                boolean isSelected, boolean cellHasFocus) {
    
            setLayout(new FlowLayout());
            setText(value.getNameEl());
            list.setVisibleRowCount(30);
            list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
            setFont(new Font("Arial", Font.ITALIC, 17));
    
            if (isSelected) {
                setBackground(Color.BLUE);
                setForeground(Color.BLUE);
            } else {
                // TODO: fix!
                // setBackground(getBackground());
                // setForeground(getForeground());
                setBackground(null);
                setForeground(null);
            }
    
            if (value.getOrderRead()) {
                setBackground(Color.YELLOW);
            } else {
                setBackground(Color.WHITE);
            }
    
            if (value.getOrderDone()) {
                setBackground(Color.GREEN);
            } else {
                setBackground(Color.WHITE);
            }
            return this;
        }
    
    }
    
    class Order {
    
        private String nameE1;
        private boolean orderDone;
        private boolean orderRead;
    
        public Order(String nameE1) {
            this.nameE1 = nameE1;
        }
    
        public String getNameEl() {
            return nameE1;
        }
    
        public boolean getOrderDone() {
            return orderDone;
        }
    
        public boolean getOrderRead() {
            return orderRead;
        }
    
        public void setOrderDone(boolean orderDone) {
            this.orderDone = orderDone;
        }
    
        public void setOrderRead(boolean orderRead) {
            this.orderRead = orderRead;
        }
    
    }
    

    In the future, please do this work for us, since you're the one asking for help, and we're volunteers.

    Also this:

        list.setVisibleRowCount(30);
        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    

    Never do this sort of thing from within a renderer. This code should go where you create the JList, not in the renderer.

    Here's my latest SSCCE, one that includes clear/read/done buttons

    import java.awt.Color;
    import java.awt.Component;
    import java.awt.Font;
    import java.awt.event.*;
    import java.util.List;
    
    import javax.swing.*;
    
    public class InitGraphics {
        @SuppressWarnings("serial")
        public static void main(String[] args) {
            SwingUtilities.invokeLater(() -> {
                final DefaultListModel<Order> orderModel = new DefaultListModel<>();
                orderModel.addElement(new Order("One"));
                orderModel.addElement(new Order("Two"));
                orderModel.addElement(new Order("Three"));
                orderModel.addElement(new Order("Four"));
                orderModel.addElement(new Order("Five"));
    
                final JList<Order> orderList = new JList<>(orderModel);
                orderList.setCellRenderer(new CookListCellRender());
                orderList.setVisibleRowCount(5);
    
                JPanel panel = new JPanel();
                // panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS));
                panel.add(new JScrollPane(orderList));
    
                panel.add(new JButton(new AbstractAction("Read") {
    
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        List<Order> selectedOrders = orderList.getSelectedValuesList();
                        for (Order order : selectedOrders) {
                            order.setOrderRead(true);
                        }
                        orderList.repaint();
                    }
                }));
                panel.add(new JButton(new AbstractAction("Done") {
    
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        List<Order> selectedOrders = orderList.getSelectedValuesList();
                        for (Order order : selectedOrders) {
                            order.setOrderDone(true);
                        }
                        orderList.repaint();
                    }
                }));
                panel.add(new JButton(new AbstractAction("Clear") {
    
                    @Override
                    public void actionPerformed(ActionEvent e) {
    
                        for (int i = 0; i < orderModel.size(); i++) {
                            orderModel.getElementAt(i).setOrderDone(false);
                            orderModel.getElementAt(i).setOrderRead(false);
                        }
                        orderList.repaint();
                    }
                }));
    
    
                JFrame frame = new JFrame("Test List");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(panel);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            });
        }
    }
    
    @SuppressWarnings("serial")
    class CookListCellRender extends JLabel implements ListCellRenderer<Order> {
    
        public CookListCellRender() {
            setOpaque(true);
        }
    
        @Override
        public Component getListCellRendererComponent(JList<? extends Order> list, Order value, int index,
                boolean isSelected, boolean cellHasFocus) {
    
            // setOpaque(true); // !! 
    
            // !! setLayout(new FlowLayout());
            setText(value.getNameEl());
            // !! list.setVisibleRowCount(30);
            // !! list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
            setFont(new Font("Arial", Font.ITALIC, 17));
    
            if (isSelected) {
                setBackground(Color.BLUE);
                setForeground(Color.BLUE);
            } else {
                // TODO: fix!
                // setBackground(getBackground());
                // setForeground(getForeground());
                setBackground(null);
                setForeground(null);
            }
    
            if (value.getOrderRead()) {
                setBackground(Color.YELLOW);
            } else {
                setBackground(Color.WHITE);
            }
    
            if (value.getOrderDone()) {
                setBackground(Color.GREEN);
            } else {
                setBackground(Color.WHITE);
            }
            return this;
        }
    
    }
    
    class Order {
    
        private String nameE1;
        private boolean orderDone;
        private boolean orderRead;
    
        public Order(String nameE1) {
            this.nameE1 = nameE1;
        }
    
        public String getNameEl() {
            return nameE1;
        }
    
        public boolean getOrderDone() {
            return orderDone;
        }
    
        public boolean getOrderRead() {
            return orderRead;
        }
    
        public void setOrderDone(boolean orderDone) {
            this.orderDone = orderDone;
        }
    
        public void setOrderRead(boolean orderRead) {
            this.orderRead = orderRead;
        }
    
    }