0

I`ve got a mysterious problem with my custom JTable and a custom TableRenderer. In 95%-99,99% it works perfectly, but sometimes the renderer just stops doing his job, and leaves a portion of the table (which is inside a JScrollPane) blank.

The problem case looks like that: rednering has terminated to early

In all other cases, and after a slight resize of the window, the Table look like that: rendering has terminated successful

Now both columns has a TextAreaCellRenderer associated to, which works as follows:

public class TextAreaCellRenderer extends JTextArea implements TableCellRenderer {

    private final Color evenColor = new Color(252, 248, 202);


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


    @Override
    public Component getTableCellRendererComponent(final JTable table, final Object value, final boolean isSelected, final boolean hasFocus, final int row, final int column) {
        if (isSelected) {
            setForeground(table.getSelectionForeground());
            setBackground(table.getSelectionBackground());
        } else {
            setForeground(table.getForeground());
            setBackground(table.getBackground());
            setBackground((row % 2 == 0) ? evenColor : getBackground());
        }
        setWrapStyleWord(true);
        setFont(table.getFont());
        setText((value == null) ? "" : value.toString());
        return this;
    }
}

I also have to override the doLayout method of the JTable to be able to calculate the hight of a cell depending on the content. The custom table looks like that:

public class MediaMetaDataTable extends JTable {

    @Override
    public void doLayout() {
        TableColumn col = getColumnModel().getColumn(1);
        for (int row = 0; row < getRowCount(); row++) {
            Component c = prepareRenderer(col.getCellRenderer(), row, 1);
            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(final 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;
    }
}

The table is instantiated once with the following parameters:

metaTable = new MediaMetaDataTable();
metaTable.setModel(new MediaMetaDataTableModel());
metaTable.setEnabled(false);
metaTable.setShowGrid(false);
metaTable.setTableHeader(null);
metaTable.getColumnModel().getColumn(0).setCellRenderer(new TextAreaCellRenderer());
metaTable.getColumnModel().getColumn(1).setCellRenderer(new TextAreaCellRenderer());
metaTable.setPreferredScrollableViewportSize(new Dimension(-1, -1));
metaTable.setShowHorizontalLines(false);
metaTable.setShowVerticalLines(false);

Each time the data to show changes i update table by replacing the underlying models data:

List<MediaMetaData> metaInformation = mediaSearchHit.getMetaInformation();
        if (metaInformation != null) {
            ((MediaMetaDataTableModel) metaTable.getModel()).replaceMetaInfos(metaInformation);
        }

On update the model itself fires a table data changed event:

public class MediaMetaDataTableModel extends AbstractTableModel {

    private List<MediaMetaData> metaInfos = new LinkedList<MediaMetaData>();

    public static final int COL_INDEX_NAME = 0;
    public static final int COL_INDEX_VALUE = 1;


    public void replaceMetaInfos(final List<MediaMetaData> metaInfos) {
        this.metaInfos = null;
        this.metaInfos = metaInfos;
        fireTableDataChanged();
    }
...

Now does anybody has a idea, what causes the described rendering problem?

Thanks for any advices.

Macilias
  • 3,279
  • 2
  • 31
  • 43
  • 2
    For better help sooner, post an [SSCCE](http://sscce.org/). – Andrew Thompson Dec 02 '13 at 15:52
  • 2
    _Sometimes_ suggests incorrect synchronization. – trashgod Dec 02 '13 at 16:19
  • Now does anybody has a idea, what causes the described rendering problem? == SSCCE, short, runnable, compilable with hardcoded value for JTable, btw yesteday were asked&answered similair question – mKorbel Dec 02 '13 at 19:18
  • 2
    and create an local variable instead of extends JTextArea, doLayout is very familair, whats reason for important mistake setPreferredScrollableViewportSize(new Dimension(-1, -1));, start to search how to wrote AbstractTableModel if rest is the same as your code snipped you showed here – mKorbel Dec 02 '13 at 19:23

2 Answers2

3

I also have to override the doLayout method of the JTable to be able to calculate the hight of a cell depending on the content.

To achieve this goal there's no need to override doLayout() method. I think the simplest way to do this is by adding the text area used to render the cell content into a JPanel with BorderLayout and set the row height based on the panel's preferred size. This way the layout manager will do the trick for you and all the cell's content will be visible:

@Override
public Component getTableCellRendererComponent(...) {
    ...
    JPanel contentPane = new JPanel(new BorderLayout());            
    contentPane.add(this);
    table.setRowHeight(row, contentPane.getPreferredSize().height); // sets row's height
    return contentPane;
}

As @mKorbel pointed out, there's no need to make the renderer extend from JTextArea: a single variable will work. Keeping this in mind take a look to this implementation based on your work:

class TextAreaRenderer implements TableCellRenderer {

    private JTextArea renderer;
    private final Color evenColor = new Color(252, 248, 202);

    public TextAreaRenderer() {
        renderer = new JTextArea();            
        renderer.setLineWrap(true);
        renderer.setWrapStyleWord(true);
        renderer.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) {
            renderer.setForeground(table.getSelectionForeground());
            renderer.setBackground(table.getSelectionBackground());
        } else {
            renderer.setForeground(table.getForeground());
            renderer.setBackground((row % 2 == 0) ? evenColor : table.getBackground());
        }            
        renderer.setFont(table.getFont());
        renderer.setText((value == null) ? "" : value.toString());
        JPanel contentPane = new JPanel(new BorderLayout());            
        contentPane.add(renderer);
        table.setRowHeight(row, contentPane.getPreferredSize().height); // sets row's height
        return contentPane;
    }

}

Screenshot

enter image description here

dic19
  • 17,821
  • 6
  • 40
  • 69
  • Thanks for your time and afford Delcio! Your solution woks fine. The only issue is that the effect sometimes accrues when iterating quickly the result set, but it does not stay for long, there seam to be a refresh more which do the trick. The reason probably has to do with some sort of event handling behind the scenes, which update the results. Thats also the reason why providing a SSCCE would be very hard in this case. Anyway, your solution is a great help! THX! – Macilias Jan 08 '14 at 16:01
  • @Macilias you're welcome! I'm glad it helped you :) As you say the reason for this behavior may has something to do with event handling. Just be sure to be adding the rows to the model in the Event Dispatching Thread, for instance by using SwingWorker as exemplified [here](http://stackoverflow.com/questions/20361363/java-select-from-combobox-and-delete-the-row/20363649#20363649). The example is about adding items to a JComboBox but exactly the same applies to JTable (or any swing component). – dic19 Jan 08 '14 at 21:39
1

If I had to guess I would say it could be a concurency problem. Are you doing everything in the GUI-Thread? If yes, it can't be a concurency problem. Otherwhise try to call everything with Thread.InvokeLater() in an inital debug step, if you don't encounter the error anymore after a long time of testing, you know the cause of the problem.

In a second step you would then check exactly where it is necessary to make the calls with invokelater() and where not (because you shouldn't do that all the time, because it leads to very poor performance.

As I said, just a wild guess... It can of youre just be another bug. Are you using Java7? There are millions of Bugs in Swing with java 7 code (just all the code that from oracle came).

Colin
  • 101
  • 8