4

I have a JTable where I want to highlight some rows using a custom background-color. This I have done with the following class:

public class MyTable extends JTable {

    private List<RefData> data = null;

    public List<RefData> getData() {
        return data;
    }

    public void setData(List<RefData> data) {
        this.data = data;
    }

    @Override
    public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
        Component comp = super.prepareRenderer(renderer, row, column);

        if (this.data == null || row < 0 || row > this.data.size()-1){
            return comp;
        }

        RefData rowData = this.data.get(row);
        if (rowData.getStatus() < 3000){
            comp.setBackground(Color.YELLOW);
        } else {
            comp.setBackground(Color.WHITE);
        }

        return comp;
    }

}

All this works like a charm, and I get exactly what I want. Next, while looking at the resulting GUI, I realise that the table looks way too condensed. Everything looks squished together. As always with the default JTable settings ;)

Well, that's easily solved I thought:

myTable.setIntercellSpacing(new java.awt.Dimension(10, 1));

Now, the cells are nicely spaced but, the added cell margins are now in the default table background color, which in my case is white. This looks butt-ugly.

I assume that the intercell spacing adds spacing between the cell border and the component returned by prepareRenderer. This would explain the result. But how can I get it to change the background of the cell itself?

Is my prepareRenderer solution is not suited for this task? Or is there another solution?

exhuma
  • 20,071
  • 12
  • 90
  • 123

3 Answers3

3

The Border approach is correct, but it is better to do it the cell renderer, not the prepareRenderer() method:

    JTable.setDefaultRenderer(Object.class, new DefaultCellRenderer() {
        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
            JComponent component = (JComponent) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
            component.setBorder(...);
            return component;
        }
    });
Denis Tulskiy
  • 19,012
  • 6
  • 50
  • 68
  • Thanks. But why? What are the advantages/drawbacks of `prepareRenderer` vs. `setDefaultRenderer`? Will one of the two speed up the UI (less calls)? – exhuma Nov 08 '10 at 10:42
  • @exhuma: no, both approaches are equally fast. it's just the renderer is responsible for, khm, rendering, it's the default way of changing the way cells are painted, and overriding the prepareRenderer() method look a bit like a hack. – Denis Tulskiy Nov 08 '10 at 10:52
  • Okay. I see. I used your approach to change the row colors as well, but have a problem. Some columns do not change the color. The data comes from a beans-binding. I am guessing that they are `int`s. As such the setDefaultRenderer's signature won't match. A strange phenomenon as I thought that the table-values were always objects... – exhuma Nov 08 '10 at 12:34
  • @exhuma: int should be auto-boxed to Integer, so renderer for Object should cover all columns. – Denis Tulskiy Nov 08 '10 at 12:45
  • JTable uses different renderer for different Object. For example, numbers (Integer, Double) are right aligned, String is left aligned and uses the default Object renderer. – camickr Nov 08 '10 at 16:07
  • @tulskiy, @camickr: This is what I thought as well. However, in my case this seems not to be the case. I am using Netbeans to create the bindings, an it correctly boxed the values as `Integers`. However, the background color is not set. – exhuma Nov 08 '10 at 16:31
  • @camickr: Yes, but if you set a renderer for Object, it should be used to render all types. – Denis Tulskiy Nov 08 '10 at 17:52
  • 1
    It uses the specific render for the specific class. If there is no default renderer for that class then it uses a renderer for the super class. For example there is no renderer for Float or Integer, so the Number renderer is used. There is no renderer for String so the Object renderer is used. The Object renderer is simply the default renderer if there is no other more specific renderer specified. The Date, Number, Boolean renderers are all installed by default which is why the Integer column uses the Number renderer. – camickr Nov 08 '10 at 19:02
  • 1
    As an edit to my above statement, this is only true when you override the getColumnClass() method of the TableModel. In this case the class of the column is used to determine the renderer or editor to be used by the table. If you do not override this method, then the default implementation always returns Object as the column class, in which case your statement that the Object renderer is always used would be true. However, this is not good way to use a TableModel, you should override this method to return the proper class. – camickr Nov 08 '10 at 19:11
3

Can someone please confirm that this is the correct way of doing it in Swing, please?

Overriding prepareRenderer() has the advantage of applying to all cells, and "the internal implementations always use this method to prepare renderers so that this default behavior can be safely overridden by a subclass." I don't see a problem, even if you later use setDefaultRenderer() for a particular class.

Addendum: Examples may be found in the article Table Row Rendering and this related answer.

Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
2

I now came up with the following solution. It seems OK to me. But can someone please confirm that this is the correct (or the right way) of doing it in wing please?

public class MyTable extends JTable {

    // [snip]

    private Border paddingBorder = BorderFactory.createEmptyBorder(2, 3, 2, 3);

    // [snip]

    @Override
    public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
        Component comp = super.prepareRenderer(renderer, row, column);

        if (JComponent.class.isInstance(comp)){
            ((JComponent)comp).setBorder(paddingBorder);
        }

        // [snip]
    }

    // [snip]

}
exhuma
  • 20,071
  • 12
  • 90
  • 123