Wrapping Line, Right Align,Auto Resize Row Height In JTable

拜拜、爱过 提交于 2019-12-01 13:01:41

For what is worth here is some old code I had lying around.

It uses a JTextArea with wrapping but limits the lines in the text area to 2 lines. You can then scroll the text area to see the other lines.

Maybe this type of approach will help with your requirement?

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

public class TableTextArea extends JFrame
{
    public TableTextArea()
    {
        JTable table = new JTable(40, 5);
        table.setPreferredScrollableViewportSize(table.getPreferredSize());
        table.setRowHeight(40);
        table.setValueAt("one two three four five six seven eight nine ten", 0, 2);
        table.setValueAt("aaa bbb ccccc dddd eeee fff ggggg hhhhh iiii jjj", 1, 2);
        table.setValueAt("1111 2222 3333 4444 5555 6666 7777 8888 9999 0000", 0, 4);
        table.setValueAt("one two three four five six seven eight nine ten", 2, 2);
        table.setValueAt("aaa bbb ccccc dddd eeee fff ggggg hhhhh iiii jjj", 3, 2);
        table.setValueAt("1111 2222 3333 4444 5555 6666 7777 8888 9999 0000", 4, 4);
        table.setValueAt("one two three four five six seven eight nine ten", 5, 2);
        table.setValueAt("aaa bbb ccccc dddd eeee fff ggggg hhhhh iiii jjj", 6, 2);
        table.setValueAt("1111 2222 3333 4444 5555 6666 7777 8888 9999 0000", 7, 4);
        table.setValueAt("one two three four five six seven eight nine ten", 8, 2);
        table.setValueAt("aaa bbb ccccc dddd eeee fff ggggg hhhhh iiii jjj", 9, 2);
        table.setValueAt("1111 2222 3333 4444 5555 6666 7777 8888 9999 0000", 10, 4);
        table.setValueAt("one two three four five six seven eight nine ten", 11, 2);
        table.setValueAt("aaa bbb ccccc dddd eeee fff ggggg hhhhh iiii jjj", 12, 2);
        table.setValueAt("1111 2222 3333 4444 5555 6666 7777 8888 9999 0000", 13, 4);
        table.setValueAt("one two three four five six seven eight nine ten", 14, 2);
        table.setValueAt("aaa bbb ccccc dddd eeee fff ggggg hhhhh iiii jjj", 15, 2);
        table.setValueAt("1111 2222 3333 4444 5555 6666 7777 8888 9999 0000", 16, 4);
        table.setValueAt("one two three four five six seven eight nine ten", 17, 2);
        table.setValueAt("aaa bbb ccccc dddd eeee fff ggggg hhhhh iiii jjj", 18, 2);
        table.setValueAt("1111 2222 3333 4444 5555 6666 7777 8888 9999 0000", 19, 4);
        table.setValueAt("one two three four five six seven eight nine ten", 20, 2);
        table.setValueAt("aaa bbb ccccc dddd eeee fff ggggg hhhhh iiii jjj", 21, 2);
        table.setValueAt("1111 2222 3333 4444 5555 6666 7777 8888 9999 0000", 22, 4);
        table.setValueAt("one two three four five six seven eight nine ten", 23, 2);
        table.setValueAt("aaa bbb ccccc dddd eeee fff ggggg hhhhh iiii jjj", 24, 2);
        table.setValueAt("1111 2222 3333 4444 5555 6666 7777 8888 9999 0000", 25, 4);
        table.setValueAt("one two three four five six seven eight nine ten", 26, 2);
        table.setValueAt("aaa bbb ccccc dddd eeee fff ggggg hhhhh iiii jjj", 27, 2);
        table.setValueAt("1111 2222 3333 4444 5555 6666 7777 8888 9999 0000", 28, 4);
        JScrollPane scrollPane = new JScrollPane( table );
        add( scrollPane );

        //  Override default renderer for a specific column

        TableCellRenderer renderer = new TextAreaRenderer();
        table.getColumnModel().getColumn(2).setCellRenderer( renderer );
        table.getColumnModel().getColumn(4).setCellRenderer( renderer );
        table.changeSelection(0, 0, false, false);
    }

    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            public void run()
            {
                TableTextArea frame = new TableTextArea();
                frame.setDefaultCloseOperation( EXIT_ON_CLOSE );
                frame.pack();
                frame.setLocationRelativeTo( null );
                frame.setVisible(true);
            }
        });
    }

    /*
    **
    */
    class TextAreaRenderer implements TableCellRenderer
    {
        private JTextArea renderTextArea;
        private JScrollPane renderScrollPane;
        private JTextArea focusTextArea;
        private JScrollPane focusScrollPane;
        private boolean firstTime = true;

        public TextAreaRenderer()
        {
            renderTextArea = new JTextArea();
            renderTextArea.setEditable( false );
            renderTextArea.setLineWrap( true );
            renderTextArea.setWrapStyleWord( true );
            renderScrollPane = new JScrollPane( renderTextArea );
            renderScrollPane.setBorder(null);
            renderScrollPane.revalidate();

            focusTextArea = new JTextArea();
            focusTextArea.setEditable( false );
            focusTextArea.setLineWrap( true );
            focusTextArea.setWrapStyleWord( true );
            focusScrollPane = new JScrollPane( focusTextArea );
            focusScrollPane.setBorder(null);
        }

        public Component getTableCellRendererComponent(
            final JTable table, Object value, boolean isSelected, boolean hasFocus, final int row, final int column)
        {
            //  For some reason the scrollbars don't appear on the first cell renderered.
            //  Forcing a repaint of the cell seems to fix the problem.

            if (firstTime)
            {
                firstTime = false;
                Rectangle cellRectangle = table.getCellRect(row, column, false);
//              renderScrollPane.setBounds(cellRectangle);
//              table.add(renderScrollPane);
//              renderScrollPane.revalidate();
                table.repaint(cellRectangle);
            }

            System.out.println(row + " :: " + column + " : " + hasFocus);
            table.remove(focusScrollPane);

            renderTextArea.setText( value != null ? value.toString() : "" );
            renderTextArea.setCaretPosition(0);

            if (hasFocus)
            {
                renderTextArea.setBackground( table.getSelectionBackground() );

                SwingUtilities.invokeLater( new Runnable()
                {
                    public void run()
                    {
                        addRealTextAreaToTable(table, row, column);
                    }
                });
            }
            else if (isSelected)
                renderTextArea.setBackground( table.getSelectionBackground() );
            else
                renderTextArea.setBackground( table.getBackground() );

            return renderScrollPane;
        }

        private void addRealTextAreaToTable(JTable table, int row, int column)
        {
            System.out.println(row + " :: " + column);
            Object value = table.getValueAt(row, column);
            focusTextArea.setText( value != null ? value.toString() : "" );
            focusTextArea.setCaretPosition(0);
//          focusTextArea.setBackground( table.getBackground() );
            focusTextArea.setBackground( table.getSelectionBackground() );
            Rectangle cellRectangle = table.getCellRect(row, column, false);
            focusScrollPane.setBounds(cellRectangle);
            table.add(focusScrollPane);
            focusScrollPane.revalidate();
            focusTextArea.requestFocusInWindow();
        }
    }
}

So, I've spent a bit of time banging my head against this trying a verity of different things.

Starting with TextAreaRenderer. I'd prefer to use the DefaultTableCelllRenderer, but after playing around with that, I wasn't able to get it to work satisfactorily. So, instead, I took most of it's own optimisations and applied them to the TextAreaRenderer itself.

One of things you really want to do is reduce the amount changes that each getTableCellRendererComponent will make. JTextArea is already a complex component.

I removed the setOpaque call and moved the setFont call to the constructor. I also added selection support (it was part of my testing)

public class TextAreaRenderer extends JTextArea
        implements TableCellRenderer {

    public TextAreaRenderer() {
        setLineWrap(true);
        setWrapStyleWord(true);
        setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);

        setEditable(false);
        setCursor(null);
        //setOpaque(false);
        setFocusable(false);
        setLineWrap(true);
        setWrapStyleWord(true);
        setFont(new Font("bnazanin", Font.BOLD, 15));

        getCaret().setBlinkRate(0);
    }

    public Component getTableCellRendererComponent(JTable jTable,
            Object obj, boolean isSelected, boolean hasFocus, int row,
            int column) {
        setText((String) obj);

        if (isSelected) {
            System.out.println("!!Selected");
            setBackground(jTable.getSelectionBackground());
            setForeground(jTable.getSelectionForeground());
        } else {
            setBackground(jTable.getBackground());
            setForeground(jTable.getForeground());
        }

        return this;
    }

    /**
     * Overridden for performance reasons. See the
     * <a href="#override">Implementation Note</a>
     * for more information.
     */
    public boolean isOpaque() {
        Color back = getBackground();
        Component p = getParent();
        if (p != null) {
            p = p.getParent();
        }

        // p should now be the JTable.
        boolean colorMatch = (back != null) && (p != null)
                && back.equals(p.getBackground())
                && p.isOpaque();
        return !colorMatch && super.isOpaque();
    }

    /**
     * Overridden for performance reasons. See the
     * <a href="#override">Implementation Note</a>
     * for more information.
     *
     * @since 1.5
     */
    public void invalidate() {
    }

    /**
     * Overridden for performance reasons. See the
     * <a href="#override">Implementation Note</a>
     * for more information.
     */
    public void validate() {
    }

    /**
     * Overridden for performance reasons. See the
     * <a href="#override">Implementation Note</a>
     * for more information.
     */
    public void revalidate() {
    }

    /**
     * Overridden for performance reasons. See the
     * <a href="#override">Implementation Note</a>
     * for more information.
     */
    public void repaint(long tm, int x, int y, int width, int height) {
    }

    /**
     * Overridden for performance reasons. See the
     * <a href="#override">Implementation Note</a>
     * for more information.
     */
    public void repaint(Rectangle r) {
    }

    /**
     * Overridden for performance reasons. See the
     * <a href="#override">Implementation Note</a>
     * for more information.
     *
     * @since 1.5
     */
    public void repaint() {
    }

    /**
     * Overridden for performance reasons. See the
     * <a href="#override">Implementation Note</a>
     * for more information.
     */
    public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {
    }

}

I then attacked the BlankTable...

One of the big changes is the table is now capable of calculating the height of the rows dynamically. Because the calculation is expensive, the result of this operation is cached. The problem, though, is the JTable also has it's own internal cache ... which is private 🤬 ... so rather then tracking down the reflection route, we need to provide some compensation to circumvent some of the side effects this API creates.

When calling setRowHeight, the JTable call invalidate and repaint itself. When we're performing our calculations we'd like to stop this, but there are some circumstances under which we should still allow to work (such as when the columns are resized and the JTable is invalidated)

public class BankTable extends JTable {

    public BankTable(String[][] data, String[] columns) {
        super(data, columns);

        changeTableHeader();

        setGridColor(Color.RED);

        TableCellRenderer textAreaRenderer = new TextAreaRenderer();

        for (int i = 0; i < columns.length; i++) {
            getColumnModel().getColumn(i).setCellRenderer(textAreaRenderer);
        }
    }

    public void changeTableHeader() {
        getTableHeader().setBackground(new Color(57, 77, 112));
        getTableHeader().setForeground(Color.WHITE);
        getTableHeader().setFont(new Font("Calibri Light", Font.BOLD, 20));

    }

    @Override
    public boolean isCellEditable(int i, int i1) {
        return false;
    }

    private Map<Integer, Integer> rowMap = new HashMap<>();
    private boolean quietUpdate = false;
    private boolean forceUpdate = false;

    @Override
    public void invalidate() {
        rowMap.clear();
        forceUpdate = true;
        super.invalidate();
        forceUpdate = false;
    }

    @Override
    protected void resizeAndRepaint() {
        if (quietUpdate) {
            if (forceUpdate) {
                super.resizeAndRepaint();
            }
        } else {
            super.resizeAndRepaint();
        }
    }

    @Override
    public int getRowHeight(int row) {
        Integer rowHeight = rowMap.get(row);
        if (rowHeight == null) {
            TableColumnModel model = getColumnModel();
            rowHeight = getRowHeight();
            for (int column = 0; column < getColumnCount(); column++) {
                int colWidth = model.getColumn(column).getWidth();
                Component comp = prepareRenderer(getCellRenderer(row, column), row, column);
                comp.setSize(new Dimension(colWidth, Integer.MAX_VALUE));
                rowHeight = Math.max(rowHeight, comp.getPreferredSize().height);
            }
            rowMap.put(row, rowHeight);
            quietUpdate = true;
            setRowHeight(row, rowHeight);
            quietUpdate = false;
        }
        return rowHeight;
    }

    @Override
    public void tableChanged(TableModelEvent e) {
        if (rowMap == null) {
            super.tableChanged(e);
            return;
        }
        if (e == null || e.getFirstRow() == TableModelEvent.HEADER_ROW) {
            rowMap.clear();
            super.tableChanged(e);
            return;
        }

        if (e.getType() == TableModelEvent.INSERT) {
            super.tableChanged(e);
            return;
        }

        int modelColumn = e.getColumn();
        int start = e.getFirstRow();
        int end = e.getLastRow();

        if (e.getType() == TableModelEvent.DELETE || e.getType() == TableModelEvent.UPDATE) {
            for (int row = start; row <= end; row++) {
                rowMap.remove(row);
            }
            super.tableChanged(e);
            return;
        }

        if (end == Integer.MAX_VALUE) {
            rowMap.clear();
        }
        super.tableChanged(e); //To change body of generated methods, choose Tools | Templates.
    }
}

There are still a few other areas which might need further optimising (I've not tested a sorted table, so tableChanged which need to be updated and I've not tested for changes to a cell either)

After the initial load, I've found the rendering to be reasonable. Because of the component in use, the font and other system issues, you're milage may fair.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!