在 JList 中自定义渲染 JPanel

Custom Render JPanel in a JList

我正在尝试通过修改渲染在 JList 中显示面板。

我用 JLabel (https://www.codejava.net/java-se/swing/jlist-custom-renderer-example) 尝试了一个示例,它运行良好(见图)

所以我尝试将它改编为 JPanel(而不是 JLabel),但我遇到了一个有趣的问题,我真的不知道如何解决它。

如您所见,不是只出现一次,而是每行显示每个国家及其相关图像,我不明白为什么。 (有8行因为有8个国家)

这是我编写的代码:

CountryRenderer.java

import java.awt.*;
import java.io.IOException; 
import javax.imageio.ImageIO;
import javax.swing.*;

public class CountryRenderer extends JPanel implements ListCellRenderer<Country> {

    @Override
    public Component getListCellRendererComponent(JList<? extends Country> list, Country country, int index,
        boolean isSelected, boolean cellHasFocus) {
          
        String code = country.getCode();
        
        // to load and resize the image 
        Image imgSettings = null;
        try {
            imgSettings = ImageIO.read(getClass().getResource("./images/" + code + "1.png"));
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        imgSettings = imgSettings.getScaledInstance(25, 25, imgSettings.SCALE_SMOOTH); 
         
        // create the button and put the image on it
        JButton buttontest = new JButton() ; 
        buttontest.setIcon(new ImageIcon(imgSettings));
        add(buttontest); 
        
        // create the text (name of the country) 
        JTextField txtest = new JTextField(); 
        txtest.setText(country.getName());
        add(txtest); 
       
        return this;
    }
}

如果您需要其他 2 个文件来制作这个 运行,它们在我放在上面或这里的 link 上:

Country.java

public class Country {
     
    private String name;
    private String code;
 
    public Country(String name, String code) {
        this.name = name;
        this.code = code;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public String getCode() {
        return code;
    }
 
    public void setCode(String code) {
        this.code = code;
    }
    
    @Override
    public String toString() {
        return name;
    }
} 

这是您可以 运行 的文件: JListCustomRendererExample.java

import javax.swing.*;
 
public class JListCustomRendererExample extends JFrame {
 
    public JListCustomRendererExample() {
        Country us = new Country("USA", "1");
        Country in = new Country("India", "2");
        Country vn = new Country("Vietnam", "3");
        Country ca = new Country("Canada", "4");
        Country de = new Country("Denmark", "5");
        Country fr = new Country("France", "6");
        Country gb = new Country("Great Britain", "7");
        Country jp = new Country("Japan", "8");
 
        //create the model and add elements
        DefaultListModel<Country> listModel = new DefaultListModel<>();
        listModel.addElement(us);
        listModel.addElement(in);
        listModel.addElement(vn);
        listModel.addElement(ca);
        listModel.addElement(de);
        listModel.addElement(fr);
        listModel.addElement(gb);
        listModel.addElement(jp);
 
        //create the list
        JList<Country> countryList = new JList<>(listModel);
        add(new JScrollPane(countryList));
 
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setTitle("JList Renderer Example");
        this.setSize(200, 200);
        this.setLocationRelativeTo(null);
        this.setVisible(true);
        
        countryList.setCellRenderer(new CountryRenderer());
    }
 
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new JListCustomRendererExample();
            }
        });
    }
}

编辑

我尝试了@Gilbert le blanc 的回答,但我仍然有问题,我尝试向这个标签添加新元素,比如一个按钮,我希望这些按钮上有与名称相同的文本它所在的国家/地区行。 示例:在美国行,我希望按钮上有“美国”。

所以我在 public Component getListCellRendererComponent 中添加了这 4 行:

label.setLayout(null);
JButton test = new JButton(country.getName()); 
test.setBounds(10,10,50,50);
label.add(test); 

我明白了:

这不是预期的结果,这与我在第一个问题中遇到的问题有点相同,您知道为什么吗?有解决办法吗?

Oracle 有一个有用的教程,Creating a GUI With Swing。跳过 Netbeans 部分。

事实证明,您不能使用 JPanel 来保持 JList 选择。你必须使用 JLabel.

因此,我设计了一个略有不同的 GUI。我从您使用的教程中复制了图标。这些图标位于源代码 zip 文件中。

我做的第一件不同的事情是将图像添加到 Country class。我还将所有 class 字段设为最终字段,因为它们不会更改。

public class Country {
    
    private final Image image;
     
    private final String name;
    private final String code;
 
    public Country(String name, String code) {
        this.name = name;
        this.code = code;
        this.image = getImage(code);
    }
    
    private Image getImage(String code) {
        Image image = null;
        try {
            URL url = getClass().getResource("/images/" + code + ".png");
            image = ImageIO.read(url);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return image;
    }
 
    public String getName() {
        return name;
    }
 
    public String getCode() {
        return code;
    }
    
    public Image getImage() {
        return image;
    }

    @Override
    public String toString() {
        return name;
    }
    
} 

接下来,我创建了一个 CountryModel class 来保存 ListModel

public class CountryModel {

    private final DefaultListModel<Country> listModel;

    public CountryModel() {
        Country us = new Country("USA", "us");
        Country in = new Country("India", "in");
        Country vn = new Country("Vietnam", "vn");
        Country ca = new Country("Canada", "ca");
        Country de = new Country("Denmark", "de");
        Country fr = new Country("France", "fr");
        Country gb = new Country("Great Britain", "gb");
        Country jp = new Country("Japan", "jp");

        // create the model and add elements
        listModel = new DefaultListModel<>();
        listModel.addElement(us);
        listModel.addElement(in);
        listModel.addElement(vn);
        listModel.addElement(ca);
        listModel.addElement(de);
        listModel.addElement(fr);
        listModel.addElement(gb);
        listModel.addElement(jp);
    }

    public DefaultListModel<Country> getListModel() {
        return listModel;
    }

}

这简化了 JListCustomRendererExample 视图 class。我创建国家模型,创建 JFrame,创建 JList JPanelJButton JPanel.

JFrameJPanels 创建单独的方法有助于将相似的代码放在一起,将不同的代码分离到方法中。这使得代码更易于阅读和修改。

JButton JPanel 允许您创建一个 ActionListener 来处理 JList 个选择。

public class JListCustomRendererExample implements Runnable {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new JListCustomRendererExample());
    }
    
    private final CountryModel model;
    
    public JListCustomRendererExample() {
        this.model = new CountryModel();
    }

    @Override
    public void run() {
        JFrame frame = new JFrame("JList Renderer Example");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        frame.add(createMainPanel(model), BorderLayout.CENTER);
        frame.add(createButtonPanel(), BorderLayout.SOUTH);
        
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
    
    private JPanel createMainPanel(CountryModel model) {
        JPanel panel = new JPanel(new BorderLayout());
        panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
        
        JList<Country> countryList = new JList<>(model.getListModel());
        countryList.setCellRenderer(new CountryRenderer());
        panel.add(new JScrollPane(countryList), BorderLayout.CENTER);
        
        return panel;
    }
    
    private JPanel createButtonPanel() {
        JPanel panel = new JPanel(new BorderLayout());
        panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
        
        JButton button = new JButton("Select Countries");
        panel.add(button, BorderLayout.CENTER);
        
        return panel;
    }

}

最后,我们有 ListCellRenderer class。

public class CountryRenderer implements ListCellRenderer<Country> {

    private final JLabel label;
    
    public CountryRenderer() {
        this.label = new JLabel();
        label.setOpaque(true);
    }

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

        label.setIcon(new ImageIcon(country.getImage()));
        label.setText(country.getName());

        if (isSelected) {
            label.setBackground(list.getSelectionBackground());
            label.setForeground(list.getSelectionForeground());
        } else {
            label.setBackground(list.getBackground());
            label.setForeground(list.getForeground());
        }

        return label;
    }
}

这是完整的可运行代码。我在 classes 中添加了额外的 classes,这样我就可以 post 将代码作为一个块。

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Image;
import java.io.IOException;
import java.net.URL;

import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.DefaultListModel;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListCellRenderer;
import javax.swing.SwingUtilities;

public class JListCustomRendererExample implements Runnable {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new JListCustomRendererExample());
    }
    
    private final CountryModel model;
    
    public JListCustomRendererExample() {
        this.model = new CountryModel();
    }

    @Override
    public void run() {
        JFrame frame = new JFrame("JList Renderer Example");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        frame.add(createMainPanel(model), BorderLayout.CENTER);
        frame.add(createButtonPanel(), BorderLayout.SOUTH);
        
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
    
    private JPanel createMainPanel(CountryModel model) {
        JPanel panel = new JPanel(new BorderLayout());
        panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
        
        JList<Country> countryList = new JList<>(model.getListModel());
        countryList.setCellRenderer(new CountryRenderer());
        panel.add(new JScrollPane(countryList), BorderLayout.CENTER);
        
        return panel;
    }
    
    private JPanel createButtonPanel() {
        JPanel panel = new JPanel(new BorderLayout());
        panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
        
        JButton button = new JButton("Select Countries");
        panel.add(button, BorderLayout.CENTER);
        
        return panel;
    }
    
    public class CountryRenderer implements ListCellRenderer<Country> {

        private final JLabel label;
        
        public CountryRenderer() {
            this.label = new JLabel();
            label.setOpaque(true);
        }

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

            label.setIcon(new ImageIcon(country.getImage()));
            label.setText(country.getName());

            if (isSelected) {
                label.setBackground(list.getSelectionBackground());
                label.setForeground(list.getSelectionForeground());
            } else {
                label.setBackground(list.getBackground());
                label.setForeground(list.getForeground());
            }

            return label;
        }
    }
    
    public class CountryModel {

        private final DefaultListModel<Country> listModel;

        public CountryModel() {
            Country us = new Country("USA", "us");
            Country in = new Country("India", "in");
            Country vn = new Country("Vietnam", "vn");
            Country ca = new Country("Canada", "ca");
            Country de = new Country("Denmark", "de");
            Country fr = new Country("France", "fr");
            Country gb = new Country("Great Britain", "gb");
            Country jp = new Country("Japan", "jp");

            // create the model and add elements
            listModel = new DefaultListModel<>();
            listModel.addElement(us);
            listModel.addElement(in);
            listModel.addElement(vn);
            listModel.addElement(ca);
            listModel.addElement(de);
            listModel.addElement(fr);
            listModel.addElement(gb);
            listModel.addElement(jp);
        }

        public DefaultListModel<Country> getListModel() {
            return listModel;
        }

    }
    
    public class Country {
        
        private final Image image;
         
        private final String name;
        private final String code;
     
        public Country(String name, String code) {
            this.name = name;
            this.code = code;
            this.image = getImage(code);
        }
        
        private Image getImage(String code) {
            Image image = null;
            try {
                URL url = getClass().getResource("/images/" + code + ".png");
                image = ImageIO.read(url);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return image;
        }
     
        public String getName() {
            return name;
        }
     
        public String getCode() {
            return code;
        }
        
        public Image getImage() {
            return image;
        }

        @Override
        public String toString() {
            return name;
        }
        
    } 

}

好吧,我设法通过创建一个带有侦听器的 JPanel 来解决自己的问题,因此它就像一个 JList,但我可以将我想要的对象放入其中,这样它甚至可以更好地工作。