Set Column Width of JTable by Percentage

2019-03-20 15:20发布

问题:

I need to assign a fixed width to a few columns of a JTable and then an equal width to all the other columns.

Suppose a JTable has 5 columns. The first column should have a width of 100 and the second one a width of 150. If the remaining width of the JTable is 600 after setting the width of the two columns, I'd like to evenly split it among the other three columns.

The problem is table.getParent().getSize().width is often 0, even if it is added to the JFrame and visible, so I can't use it as a basis.

How do I go about doing this?

回答1:

public MyJFrame() {
    initComponents();
    resizeColumns();
    addComponentListener(new ComponentAdapter() {
        @Override
        public void componentResized(ComponentEvent e) {
            resizeColumns();
        }
    });
}
//SUMS 100%
float[] columnWidthPercentage = {20.0f, 55.0f, 10.0f, 5.0f, 5.0f, 5.0f};

private void resizeColumns() {
    int tW = jTable1.getWidth();
    TableColumn column;
    TableColumnModel jTableColumnModel = jTable1.getColumnModel();
    int cantCols = jTableColumnModel.getColumnCount();
    for (int i = 0; i < cantCols; i++) {
        column = jTableColumnModel.getColumn(i);
        int pWidth = Math.round(columnWidthPercentage[i] * tW);
        column.setPreferredWidth(pWidth);
    }
}


回答2:

I need to assign a fixed width to a few columns of a JTable and then an equal width to all the other columns.

Let the table's resize mode do the work for you. Set the resize mode to all columns and set the min/max values of the fixed columns:

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

public class TableLayout extends JPanel
{
    public TableLayout()
    {
        setLayout( new BorderLayout() );

        JTable table = new JTable(5, 7);
        add( new JScrollPane( table ) );

        table.setAutoResizeMode( JTable.AUTO_RESIZE_ALL_COLUMNS );
        TableColumn columnA = table.getColumn("A");
        columnA.setMinWidth(100);
        columnA.setMaxWidth(100);
        TableColumn columnC = table.getColumn("C");
        columnC.setMinWidth(50);
        columnC.setMaxWidth(50);
    }

    private static void createAndShowUI()
    {
        JFrame frame = new JFrame("TableLayout");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add( new TableLayout() );
        frame.setSize(600, 200);
        frame.setLocationByPlatform( true );
        frame.setVisible( true );
    }

    public static void main(String[] args)
    {
        EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                createAndShowUI();
            }
        });
    }
}


回答3:

I think you need to use table.getPreferredSize() instead. Try this code snippet:

import java.awt.Dimension;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;


public class Tests {

    private void initGUI(){        
        Object[] tableHeader = new Object[]{"Name", "Category", "Color","Ranking"};
        DefaultTableModel dftm = new DefaultTableModel(tableHeader, 0);        
        dftm.addRow(new Object[]{"Watermelon","Fruit","Green and red",3});
        dftm.addRow(new Object[]{"Tomato","Vegetable","Red",5});
        dftm.addRow(new Object[]{"Carrot","Vegetable","Orange",2});

        JTable table = new JTable(dftm);

        JScrollPane scrollPane = new JScrollPane();
        scrollPane.setViewportView(table);

        Dimension tableSize = table.getPreferredSize();
        table.getColumn("Name").setPreferredWidth(100);
        table.getColumn("Category").setPreferredWidth(150);
        table.getColumn("Color").setPreferredWidth(Math.round((tableSize.width - 250)* 0.70f));
        table.getColumn("Ranking").setPreferredWidth(Math.round((tableSize.width - 250)* 0.30f));

        JFrame frame = new JFrame("Demo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(scrollPane);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                 new Tests().initGUI();
            }
        });        

    }
}

As you'll see, Name column will have a width of 100, Category will have a width of 150, Color column will fit at 70% of remanent width and Ranking will fit at last 30%.

Update

Based on this comment:

Thanks, but this will not work if the JFrame's size is set explicitly larger than the JTable...

Solution could be play with setMinWidth and setMaxWidth methods to fix static columns width, or you can implement your own TableColumnModelListener. In the example above replace setPreferredWith lines as follows and try set frame's preferred size as you wish:

    final JTable table = new JTable(dftm);        
    table.getColumnModel().addColumnModelListener(new TableColumnModelListener() {

        @Override
        public void columnAdded(TableColumnModelEvent e) {
            table.columnAdded(e);
        }

        @Override
        public void columnRemoved(TableColumnModelEvent e) {
            table.columnRemoved(e);
        }

        @Override
        public void columnMoved(TableColumnModelEvent e) {
            table.columnMoved(e);
        }

        @Override
        public void columnMarginChanged(ChangeEvent e) {
            Dimension tableSize = table.getSize();
            table.getColumn("Name").setWidth(100);
            table.getColumn("Category").setWidth(150);
            table.getColumn("Color").setWidth(Math.round((tableSize.width - 250)* 0.70f));
            table.getColumn("Ranking").setWidth(Math.round((tableSize.width - 250)* 0.30f));
        }

        @Override
        public void columnSelectionChanged(ListSelectionEvent e) {
            table.columnSelectionChanged(e);
        }
    });


回答4:

I had to dynamically set my table columns width for a project that had to display correctly on different screen resolution DPI settings. The idea for my solution came from dic19s answer.

Basically I got the preferred size of the JPanel where the JSrollPane of the JTable was placed and work great. Please see below

Dimension tableSize =  tableScrollPanePanel.getPreferredSize();
table.getColumnModel().getColumn(0).setPreferredWidth(Math.round(tableSize.width*0.35f));
table.getColumnModel().getColumn(1).setPreferredWidth(Math.round(tableSize.width*0.215f));
table.getColumnModel().getColumn(2).setPreferredWidth(Math.round(tableSize.width*0.20f));
table.getColumnModel().getColumn(3).setPreferredWidth(Math.round(tableSize.width*0.10f));
table.getColumnModel().getColumn(4).setPreferredWidth(Math.round(tableSize.width*0.10f));

Hope this helps somebody

Thanks



回答5:

It think it would be easier to use (and re-use) relative component resizing if we encapsulate it in an own class. Since I also was confronted with the same issue, I would like to post my code here.

From a client perspective I would like to do something like this

TableColumnModel columnModel = jTable.getColumnModel();

TableColumn firstColumn = columnModel.getColumn(0);
TableColumn secondColumn = columnModel.getColumn(1);

ComponentResize<TableColumn> columnResize = new TableColumnResize();
RelativeWidthResizer<TableColumn> relativeWidthResizer = new RelativeWidthResizer<TableColumn>(columnResize);

relativeWidthResizer.setRelativeWidth(firstColumn, 0.8);
relativeWidthResizer.setRelativeWidth(secondColumn, 0.2);

jTable.addComponentListener(relativeWidthResizer);

So I first defined the ComponentResize interface and implement a TableColumnResize

public interface ComponentResize<T> {
    public void setWidth(T component, int width);
}

public class TableColumnResize implements ComponentResize<TableColumn> {

    public void setWidth(TableColumn component, int width) {
        component.setPreferredWidth(width);
    }
}

The ComponentResize interface decouples the way a component's size is set from the concrete APIs. E.g. a TableColumn's width can be set via setPreferredWidth(int) while a JComponent's size can be set by setPreferredWidth(Dimension)

Than I implemented the RelativeWidthResizer that encapsulates the relative width calculation logic.

public class RelativeWidthResizer<T> extends ComponentAdapter {

    private Map<T, Double> relativeWidths = new HashMap<T, Double>();
    private ComponentResize<T> componentResize;

    public RelativeWidthResizer(ComponentResize<T> componentResize) {
        this.componentResize = componentResize;
    }

    public void setRelativeWidth(T component, double relativeWidth) {
        if (relativeWidth < 0.0) {
            throw new IllegalArgumentException(
                    "Relative width must be greater or equal to 0.0");
        }

        if (relativeWidth > 1.0) {
            throw new IllegalArgumentException(
                    "Relative width must be less or equal to 1.0");
        }

        double totalRelativeWidth = 0.0;
        for (Double relativeComponentWidth : relativeWidths.values()) {
            totalRelativeWidth += relativeComponentWidth.doubleValue();
        }

        double availableRelativeWidth = 1.0d - (totalRelativeWidth + relativeWidth);

        boolean totalPercentageExceeded = availableRelativeWidth < 0;
        if (totalPercentageExceeded) {
            double remainingRelativeWidth = 1.0d - totalRelativeWidth;
            String message = MessageFormat.format(
                    "Can't set component's relative width to {0}."
                            + " {1} relative width remaining", relativeWidth,
                    remainingRelativeWidth);
            throw new IllegalArgumentException(message);
        }

        relativeWidths.put(component, relativeWidth);
    }

    @Override
    public void componentResized(ComponentEvent e) {
        Component component = e.getComponent();
        apply(component);
    }

    public void apply(Component baseComponent) {
        Dimension size = baseComponent.getSize();
        int maxWidth = (int) size.getWidth();

        int remaining = maxWidth;

        Set<Entry<T, Double>> entrySet = relativeWidths.entrySet();
        Iterator<Entry<T, Double>> entrySetIter = entrySet.iterator();

        while (entrySetIter.hasNext()) {
            Entry<T, Double> componentEntry = entrySetIter.next();
            T componentToResize = componentEntry.getKey();
            Double relativeWidth = componentEntry.getValue();

            int width = (int) (maxWidth * relativeWidth.doubleValue());
            remaining -= width;

            boolean lastComponent = !entrySetIter.hasNext();
            if (lastComponent && remaining > 0) {
                width += remaining;
            }
            componentResize.setWidth(componentToResize, width);
        }
    }
}