可滚动 (GridBagLayout) 面板内的空 JTables - header 在调整列大小时折叠

Empty JTables inside a scrollable (GridBagLayout) panel - header collapses on column resize

我正在尝试创建一个 "sectioned" table,它们实际上是 "scrollable" JPanel 中的多个 table 通过 GridBagLayout。 table共享相同的table模型(class),tableheader和列模型,JTableHeader设置为列header 包含所有内容的 JScrollPane 视图。只有一个JScrollPane.

frame (BorderLayout)
    |- JScrollPane
        |- JPanel (GridBagLayout)
            |- Section title panel
            |- JTable 1
            |- Section title panel
            |- JTable 2
            |- JTable (fake)
            |- vertical filler

一切似乎都按预期工作,直到我尝试调整任何一个 tables 中没有值的任意列的大小 - 如果 tables 至少有一行,它工作正如预期的那样。我考虑过在 table 中有 "null" 行,但这会干扰过滤、排序等。所以我更改了代码以包含一个 "obscured" 假 table,这意味着通过始终保持一行来保持 header 表现良好。

但这不起作用。一旦 table 之一为空并尝试调整列大小,table header 就会损坏(其中一列缩小)。

为什么会发生这种情况,我该怎么办?

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.LayoutManager;
import java.awt.Rectangle;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.Scrollable;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;

public class SectionTables extends JFrame {

    public SectionTables() {
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLayout(new BorderLayout());

        GridBagConstraints gbc;

        JPanel tables = new ScrollableJPanel(new GridBagLayout());
        JScrollPane scrollPane = new JScrollPane(tables);

        JPanel section1Title = new JPanel(new BorderLayout());     
        section1Title.setBorder(BorderFactory.createMatteBorder(1, 1, 1, 1, UIManager.getColor("controlShadow")));
        JLabel section1 = new JLabel("Section One", null, JLabel.CENTER);
        section1Title.add(section1);
        gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 0;
        gbc.weightx = 1.0d;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        tables.add(section1Title, gbc);

        MyTableModel model1 = new MyTableModel();
        JTable table1 = new MyTable(model1);
        gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 1;
        gbc.weightx = 1.0d;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        tables.add(table1, gbc);

        JPanel section2Title = new JPanel(new BorderLayout());     
        section2Title.setBorder(BorderFactory.createMatteBorder(1, 1, 1, 1, UIManager.getColor("controlShadow")));
        JLabel section2 = new JLabel("Section Two", null, JLabel.CENTER);
        section2Title.add(section2);
        gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 2;
        gbc.weightx = 1.0d;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        tables.add(section2Title, gbc);

        MyTableModel model2 = new MyTableModel();
        JTable table2 = new MyTable(model2);   
        gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 3;
        gbc.weightx = 1.0d;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        tables.add(table2, gbc);

        MyTableModel modelFake = new MyTableModel();
        modelFake.addRow(new String[] {"", "", ""});
        JTable tableFake = new MyObscuredTable();   
        tableFake.setModel(modelFake);
        gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 4;
        gbc.weightx = 1.0d;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        tables.add(tableFake, gbc);

        Box.Filler filler1 = new Box.Filler(new Dimension(0, 0), new Dimension(0, 0), new Dimension(0, 32767));
        gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 5;
        gbc.weighty = 1.0d;
        gbc.fill = GridBagConstraints.VERTICAL;
        tables.add(filler1, gbc);

        add(scrollPane);

        TableColumnModel columnModel = table1.getColumnModel();
        table2.setColumnModel(columnModel);
        tableFake.setColumnModel(columnModel);

        JTableHeader tableHeader = new JTableHeader(columnModel);
        scrollPane.setColumnHeaderView(tableHeader);
        table1.setTableHeader(tableHeader);
        table2.setTableHeader(tableHeader);
        tableFake.setTableHeader(tableHeader);


        // if tables are filled, the issue does not occur
//        model1.addRow(new String[] {"", "", ""});
//        model2.addRow(new String[] {"", "", ""});

        pack();
        setLocationRelativeTo(null);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new SectionTables().setVisible(true);
            }
        });
    }

    private class MyTableModel extends DefaultTableModel {
        private String[] columnNames = new String[] {"First", "Second", "Third"};
        private Class[] columnClasses = new Class[] {String.class, String.class, String.class};

        @Override
        public int getColumnCount() {
            return columnNames.length;
        }

        @Override
        public String getColumnName(int column) {
            return columnNames[column];
        }

        @Override
        public Class<?> getColumnClass(int columnIndex) {
            return columnClasses[columnIndex];
        }

        @Override
        public boolean isCellEditable(int row, int column) {
            return false;
        }        

    }

    private class ScrollableJPanel extends JPanel implements Scrollable {

        public ScrollableJPanel(LayoutManager layout) {
            super(layout);
        }

        public ScrollableJPanel() {
            super();
        }

        @Override
        public Dimension getPreferredScrollableViewportSize() {
            return getPreferredSize();
        }

        @Override
        public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
            return 16;
        }

        @Override
        public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
            return 16;
        }

        @Override
        public boolean getScrollableTracksViewportWidth() {
            return true;
        }

        @Override
        public boolean getScrollableTracksViewportHeight() {
            return false;
        }

    }

    private class MyObscuredTable extends JTable {

        @Override
        public Dimension getPreferredSize() {
            int height; 
            height = 1; // obscure null row           
            Container ancestorOfClass = SwingUtilities.getAncestorOfClass(JPanel.class, this);
            int width = ancestorOfClass.getWidth();
            return new Dimension(width, height);
        }

    }

    private class MyTable extends JTable {

        public MyTable(TableModel model) {
            super(model);
        }

        @Override
        public Dimension getPreferredSize() {
            int height; 
            height = getRowHeight() * getRowCount();
            Container ancestorOfClass = SwingUtilities.getAncestorOfClass(JPanel.class, this);
            int width = ancestorOfClass.getWidth();
            return new Dimension(width, height);
        }

    }
}

也许您可以修改这种使用 TableColumnModelListener 的方法,以便在使用多个表时保持列宽同步:

import java.awt.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;

public class TableColumnsShared implements Runnable
{
  JTable table1, table2;
  TableColumnModelListener columnListener1, columnListener2;
  Map<JTable, TableColumnModelListener> map;

  public static void main(String[] args)
  {
    SwingUtilities.invokeLater(new TableColumnsShared());
  }

  public void run()
  {
    Vector<String> names = new Vector<String>();
    names.add("One");
    names.add("Two");
    names.add("Three");

    table1 = new JTable(null, names);
    table2 = new JTable(null, names);

    columnListener1 = new ColumnChangeListener(table1, table2);
    columnListener2 = new ColumnChangeListener(table2, table1);

    table1.getColumnModel().addColumnModelListener(columnListener1);
    table2.getColumnModel().addColumnModelListener(columnListener2);

    map = new HashMap<JTable, TableColumnModelListener>();
    map.put(table1, columnListener1);
    map.put(table2, columnListener2);

    JPanel p = new JPanel(new GridLayout(2,1));
    p.add(new JScrollPane(table1));
    p.add(new JScrollPane(table2));

    JFrame frame = new JFrame();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.getContentPane().add(p);
    frame.setSize(300, 200);
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
  }

  class ColumnChangeListener implements TableColumnModelListener
  {
    JTable sourceTable;
    JTable targetTable;

    public ColumnChangeListener(JTable source, JTable target)
    {
      this.sourceTable = source;
      this.targetTable = target;
    }

    public void columnAdded(TableColumnModelEvent e) {}
    public void columnSelectionChanged(ListSelectionEvent e) {}
    public void columnRemoved(TableColumnModelEvent e) {}
    public void columnMoved(TableColumnModelEvent e) {}

    public void columnMarginChanged(ChangeEvent e)
    {
      TableColumnModel sourceModel = sourceTable.getColumnModel();
      TableColumnModel targetModel = targetTable.getColumnModel();
      TableColumnModelListener listener = map.get(targetTable);

      targetModel.removeColumnModelListener(listener);

      for (int i = 0; i < sourceModel.getColumnCount(); i++)
      {
        targetModel.getColumn(i).setPreferredWidth(sourceModel.getColumn(i).getWidth());
      }

      targetModel.addColumnModelListener(listener);
    }
  }
}

根据@camickr 的建议,最终得到了这个。比我最初的尝试优雅得多。

import java.awt.*;
import java.util.*;
import java.util.List;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.event.TableColumnModelListener;
import javax.swing.table.*;

public class SectionTables extends JFrame {

    private Map<JTable, TableColumnModelListener> tableToColModelListener;

    public SectionTables() {
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLayout(new BorderLayout());

        GridBagConstraints gbc;

        JPanel tables = new ScrollableJPanel(new GridBagLayout());
        JScrollPane scrollPane = new JScrollPane(tables);

        JPanel section1Title = new JPanel(new BorderLayout());     
        section1Title.setBorder(BorderFactory.createMatteBorder(1, 1, 1, 1, UIManager.getColor("controlShadow")));
        JLabel section1 = new JLabel("Section One", null, JLabel.CENTER);
        section1Title.add(section1);
        gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 0;
        gbc.weightx = 1.0d;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        tables.add(section1Title, gbc);

        MyTableModel model1 = new MyTableModel();
        JTable table1 = new MyTable(model1);
        gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 1;
        gbc.weightx = 1.0d;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        tables.add(table1, gbc);

        JPanel section2Title = new JPanel(new BorderLayout());     
        section2Title.setBorder(BorderFactory.createMatteBorder(1, 1, 1, 1, UIManager.getColor("controlShadow")));
        JLabel section2 = new JLabel("Section Two", null, JLabel.CENTER);
        section2Title.add(section2);
        gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 2;
        gbc.weightx = 1.0d;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        tables.add(section2Title, gbc);

        MyTableModel model2 = new MyTableModel();
        JTable table2 = new MyTable(model2);   
        gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 3;
        gbc.weightx = 1.0d;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        tables.add(table2, gbc);

        MyTableModel modelFake = new MyTableModel();
        modelFake.addRow(new String[] {"", "", ""});
        JTable tableFake = new MyObscuredTable();   
        tableFake.setModel(modelFake);
        gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 4;
        gbc.weightx = 1.0d;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        tables.add(tableFake, gbc);

        Box.Filler filler1 = new Box.Filler(new Dimension(0, 0), new Dimension(0, 0), new Dimension(0, 32767));
        gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 5;
        gbc.weighty = 1.0d;
        gbc.fill = GridBagConstraints.VERTICAL;
        tables.add(filler1, gbc);

        add(scrollPane);

        tableToColModelListener = new HashMap<JTable, TableColumnModelListener>();

        scrollPane.setColumnHeaderView(tableFake.getTableHeader());


        MyColumnChangeListener listener;

        listener = new MyColumnChangeListener(tableFake, table1, table2);
        tableFake.getColumnModel().addColumnModelListener(listener);
        tableToColModelListener.put(tableFake, listener);

        setSize(200, 200);
        setLocationRelativeTo(null);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new SectionTables().setVisible(true);
            }
        });
    }

    private class MyColumnChangeListener implements TableColumnModelListener {

        private JTable sourceTable;
        private List<JTable> targetTables;

        public MyColumnChangeListener(JTable source, JTable... targets) {
            this.sourceTable = source;
            if (targets.length < 1) {
                throw new IllegalArgumentException();
            }
            this.targetTables = Arrays.asList(targets);
        }

        public void columnAdded(TableColumnModelEvent e) {}
        public void columnRemoved(TableColumnModelEvent e) {}
        public void columnMoved(TableColumnModelEvent e) {}
        public void columnSelectionChanged(ListSelectionEvent e) {}

        @Override
        public void columnMarginChanged(ChangeEvent e) {
            TableColumnModel sourceModel = sourceTable.getColumnModel();

            for (JTable targetTable : targetTables) {
                TableColumnModel targetModel = targetTable.getColumnModel();
                TableColumnModelListener listener = tableToColModelListener.get(targetTable);
                targetModel.removeColumnModelListener(listener);
                try {
                    for (int i = 0; i < sourceModel.getColumnCount(); i++) {
                        targetModel.getColumn(i).setPreferredWidth(sourceModel.getColumn(i).getWidth());
                    }
                } finally {
                    targetModel.addColumnModelListener(listener);
                }
            }
        }

    }

    private class MyTableModel extends DefaultTableModel {
        private String[] columnNames = new String[] {"First", "Second", "Third"};
        private Class[] columnClasses = new Class[] {String.class, String.class, String.class};

        @Override
        public int getColumnCount() {
            return columnNames.length;
        }

        @Override
        public String getColumnName(int column) {
            return columnNames[column];
        }

        @Override
        public Class<?> getColumnClass(int columnIndex) {
            return columnClasses[columnIndex];
        }

        @Override
        public boolean isCellEditable(int row, int column) {
            return false;
        }        

    }

    private class ScrollableJPanel extends JPanel implements Scrollable {

        public ScrollableJPanel(LayoutManager layout) {
            super(layout);
        }

        public ScrollableJPanel() {
            super();
        }

        @Override
        public Dimension getPreferredScrollableViewportSize() {
            return getPreferredSize();
        }

        @Override
        public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
            return 16;
        }

        @Override
        public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
            return 16;
        }

        @Override
        public boolean getScrollableTracksViewportWidth() {
            return true;
        }

        @Override
        public boolean getScrollableTracksViewportHeight() {
            return false;
        }

    }

    private class MyObscuredTable extends JTable {

        @Override
        public Dimension getPreferredSize() {
            int height; 
            height = 1; // obscure null row           
            Container ancestorOfClass = SwingUtilities.getAncestorOfClass(JPanel.class, this);
            int width = ancestorOfClass.getWidth();
            return new Dimension(width, height);
        }

    }

    private class MyTable extends JTable {

        public MyTable(TableModel model) {
            super(model);
        }

        @Override
        public Dimension getPreferredSize() {
            int height; 
            height = getRowHeight() * getRowCount();
            Container ancestorOfClass = SwingUtilities.getAncestorOfClass(JPanel.class, this);
            int width = ancestorOfClass.getWidth();
            return new Dimension(width, height);
        }

    }
}