JScrollPane "stretching" 出我添加的面板

JScrollPane "stretching" out the panel that I add to it

我必须制作一个可滚动列表,我可以在其中多次添加带有 3 个标签的面板。 我有点让它工作,但第一个面板被拉伸并占据了 JScrollPane 的所有区域,我无法弄清楚如何解决这个问题,我多次尝试更改布局但仍然无法修复它。 我希望添加的面板占据固定大小,但我无法弄清楚。此图片中的示例: https://i.stack.imgur.com/LNznP.png 左边的是我得到的,右边的(编辑过的)是我想要的。

这是我第一天使用 Swing,所以代码很可能是乱七八糟的,提前抱歉。

代码如下:

public class MainWindow extends JFrame implements ActionListener{
    private JPanel viewportPanel;
    private JButton addButton,remButton;
    private JScrollPane scrollPane;
    private int counter = 0;
    private JLabel dateLabel,dateLabel_1,dateLabel_2;
    
    public MainWindow(boolean run) {
        //BUTTONS
        addButton = new JButton("Add");
        addButton.setLocation(521, 11);
        addButton.setSize(101, 100);
        addButton.addActionListener(this);
        
        remButton = new JButton("Remove");
        remButton.setLocation(521, 122);
        remButton.setSize(101, 100);
        remButton.addActionListener(this);
        
        //SCROLLPANE
        scrollPane = new JScrollPane();
        scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
        scrollPane.setBounds(10, 11, 501, 211);
        add(scrollPane);
        
        //PANELS
        viewportPanel = new JPanel(new GridLayout(0,1));
        scrollPane.setViewportView(viewportPanel);
        
        //FRAME
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(100, 100, 650, 273);
        setResizable(false);
        //setIconImage(new ImageIcon("epic.png").getImage());
        setLayout(null);
        if(run) setVisible(true);
        add(addButton);
        add(remButton);
    }

    public void actionPerformed(ActionEvent e) {
        if(e.getSource() == addButton) {
            //LABELS
            dateLabel = new JLabel("DATE");
            dateLabel.setFont(new Font("Tahoma", Font.BOLD, 28));
            dateLabel.setSize(500,500);

            dateLabel_1 = new JLabel("LABEL1");
            dateLabel_1.setFont(new Font("Tahoma", Font.BOLD, 22));
            dateLabel_1.setBounds(10, 45, 481, 30);
            
            dateLabel_2 = new JLabel("LABEL2");
            dateLabel_2.setFont(new Font("Tahoma", Font.ITALIC, 22));
            dateLabel_2.setBounds(10, 45, 481, 30);
            
            //PANEL WITH ALL THE STUFF
            JPanel componentPanel = new JPanel();
            componentPanel.setLayout(new GridLayout(3, 1));
            componentPanel.setMaximumSize(new Dimension(50,50));
            componentPanel.setBorder(BorderFactory.createMatteBorder(1, 1, 1, 1, Color.BLACK));
            componentPanel.setBackground(Color.gray);
            componentPanel.add(dateLabel);
            componentPanel.add(dateLabel_1);
            componentPanel.add(dateLabel_2);
            
            viewportPanel.add(componentPanel);  //add panel with labels to viewportpanel
            counter++;
        }
        
        if(e.getSource() == remButton) {
            Component[] componentList = viewportPanel.getComponents();
            int lastElement = (componentList.length);
            viewportPanel.remove(--lastElement);
            --counter;
        }
        
        viewportPanel.revalidate();
        viewportPanel.repaint();
    }
}

有些帮助会很棒!

首先,永远不要这样做:

setLayout(null);

接下来,如果您希望将内容压缩在容器顶部,请使用执行此操作的布局。例如 BorderLayout,将压缩项目放入 JPanel(可能使用 GridLayout 的 JPanel)中 BorderLayout.PAGE_START

实际上,看起来您最好的解决方案是给我们一个 JList,它使用自定义呈现器将您的时间 JLabel 和两个文本 JLabel 放入 JPanel 中,并将其显示在列表中。那么让我们来探讨一下。

首先创建一个class来保存JList显示的数据,看起来是一个日期和两行文本:

public class ListItem {
    private LocalDate date;
    private String text1;
    private String text2;

    public ListItem(LocalDate date, String text1, String text2) {
        super();
        this.date = date;
        this.text1 = text1;
        this.text2 = text2;
    }

    public LocalDate getDate() {
        return date;
    }

    public String getText1() {
        return text1;
    }

    public String getText2() {
        return text2;
    }   
}

然后让我们创建一个渲染器,JList 可以使用该渲染器在 JPanel 中显示上述信息。这更复杂,需要 class 创建一个 JPanel 将标签放置在我们想要的位置,也许使用具有 1 列和 3 行的 GridLayout,加上行之间的一些间隙:setLayout(new GridLayout(3, 1, 2 * gap, 2 * gap));。 class 必须实现 ListCellRenderer<T> 接口,该接口具有一个方法:public Component getListCellRendererComponent(...)。 Java 会将参数传入此方法,包括一个 ListItem 值,我们将使用该值来填充我们添加到此 JPanel 中的 JLabel。编辑以突出显示所选项目。

import java.awt.*;
import java.time.format.DateTimeFormatter;
import javax.swing.*;
import javax.swing.border.Border;

@SuppressWarnings("serial")
public class ListItemRenderer extends JPanel implements ListCellRenderer<ListItem> {
    private static final Color LIGHT_BLUE = new Color(204, 255, 255);
    private static final Color REDDISH_GREY = new Color(205, 126, 121);
    private DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("MM/dd/yyyy");
    private static final int GAP = 2;
    private Border emptyBorder = BorderFactory.createEmptyBorder(GAP, GAP, GAP, GAP);
    private Border blackBorder = BorderFactory.createLineBorder(Color.BLACK);
    private Border redBorder = BorderFactory.createLineBorder(REDDISH_GREY, 2);
    private JLabel dateLabel = new JLabel();
    private JLabel text1Label = new JLabel();
    private JLabel text2Label = new JLabel();
    
    public ListItemRenderer() {
        dateLabel.setFont(dateLabel.getFont().deriveFont(Font.BOLD, 14f));
        text1Label.setFont(text1Label.getFont().deriveFont(Font.BOLD));
        text2Label.setFont(text1Label.getFont().deriveFont(Font.ITALIC));       
        
        int gap = 2;
        setLayout(new GridLayout(0, 1, 2 * gap, 2 * gap));
        
        setBorder(BorderFactory.createCompoundBorder(emptyBorder, blackBorder));
        add(dateLabel);
        add(text1Label);
        add(text2Label);
    }

    @Override
    public Component getListCellRendererComponent(JList<? extends ListItem> list, ListItem value, int index,
            boolean isSelected, boolean cellHasFocus) {
        if (value != null) {
            String dateText = dateFormatter.format(value.getDate());
            dateLabel.setText(dateText);
            text1Label.setText(value.getText1());
            text2Label.setText(value.getText2());
        } else {
            dateLabel.setText("");
            text1Label.setText("");
            text2Label.setText("");
        }
        
        if (isSelected ) {
            setBackground(LIGHT_BLUE);
            setBorder(BorderFactory.createCompoundBorder(emptyBorder, redBorder));
        } else {
            setBackground(null);
            setBorder(BorderFactory.createCompoundBorder(emptyBorder, blackBorder));
        }
        return this;
    }
    
}

现在是将所有这些放在一起的主程序,经过编辑以显示删除项目:

import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.KeyEvent;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.Random;
import javax.swing.*;

@SuppressWarnings("serial")
public class MainPanel extends JPanel {
    DefaultListModel<ListItem> listModel = new DefaultListModel<>();
    private JList<ListItem> jList = new JList<>(listModel);
    private JScrollPane scrollPane = new JScrollPane(jList);
    private JButton addButton = new JButton("Add");
    private JButton removeButton = new JButton("Remove");
    
    public MainPanel() {
        jList.setPrototypeCellValue(new ListItem(LocalDate.now(), 
                "This is text 1 for testing.  This is text 1 for testing.   This is text 1 for testing", 
                "This is text 2 for testing.  This is text 2 for testing.   This is text 2 for testing"));
        jList.setVisibleRowCount(4);
        jList.setCellRenderer(new ListItemRenderer());
        scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
        
        JPanel buttonPanel = new JPanel(new GridLayout(0, 1, 3, 3));
        buttonPanel.add(addButton);
        buttonPanel.add(removeButton);
        
        addButton.setMnemonic(KeyEvent.VK_A);
        removeButton.setMnemonic(KeyEvent.VK_R);
        addButton.addActionListener(e -> addEvent());
        removeButton.addActionListener(e -> removeEvent());
        
        JPanel rightPanel = new JPanel();
        rightPanel.add(buttonPanel);
        
        int gap = 5;
        setBorder(BorderFactory.createEmptyBorder(gap, gap, gap, gap)); 
        setLayout(new BorderLayout());
        add(scrollPane);
        add(rightPanel, BorderLayout.LINE_END);
    }
    
    private void removeEvent() {
        int[] selectedIndices = jList.getSelectedIndices();
        for (int i = selectedIndices.length - 1; i >= 0; i--) {
            listModel.remove(selectedIndices[i]);
        }
    }

    private void addEvent() {
        // this adds random stuff to the JList
        String text1 = "Some random text: " + randomText();
        String text2 = "Some random text: " + randomText();
            
        listModel.addElement(new ListItem(LocalDate.now(), text1, text2));
        
        // TODO: change this so that it adds *real* data to the JList
    }
    
    private String randomText() {
        Random random = new Random();
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < 2 + random.nextInt(3); i++) {
            for (int j = 0; j < 3 + random.nextInt(5); j++) {
                char c = (char) ('a' + random.nextInt('z' - 'a'));
                builder.append(c);
            }
            builder.append(" ");
        }
        return builder.toString();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            JFrame frame = new JFrame("GUI");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add(new MainPanel());
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        });
    }
}

输出如下: