How to set the RowHeight dynamically in a JTable

后端 未结 5 1410
感动是毒
感动是毒 2020-12-09 20:39

I want to put a String in a JTable that is longer than the given cell-width. How can I set the rowHeight dynamically so that I can read the whole S

相关标签:
5条回答
  • 2020-12-09 21:05

    Many good ideas are linked in other answers and also in the related questions.

    So here is how I modified you classes to get a table with fully working auto-line-heights:

    import javax.swing.*;
    import javax.swing.event.*;
    import javax.swing.table.DefaultTableModel;
    import javax.swing.table.TableModel;
    import java.awt.*;
    
    public class ExampleTable implements TableModelListener {
        JTable textTable;
    
        public JPanel createTable() {
            JPanel totalGUI = new JPanel();
    
            //define titles for table
            String[] title = {"TITLE1", "TITLE2", "TITLE3"};
    
            //table data
            Object[][] playerdata = {
                    {new Integer(3), "Steve", "test test test"},
                    {new Integer(32), "Patrick", "du hu hu hu hu hu hu hu uh u kkkkkk oooo pppp"},
                    {new Integer(10), "Sarah", "xxxxxxxxxxxxx aaaaaaaaaa bbbbbbbbbbbb ffffdffffdffffdffffd xxxxxxx gggewr  eeeeeeeeee22 ffffd g fffffff zzzzzzz"},};
    
            //define tablemodel
            TableModel model = new DefaultTableModel(playerdata, title);
    
            //create object 'textTable'
            textTable = new JTable(model);
    
            //set column width
            textTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
            textTable.getColumnModel().getColumn(0).setPreferredWidth(17);
            textTable.getColumnModel().getColumn(1).setPreferredWidth(45);
            textTable.getColumnModel().getColumn(2).setPreferredWidth(200);
    
            //put line breaks if string is longer than cell-width
            RowHeightCellRenderer dynRow = new RowHeightCellRenderer();
            textTable.getColumnModel().getColumn(2).setCellRenderer(dynRow);
    
            // No more data changes; install listeners
            textTable.getModel().addTableModelListener(this);
            textTable.getColumnModel().addColumnModelListener(new TableColumnModelListener() {
                /**
                 * We only need to recalculate once; so track if we are already going to do it.
                 */
                boolean columnHeightWillBeCalculated = false;
    
                @Override
                public void columnAdded(TableColumnModelEvent e) {
                }
    
                @Override
                public void columnRemoved(TableColumnModelEvent e) {
                }
    
                @Override
                public void columnMoved(TableColumnModelEvent e) {
                }
    
                @Override
                public void columnMarginChanged(ChangeEvent e) {
                    if (!columnHeightWillBeCalculated && textTable.getTableHeader().getResizingColumn() != null) {
                        columnHeightWillBeCalculated = true;
                        SwingUtilities.invokeLater(new Runnable() {
                            @Override
                            public void run() {
                                // textTable.getTableHeader().getResizingColumn() is != null as long as the user still is holding the mouse down
                                // To avoid going over all data every few milliseconds wait for user to release
                                if (textTable.getTableHeader().getResizingColumn() != null) {
                                    SwingUtilities.invokeLater(this);
                                } else {
                                    tableChanged(null);
                                    columnHeightWillBeCalculated = false;
                                }
                            }
                        });
                    }
                }
    
                @Override
                public void columnSelectionChanged(ListSelectionEvent e) {
                }
            });
    
            //scrollbar
            JScrollPane scrollPane = new JScrollPane(textTable);
            totalGUI.add(scrollPane);
            return totalGUI;
        }
    
        public void tableChanged(TableModelEvent e) {
            final int first;
            final int last;
            if (e == null || e.getFirstRow() == TableModelEvent.HEADER_ROW) {
                // assume everything changed
                first = 0;
                last = textTable.getModel().getRowCount();
            } else {
                first = e.getFirstRow();
                last = e.getLastRow() + 1;
            }
            // GUI-Changes should be done through the EventDispatchThread which ensures all pending events were processed
            // Also this way nobody will change the text of our RowHeightCellRenderer because a cell is to be rendered
            if(SwingUtilities.isEventDispatchThread()) {
                updateRowHeights(first, last);
            } else {
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        updateRowHeights(first, last);
                    }
                });
            }
        }
    
        private void updateRowHeights(final int first, final int last) {
        /*
         * Auto adjust the height of rows in a JTable.
         * The only way to know the row height for sure is to render each cell
         * to determine the rendered height. After your table is populated with
         * data you can do:
         *
         */
            for (int row = first; row < last; row++) {
                int rowHeight = 20;
                for (int column = 0; column < textTable.getColumnCount(); column++) {
                    Component comp = textTable.prepareRenderer(textTable.getCellRenderer(row, column), row, column);
                    rowHeight = Math.max(rowHeight, comp.getPreferredSize().height);
                }
                if(rowHeight != textTable.getRowHeight(row)) {
                    textTable.setRowHeight(row, rowHeight);
                    System.out.println("neue Zeilenhöhe: "+rowHeight+" bei Zeile: "+row);
                }
            }
        }
    
        private static void createAndShowGUI() {
    
            //create main frame
            JFrame mainFrame = new JFrame("");
            mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            ExampleTable test = new ExampleTable();
            JPanel totalGUI = new JPanel();
            totalGUI = test.createTable();
    
            //visible mode
            mainFrame.add(totalGUI); //integrate main panel to main frame
            mainFrame.pack();
            mainFrame.setVisible(true);
    
            //adjust dynamically the Row height of each cell
            test.tableChanged(null);
        }
    
    
        public static void main (String[] args) {
            createAndShowGUI();
        }//main
    }
    

    Important changes:

    • calculate height only after JFrame is visible because we may not have the correct font before then
    • change rowHeight only from event-dispatch-thread (SwingUtilities.isEventDispatchThread()/SwingUtilities.invokeLater()); otherwise the table might assign different values to our RowHeightCellRenderer while we are in the middle of calculating height
    • initalize rowHeight to 20, so we can shrink again
    • TableColumnModelListener so we know when the user resizes a column and shrink or grow rowHeights (you can safely remove the Listener if this is undesired)

    Also now I only evaluate changed rows for TableModel changes

    import java.awt.*;
    import javax.swing.*;
    import javax.swing.table.*;
    import javax.swing.text.BadLocationException;
    
    public class RowHeightCellRenderer extends JTextArea implements TableCellRenderer {
    
        private static final long serialVersionUID = 1L;
    
    
        public RowHeightCellRenderer() {
            setLineWrap(true);
            setWrapStyleWord(true);
        }//constructor
    
        public Component getTableCellRendererComponent (JTable table,
                                                        Object value,
                                                        boolean isSelected,
                                                        boolean hasFocus,
                                                        int row,
                                                        int column ) {
            this.setText((String) value);
    
            if(isSelected) {
                this.setBackground(table.getSelectionBackground());
                this.setForeground(table.getSelectionForeground());
            } else {
                this.setBackground(table.getBackground());
                this.setForeground(table.getForeground());
            }
    
            final int columnWidth = table.getColumnModel().getColumn(column).getWidth();
            final int rowHeight = table.getRowHeight(row);
            this.setSize(columnWidth, rowHeight);
    
            this.validate();
            return this;
        }//getTableCellRendererComponent
    
        @Override
        public Dimension getPreferredSize() {
            try {
                // Get Rectangle for position after last text-character
                final Rectangle rectangle = this.modelToView(getDocument().getLength());
                if(rectangle != null) {
                    return new Dimension(this.getWidth(),
                                         this.getInsets().top + rectangle.y + rectangle.height +
                                                                      this.getInsets().bottom);
                }
            } catch (BadLocationException e) {
                e.printStackTrace();  // TODO: implement catch
            }
    
            return super.getPreferredSize();
        }
    }//RowHeightCellRenderer
    

    Changes:

    • setSize() in getTableCellRendererComponent() as otherwise the object is unable to wrap correctly
    • calculate preferedSize according to position of last character
    0 讨论(0)
  • 2020-12-09 21:08

    There are several issues when using a JTextArea as rendering component (and most if not all of them already explained in several QA's on this site). Trying to sum them up:

    Adjust individual row height to the size requirements of the rendering component

    Basically, the way to go is to loop through the cells as needed, then

    • configure its renderer with the data
    • ask the rendering component for its preferred size
    • set the table row height to the pref height

    The updateRowHeight method in the OP's edited question is just fine.

    JTextArea's calculation of its preferredSize

    to get a reasonable sizing hint for one dimension, it needs to be "seeded" with some reasonable size in the other dimension. That is if we want the height it needs a width, and that must be done in each call. In the context of a table, a reasonable width is the current column width:

    public Component getTableCellRendererComponent(JTable table,
            Object value, boolean isSelected, boolean hasFocus, int row,
            int column) {
        ... // configure visuals
        setText((String) value);
        setSize(table.getColumnModel().getColumn(column).getWidth(),
                Short.MAX_VALUE);
        return this;
    }// getTableCellRendererComponent
    

    Dynamic adjustment of the height

    The row height it fully determined in some steady state of the table/column/model. So you set it (call updateRowHeight) once after the initialization is completed and whenever any of the state it depends on is changed.

    // TableModelListener
    @Override
    public void tableChanged(TableModelEvent e) {
        updateRowHeights();
    }
    
    // TableColumnModelListener
    @Override
    public void columnMarginChanged(ChangeEvent e) {
        updateRowHeights();
    }
    

    Note

    As a general rule, all parameters in the getXXRendererComponent are strictly read-only, implementations must not change any state of the caller. Updating the rowHeight from within the renderer is wrong.

    0 讨论(0)
  • 2020-12-09 21:10

    Actually JTextArea already implements all the features required to dynamically change its height based on its width. One can see this functionality in action, when JTextArea is used inside a ScrollPane, where its height is automatically adjusted to fit the width of the ScrollPane. To use this feature, one has to first set the size of the JTextArea to some width and then JTextArea#getPreferredSize() will return the required height to display the text (if line wrap is set to true).

    So to dynamically change the row height of a JTable based on its width, one can do the following:

    1. Add a custom TableCellRenderer to the table that returns a JTextArea in TableCellRenderer#getTableCellRendererComponent()
    2. Listen to the resizing of the columns as explained here: Java JTable detect Column re-sized by user
    3. Update the row heights of the columns as explained here: Auto adjust the height of rows in a JTable
    4. During the update of the row heights, one calculates the required size of the JTextArea by first setting the new width and then getting the preferred height as shown in the following code snippet

    Update function of the row heights:

    public static void updateRowHeights(int column, int width, JTable table){
        for (int row = 0; row < table.getRowCount(); row++) {
            int rowHeight = table.getRowHeight();
            Component comp = table.prepareRenderer(table.getCellRenderer(row, column), row, column);
            Dimension d = comp.getPreferredSize();
                // first set the size to the new width
            comp.setSize(new Dimension(width, d.height));
                // then get the preferred size
            d = comp.getPreferredSize();
            rowHeight = Math.max(rowHeight, d.height);
                // finally set the height of the table
            table.setRowHeight(row, rowHeight);
        }
    }
    

    With this approach, no further calculations of colums or rows are necessary.

    Here is the complete code of the OP's ExampleTable, adjusted to this implementation.

    import java.awt.Component;
    import java.awt.Dimension;
    import java.awt.event.MouseAdapter;
    import java.awt.event.MouseEvent;
    
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.JScrollPane;
    import javax.swing.JTable;
    import javax.swing.JTextArea;
    import javax.swing.event.ChangeEvent;
    import javax.swing.event.ListSelectionEvent;
    import javax.swing.event.TableColumnModelEvent;
    import javax.swing.event.TableColumnModelListener;
    import javax.swing.table.AbstractTableModel;
    import javax.swing.table.JTableHeader;
    import javax.swing.table.TableCellRenderer;
    import javax.swing.table.TableColumn;
    
    public class ExampleTable {
    
        public class RowHeightCellRenderer extends JTextArea implements TableCellRenderer {
    
            public Component getTableCellRendererComponent(
                    JTable table, Object value,
                    boolean isSelected, boolean hasFocus,
                    int row, int column) {
    
                setEditable(false);
                setLineWrap(true);
                setWrapStyleWord(true);
    
                if (isSelected) {
                    setBackground(table.getSelectionBackground());
                    setForeground(table.getSelectionForeground());
                } else {
                    setBackground(table.getBackground());
                    setForeground(table.getForeground());
                }
    
                setText(value.toString());
                return this;
            }
        }
    
        public static void updateRowHeights(int column, int width, JTable table){
            for (int row = 0; row < table.getRowCount(); row++) {
                int rowHeight = table.getRowHeight();
                Component comp = table.prepareRenderer(table.getCellRenderer(row, column), row, column);
                Dimension d = comp.getPreferredSize();
                comp.setSize(new Dimension(width, d.height));
                d = comp.getPreferredSize();
                rowHeight = Math.max(rowHeight, d.height);
                table.setRowHeight(row, rowHeight);
            }
        }
    
        public JPanel createTable() {
    
            JPanel totalGUI = new JPanel();
    
            //define titles for table
            final String[] columnNames = {"TITLE1", "TITLE2", "TITLE3"};
    
            //table data
            final Object[][] rowData = {       
                    {new Integer(34), "Steve", "test test test"},
                    {new Integer(32), "Patrick", "dumdi dumdi dummdi dumm di di didumm"},
                    {new Integer(10), "Sarah", "blabla bla bla blabla bla bla blabla"},};
    
            AbstractTableModel model = new AbstractTableModel() {
                @Override
                public Class<?> getColumnClass(int columnIndex) {
                    return String.class;
                }
    
                public String getColumnName(int column) { return columnNames[column].toString(); }
                public int getRowCount() { return rowData.length; }
                public int getColumnCount() { return columnNames.length; }
                public Object getValueAt(int row, int col) { return rowData[row][col]; }
                public boolean isCellEditable(int row, int column) { return true; }
                public void setValueAt(Object value, int row, int col) {
                    rowData[row][col] = value;
                    fireTableCellUpdated(row, col);
                }
            };
    
            //create object 'textTable'
            final JTable textTable = new JTable();
    
            textTable.setDefaultRenderer(String.class, new RowHeightCellRenderer());
            textTable.setModel(model);
    
            //set column width
            textTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); 
            textTable.getColumnModel().getColumn(0).setPreferredWidth(60);
            textTable.getColumnModel().getColumn(1).setPreferredWidth(60);
    
            ColumnListener cl = new ColumnListener(){
    
                @Override
                public void columnMoved(int oldLocation, int newLocation) {
                }
    
                @Override
                public void columnResized(int column, int newWidth) {
                    updateRowHeights(column, newWidth, textTable);
                }
    
            };
    
            textTable.getColumnModel().addColumnModelListener(cl);
            textTable.getTableHeader().addMouseListener(cl);
    
            // initial update of row heights
            TableColumn c = textTable.getColumnModel().getColumn(2);
            updateRowHeights(2, c.getWidth(), textTable);
    
            //scrollbar
            JScrollPane scrollPane = new JScrollPane(textTable);
    
            totalGUI.add(scrollPane);               
            return totalGUI;
        }
    
        private static void createAndShowGUI() {
    
            //create main frame
            JFrame mainFrame = new JFrame("");
            mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            ExampleTable test = new ExampleTable();
    
            JPanel totalGUI = new JPanel();
            totalGUI = test.createTable();
    
            //visible mode
            mainFrame.add(totalGUI); //integrate main panel to main frame
            mainFrame.pack();
            mainFrame.setVisible(true);     
        }
    
    
        public static void main (String[] args) {               
    
            createAndShowGUI();     
    
        }//main
    
    
        abstract class ColumnListener extends MouseAdapter implements TableColumnModelListener {
    
            private int oldIndex = -1;
            private int newIndex = -1;
            private boolean dragging = false;
    
            private boolean resizing = false;
            private int resizingColumn = -1;
            private int oldWidth = -1;
    
            @Override
            public void mousePressed(MouseEvent e) {
                // capture start of resize
                if(e.getSource() instanceof JTableHeader) {
                    JTableHeader header = (JTableHeader)e.getSource();
                    TableColumn tc = header.getResizingColumn();
                    if(tc != null) {
                        resizing = true;
                        JTable table = header.getTable();
                        resizingColumn = table.convertColumnIndexToView( tc.getModelIndex());
                        oldWidth = tc.getPreferredWidth();
                    } else {
                        resizingColumn = -1;
                        oldWidth = -1;
                    }
                }   
            }
    
            @Override
            public void mouseReleased(MouseEvent e) {
                // column moved
                if(dragging && oldIndex != newIndex) {
                    columnMoved(oldIndex, newIndex);
                }
                dragging = false;
                oldIndex = -1;
                newIndex = -1;
    
                // column resized
                if(resizing) {
                    if(e.getSource() instanceof JTableHeader) {
                        JTableHeader header = (JTableHeader)e.getSource();
                        TableColumn tc = header.getColumnModel().getColumn(resizingColumn);
                        if(tc != null) {
                            int newWidth = tc.getPreferredWidth();
                            if(newWidth != oldWidth) {
                                columnResized(resizingColumn, newWidth);
                            }
                        }
                    }   
                }
                resizing = false;
                resizingColumn = -1;
                oldWidth = -1;
            }
    
            @Override
            public void columnAdded(TableColumnModelEvent e) {      
            }
    
            @Override
            public void columnRemoved(TableColumnModelEvent e) {        
            }
    
            @Override
            public void columnMoved(TableColumnModelEvent e) {
                // capture dragging
                dragging = true;
                if(oldIndex == -1){
                    oldIndex = e.getFromIndex();
                }
    
                newIndex = e.getToIndex();  
            }
    
            @Override
            public void columnMarginChanged(ChangeEvent e) {
            }
    
            @Override
            public void columnSelectionChanged(ListSelectionEvent e) {
            }
    
            public abstract void columnMoved(int oldLocation, int newLocation);
            public abstract void columnResized(int column, int newWidth);
        }
    }
    

    Note, that I reused and changed some of the code from Java JTable detect Column re-sized by user and Auto adjust the height of rows in a JTable.

    0 讨论(0)
  • 2020-12-09 21:16

    I find it simplest to adjust the row height from inside the component renderer, like this:

    public class RowHeightCellRenderer extends JTextArea implements TableCellRenderer
    {
        public Component getTableCellRendererComponent(JTable table, Object 
                value, boolean isSelected, boolean hasFocus,
                int row, int column) {
    
            setText(value.toString());
    
            // Set the component width to match the width of its table cell
            // and make the height arbitrarily large to accomodate all the contents
            setSize(table.getColumnModel().getColumn(column).getWidth(), Short.MAX_VALUE);
    
            // Now get the fitted height for the given width
            int rowHeight = this.getPreferredSize().height;
    
            // Get the current table row height
            int actualRowHeight = table.getRowHeight(row);
    
            // Set table row height to fitted height.
            // Important to check if this has been done already
            // to prevent a never-ending loop.
            if (rowHeight != actualRowHeight) {
               table.setRowHeight(row, rowHeight);
            }
    
            return this;
        }
    }
    

    This will also work for a renderer which returns a JTextPane.

    0 讨论(0)
  • 2020-12-09 21:24

    Normally we are setting the RowHeight when the text in the row goes beyond the height of a row, to set the height of a row we do this:

    table.setRowHeight(40);
    

    I want to put a String in a JTable that is longer than the given cell-width. How can I set the rowHeight dynamically so that I can read the whole String?

    To read complete string from the jtable row you don't need to set RowHeight you need to set the PreferredWidth of a column as you are already doing so. Just add a line like:

    textTable.getColumnModel().getColumn(2).setPreferredWidth(300);
    

    And you will be able to read the whole string.


    Output

    output

    0 讨论(0)
提交回复
热议问题