JTable Calls Custom Cell Renderer Method... Continuously
Asked Answered
R

3

5

Compilable source can be found at: http://www.splashcd.com/jtable.tar

I'm new to the language, so I'm not sure if this is acceptable behavior or not.

I created a JTable to display a row for each message received (it receives about one every 20 seconds). One of the table columns can contain a large amount of text, so I created a custom cell renderer which word wraps and sets the row height accordingly.

All that works as expected, except that once the table displays its first row, it calls the cell renderer about ten times a second... until the user closes the table.

Once I get approx 20 rows in there, the table gets fairly sluggish, taking 2-8 seconds to resize a column, scoll up or down, or render a selected row with the selected background color.

I inserted a print statement inside the renderer, so I can see how many times the getTableCellRendererComponent method is being called.

I disabled tool tips, and disabled all cell editing. I do have a listener that scrolls the view to the last row when either a new row is added or the table is resized.

Should the getTableCellRendererComponent method be called several times a second when I'm just viewing the screen (not touching mouse or keyboard)?

TIA

Rooke answered 12/10, 2012 at 18:26 Comment(2)
for better help sooner post an (short, runnable and compilable) SSCCE demonstrated your issue, basically I can't see there major complication(s), EDIT getTableCellRendererComponent are called internally, from TableModelEvent, mouse and keyboard events tooRoos
I'll upload a short, runnable and compilable tar file when I get home. No access to cloud services from behind my company's firewall.Rooke
R
10
  • aaaaach

  • you need doLayout(),

  • next level :-), then there you can to set Maximum visible rows for JTextComponents too, with little effort

enter image description here

doLayout()

import java.awt.*;
import javax.swing.*;
import javax.swing.table.*;
import javax.swing.text.*;
//http://tips4java.wordpress.com/2008/10/26/text-utilities/
public class AutoWrapTest {

    public JComponent makeUI() {
        String[] columnNames = {" Text Area Cell Renderer "};
        Object[][] data = {
            {"123456789012345678901234567890"},
            {"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddx"},
            {"----------------------------------------------0"},
            {">>>>>>>>>>>>>dddddddddddddddddddddddddddddddddddddddddddddddddd"
                + "dddddddxdddddddddddddddddddddddddddddddddddddddddddddd"
                + "dddddddddddx>>>>>>>>>>>>>>>>>>>>>>>>>|"},
            {">>>>>>>>>>>>ddddddddddddddddddddddddddddddddddddddddddddddddddd"
                + "ddddddx>>>>>>>>>>>>>>>>>>>>>>>>>>|"},
            {"a|"},
            {">>>>>>>>bbbb>>>>>>>>>>>>>>>>>>>|"},
            {">>>>>>>>>>>>>>>>>>|"},
            {">>>>>>>>>>>>>dddddddddddddddddddddddddddddddddddddddddddddddddd"
                + "dddddddxdddddddddddddd123456789012345678901234567890dddddd"
                + "dddddddddddddddddddddddddddddddddddddx>>>>>>>>>>>>>>>>>>>>"
                + ">>>>>|"},
            {">>>>>>>>>>>>>dddddddddddddd123456789012345678901234567890dddddd"
                + "dddddddddddddddddddddddddddddddddddddxdddddddddddddd123456"
                + "789012345678901234567890dddddddddddddddddddddddddddddddddd"
                + "ddddd123456789012345678901234567890ddddx>>>>>>>>>>>>>>>>>>"
                + ">>>>>>>|"},};
        TableModel model = new DefaultTableModel(data, columnNames) {

            private static final long serialVersionUID = 1L;

            @Override
            public boolean isCellEditable(int row, int column) {
                return false;
            }
        };
        JTable table = new JTable(model) {

            private static final long serialVersionUID = 1L;

            @Override
            public void doLayout() {
                TableColumn col = getColumnModel().getColumn(0);
                for (int row = 0; row < getRowCount(); row++) {
                    Component c = prepareRenderer(col.getCellRenderer(), row, 0);
                    if (c instanceof JTextArea) {
                        JTextArea a = (JTextArea) c;
                        int h = getPreferredHeight(a) + getIntercellSpacing().height;
                        if (getRowHeight(row) != h) {
                            setRowHeight(row, h);
                        }
                    }
                }
                super.doLayout();
            }

            private int getPreferredHeight(JTextComponent c) {
                Insets insets = c.getInsets();
                View view = c.getUI().getRootView(c).getView(0);
                int preferredHeight = (int) view.getPreferredSpan(View.Y_AXIS);
                return preferredHeight + insets.top + insets.bottom;
            }
        };
        table.setEnabled(false);
        table.setShowGrid(false);
        table.setTableHeader(null);
        table.getColumnModel().getColumn(0).setCellRenderer(new TextAreaCellRenderer());
        //table.setPreferredScrollableViewportSize(table.getPreferredSize());
        JScrollPane sp = new JScrollPane(table);
        sp.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
        sp.setPreferredSize(new Dimension(250, 533));
        JPanel p = new JPanel(new BorderLayout());
        p.add(sp);
        return p;
    }

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

            @Override
            public void run() {
                createAndShowGUI();
            }
        });
    }

    public static void createAndShowGUI() {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        f.getContentPane().add(new AutoWrapTest().makeUI());
        f.setLocation(100, 100);
        f.pack();
        f.setVisible(true);
    }
}

class TextAreaCellRenderer extends JTextArea implements TableCellRenderer {

    private static final long serialVersionUID = 1L;
    private final Color evenColor = new Color(230, 240, 255);

    public TextAreaCellRenderer() {
        super();
        setLineWrap(true);
        setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
    }

    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
        if (isSelected) {
            setForeground(table.getSelectionForeground());
            setBackground(table.getSelectionBackground());
        } else {
            setForeground(table.getForeground());
            setBackground(table.getBackground());
            setBackground((row % 2 == 0) ? evenColor : getBackground());
        }
        setFont(table.getFont());
        setText((value == null) ? "" : value.toString());
        return this;
    }
}
Roos answered 12/10, 2012 at 22:16 Comment(2)
please don't kill me my guard angel for this post here, its about doLayout() only ....Roos
why-o-why can't you refrain from setPrefSize :-) The row sizing in doLayout could well be done (though personally I wouldn't). Just a couple of comments: a) would call super first, then the col width is set b) would query the table (not the column) for the renderer - anyway, +1Stepson
M
3

To get the most performance out of a TableCellRenderer, make sure you're not creating a new instance of a component every time getTableCellRenderer is called. Make the components once and save them as fields of the class.

Also, you'll want to make sure each of the Components you use have the following methods overridden to do nothing:

  • validate
  • invalidate
  • revalidate
  • repaint
  • firePropertyChange

(and you probably want to hard code isOpaque).

For more information see: http://docs.oracle.com/javase/6/docs/api/javax/swing/tree/DefaultTreeCellRenderer.html

Metalline answered 12/10, 2012 at 18:51 Comment(1)
Thank you for the reply. After overriding each of the methods, I still didn't see a change, which my next post explains. However, I will still implement your suggestions.Rooke
R
1

The problem seems to stem from having JTable's setRowHeight() inside the custom cell renderer, as it calls the custom cell renderer, throwing it into an infinite loop.

I had to add in a check to see if the current row height matched the calculated word wrapped row height. If it did, I didnt try to setRowHeight() again.

Corrected Code:

import java.awt.Component;

import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.UIManager;
import javax.swing.table.TableCellRenderer;

//custom cell renderer for word wrapping, but if you use, you have to
//implement zebra striping functionality which the default renderer has
public class LineWrapCellRenderer extends JTextArea implements TableCellRenderer
{
    private int numOfTimesCalled;
    @Override
    public Component getTableCellRendererComponent(
            JTable table,
            Object value,
            boolean isSelected,
            boolean hasFocus,
            int row,
            int column)
    {
        System.out.println("Line Wrap Cell Renderer Called: " + numOfTimesCalled++);
        System.out.println("row:"+ row + ", col:" + column);
//set up the row size based on the number of newlines in the text in the cell
        int fontHeight = this.getFontMetrics(this.getFont()).getHeight();
        int numWraps = value.toString().split("\r\n|\r|\n").length;
        int rowHeight = fontHeight * numWraps;
//if the calculated rowHeight is the same as the row height of row,
// then don't call setRowHeight again, as doing so will throw us into
// an infinite loop
        if(rowHeight != table.getRowHeight(row))
        {
            table.setRowHeight(row, rowHeight);

//configure word wrapping
            setWrapStyleWord(true);
            setLineWrap(true);
//use the table's font
            setFont(table.getFont());
        }
//zebra striping, because whatever cell uses this renderer loses the
//default cell renderer zebra striping
        if(isSelected)
        {
            setBackground(table.getSelectionBackground());
        }
        else
        {
            if(row%2 == 1)
            {
                setBackground(UIManager.getColor("Table.alternateRowColor"));
            }
            else
            {
                setBackground(table.getBackground());
            }
        }
        this.setText(value.toString());
        return this;
    }
}
Rooke answered 12/10, 2012 at 22:6 Comment(2)
glad you found the issue - but the fix is still wrong: you must not change the state of the caller in the renderer ... Instead, do it somewhere else on a change that might effect the rowHeight, f.i. on change of content, column widths ... whateverStepson
I just wanted to clarify... You're saying not to call setRowHeight() from within LineWrapCellRenderer(), but to call it from events such as componentResized() ?Rooke

© 2022 - 2024 — McMap. All rights reserved.