Component.getWidth() returns 0 in different context - java

I have a custom TableCellRenderer for which I want to display a different tooltip depending on where in the cell the mouse is positioned. The issue I'm running into is that getWidth() is always returning 0 when called from getToolTipText.
Here's an SSCCE:
import javax.swing.*;
import javax.swing.table.TableCellRenderer;
import java.awt.*;
import java.awt.event.MouseEvent;
public class Sandbox {
public static void main(String[] args) {
JFrame testFrame = new JFrame("Test");
testFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
testFrame.setLayout(new BorderLayout());
JTable testTable = new JTable(new Object[][]{{"Value 1", null}}, new Object[] {"Column 1", "Column 2"});
testTable.getColumnModel().getColumn(1).setCellRenderer(new CustomCellRenderer());
testFrame.add(new JScrollPane(testTable), BorderLayout.CENTER);
testFrame.pack();
testFrame.setLocationRelativeTo(null);
testFrame.setVisible(true);
}
private static class CustomCellRenderer implements TableCellRenderer {
private final JLabel renderer = new JLabel()
{
#Override
protected void paintComponent(Graphics g) {
g.setColor(Color.RED);
g.fillRect(0, 0, getWidth(), getHeight());
System.out.println("Width from paintComponent = " + getWidth());
}
#Override
public String getToolTipText(MouseEvent event) {
System.out.println("Width from getToolTipText = " + getWidth());
return super.getToolTipText(event);
}
};
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column)
{
return renderer;
}
}
}
You can see that the component has been sized and the correct result is printed from the paintComponent method. However, when you hover your mouse over the cell in "Column 2", the getToolTipText method doesn't print the same value.
I found similar questions asked before, but the answer is generally that the Component hasn't been sized yet. In my case, the component has clearly been sized. Can someone please explain why getWidth() returns 0 in the getToolTipText method? Is there a better way to do this?

Override the getToolTipText() method of the JTable:
JTable testTable = new JTable(new Object[][]{{"Value 1", null}}, new Object[] {"Column 1", "Column 2"})
{
#Override
public String getToolTipText(MouseEvent event)
{
JTable table = (JTable)event.getSource();
int column = table.columnAtPoint( event.getPoint() );
if (column == 1)
{
int row = table.rowAtPoint( event.getPoint() );
Rectangle r = table.getCellRect(row, column, false);
Point p = new Point(event.getX() - r.x, event.getY() - r.y);
// System.out.println("Width from getToolTipText = " + r.width);
return p.toString();
}
return super.getToolTipText(event);
}
};
If you want to set the tool tip in the renderer, the code should be in the getTableCellRenderComponent() method. In this case you would then need to use the MouseInfo class to get the current mouse location and convert the location to be relative to the table cell. Something like:
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column)
{
Point pointInTable = MouseInfo.getPointerInfo().getLocation();
SwingUtilities.convertPointFromScreen(pointInTable, table);
Rectangle r = table.getCellRect(row, column, false);
Point p = new Point(pointInTable.x - r.x, pointInTable.y - r.y);
table.setToolTipText(p.toString());
return renderer;
}

You are seeing the result of getWidth() from the same JLabel in two different contexts:
In paintComponent(), the JLabel that has been added to a CellRendererPane which invokes validate() to calculate the width.
In getToolTipText(), the label accessed by the table's ToolTipManager has not been validated.
There's an example of using CellRendererPane here.

Related

Is there a way to make every item of a JList have different widths?

I'm trying to display Strings in rows and columns. Since they differ in length and represent parts of a continuous text, I would like each component to only take up the size it needs to show the whole String.
So far, I managed to load the individual items into the list, limit the number of rows and make them wrap horizontally. I now wrote a custom ListCellRenderer as I figured this could solve my problem but it doesn't work the way I had hoped. The two Strings that are part of each item are neither centered nor displayed above one another (in fact, they are not readable at all because they seem to be drawn outside the cell) and they all have the same size. What am I doing wrong?
This is what my Renderer looks like:
public class ElementRenderer extends JPanel implements ListCellRenderer<Element> {
private Element element;
#Override
public Component getListCellRendererComponent(JList<? extends Element> list, Element value, int index,
boolean isSelected, boolean cellHasFocus) {
element = (Element) value;
if (element.getTag().equals("N/A"))
setEnabled(false);
else
setEnabled(true);
if (isSelected)
setBorder(BorderFactory.createLoweredSoftBevelBorder());
else
setBorder(BorderFactory.createRaisedSoftBevelBorder());
return this;
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (!isEnabled())
setBackground(Color.GRAY);
String word = element.getWord();
String tag = element.getTag();
FontMetrics fm = getFontMetrics(DataManager.getCurrentFont());
Rectangle bounds = getVisibleRect();
int y = bounds.y + ((bounds.height - 2*fm.getHeight())/2) + fm.getAscent();
g.drawString(word, bounds.x + (bounds.width - fm.stringWidth(word))/2, y);
g.drawString(tag, bounds.x + (bounds.width - fm.stringWidth(tag))/2, y + fm.getHeight());
}
#Override
public Dimension getPreferredSize() {
FontMetrics fm = getFontMetrics(DataManager.getCurrentFont());
int wordwidth = fm.stringWidth(element.getWord());
int tagwidth = fm.stringWidth(element.getTag());
if (tagwidth > wordwidth)
wordwidth = tagwidth;
return new Dimension(17+wordwidth, 3+fm.getHeight()*2);
}
Well the simplest solution I could find without messing with the JList's LayoutManager, nor its ListUI, is indeed to create a custom ListCellRenderer, but with the following logic:
The custom ListCellRenderer is a JPanel with a DefaultListCellRenderer in it.
The custom ListCellRenderer, as a JPanel, will fill the entire cell space.
The DefaultListCellRenderer will be centered inside our custom ListCellRenderer. We can achieve this by setting the LayoutManager of the JPanel to GridBagLayout. Since there will be added only a single component in it (ie the DefaultListCellRenderer which is a JLabel), it will be centered.
Follows sample code:
import java.awt.Component;
import java.awt.GridBagLayout;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.ListCellRenderer;
public class Main {
public static class DynamicWidthListCellRenderer extends JPanel implements ListCellRenderer {
private final DefaultListCellRenderer delegate;
public DynamicWidthListCellRenderer() {
super(new GridBagLayout());
delegate = new DefaultListCellRenderer();
super.add(delegate);
}
#Override
public Component getListCellRendererComponent(final JList list,
final Object value,
final int index,
final boolean isSelected,
final boolean cellHasFocus) {
delegate.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
//super.setBackground(delegate.getBackground()); //Test this out also...
return this;
}
}
public static void main(final String[] args) {
final JList<String> list = new JList<>(new String[]{"abc", "abcdefghijklmnop", "abcdefg", "a"});
list.setCellRenderer(new DynamicWidthListCellRenderer());
final JFrame frame = new JFrame("List variable width cells");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(list);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
And sample screenshot (with a selected element):

Change the font color in a specific cell of a JTable?

Before starting, I've viewed a handful of solutions as well as documentation. I can't seem to figure out why my code isn't working the way I believe it should work. I've extended DefaultTableCellRenderer but I don't believe it is being applied - that or I messed things up somewhere.
Here are the threads / websites I've looked into before posting this question:
Swing - Is it possible to set the font color of 'specific' text within a JTable cell?
JTable Cell Renderer
http://docs.oracle.com/javase/tutorial/uiswing/components/table.html
I realize the first link uses HTML to change the font color, but I would think the way I went about it should produce the same result.
To make it easier on those who want to help me figure out the issues, I've created an SSCCE.
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
public class TableTest {
private static final int IMPORTANT_COLUMN = 2;
public static void createAndShowGUI() {
Object[][] data = new Object[2][4];
//create sample data
String[] realRowData = { "1", "One", "1.0.2", "compile" };
String[] fakeRowData = { "2", "Two", "1.3.2-FAKE", "compile" };
//populate sample data
for(int i = 0; i < realRowData.length; i++) {
data[0][i] = realRowData[i];
data[1][i] = fakeRowData[i];
}
//set up tableModel
JTable table = new JTable();
table.setModel(new DefaultTableModel(data,
new String[] { "ID #", "Group #", "version", "Action" })
{
Class[] types = new Class[] {
Integer.class, String.class, String.class, String.class
};
boolean[] editable = new boolean[] {
false, false, true, false
};
#Override
public Class getColumnClass(int columnIndex) {
return types[columnIndex];
}
#Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return editable[columnIndex];
}
});
//set custom renderer on table
table.setDefaultRenderer(String.class, new CustomTableRenderer());
//create frame to place table
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setMinimumSize(new Dimension(400, 400));
JScrollPane scrollPane = new JScrollPane();
scrollPane.setViewportView(table);
f.add(scrollPane);
f.pack();
f.setVisible(true);
}
//MAIN
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
//Custom DefaultTableCellRenderer
public static class CustomTableRenderer extends DefaultTableCellRenderer {
public Component getTableCellRenderer(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column)
{
Component c = super.getTableCellRendererComponent(table, value, isSelected,
hasFocus, row, column);
String versionVal = table.getValueAt(row, IMPORTANT_COLUMN).toString();
if(versionVal.contains("FAKE")) {
//set to red bold font
c.setForeground(Color.RED);
c.setFont(new Font("Dialog", Font.BOLD, 12));
} else {
//stay at default
c.setForeground(Color.BLACK);
c.setFont(new Font("Dialog", Font.PLAIN, 12));
}
return c;
}
}
}
My goal is to highlight any value in the version column that contains the word FAKE in a red bold text.
I've extended DefaultTableCellRenderer but I don't believe it is being applied
Some simple debugging tips:
Add a simple System.out.println(...) to the method you think should be invoked
When overriding a method, make sure you use the #Override annotation (you used it in the TableModel class, but not your renderer class).
Your problem is a typing mistake because you are not overriding the proper method:
#Override
// public Component getTableCellRenderer(...) // this is wrong
public Component getTableCellRendererComponent(...)
The override annotation will display a compile message. Try it before changing the code.
Also, your first column is NOT an Integer class. Just because it contains String representations of an Integer does not make it an Integer. You need to add an Integer object to the model.
Replace your custom table cell rendere with the below.
Explanations are in comments. Basically, you should override getTableCellRendererComponent then check for correct column (there may be other methods instead of checking header value), then set cell depending on color.
Do not forget last else block to set color to default if it is not the column you want.
//Custom DefaultTableCellRenderer
public static class CustomTableRenderer extends DefaultTableCellRenderer {
// You should override getTableCellRendererComponent
#Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
Component c = super.getTableCellRendererComponent(table, value, isSelected,
hasFocus, row, column);
// Check the column name, if it is "version"
if (table.getColumnName(column).compareToIgnoreCase("version") == 0) {
// You know version column includes string
String versionVal = (String) value;
if (versionVal.contains("FAKE")) {
//set to red bold font
c.setForeground(Color.RED);
c.setFont(new Font("Dialog", Font.BOLD, 12));
} else {
//stay at default
c.setForeground(Color.BLACK);
c.setFont(new Font("Dialog", Font.PLAIN, 12));
}
} else {
// Here you should also stay at default
//stay at default
c.setForeground(Color.BLACK);
c.setFont(new Font("Dialog", Font.PLAIN, 12));
}
return c;
}
}

Usability features for JComboBox within JTable

I'm trying to build a table which includes JComboBoxes as both the renderer and editor components. This mostly works fine, however there are two things I can't seem to solve.
Tabbing between cells should make the JComboBox active
Clicking the drop-down arrow should immediately open the option list
Regarding 1, the editable combo should place focus within the embedded text field, the fixed combo should allow the down arrow to open the list of options.
Regarding 2, I find that this sometimes works depending on what other cell is currently active, but other times I have to double click. I cannot make this behaviour consistent.
For convenience I have included a clear example which (I believe) uses the recommended approach for embedding JComboBoxes within Jtables.
Thank you for constructive advice.
import java.awt.Component;
import java.util.*;
import javax.swing.*;
import javax.swing.table.*;
public class TableCombos
{
public static void main(String[] args)
{
JFrame frame = new JFrame("Frame");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
AbstractTableModel model = new DefaultTableModel()
{
String[] columnHeaders = new String[]{"label", "combo-edit", "combo-fixed"};
Class<?>[] columnClasses = new Class<?>[]{String.class, Double.class, Double.class};
List<Object[]> data = new ArrayList<>();
{
data.add(new Object[]{"row 1", 1.0d, 2.0d});
data.add(new Object[]{"row 2", 2.0d, 3.0d});
}
#Override
public int getColumnCount()
{
return columnHeaders.length;
}
#Override
public boolean isCellEditable(int row, int column)
{
return column != 0;
}
#Override
public int getRowCount()
{
if (data == null) // race condition
return 0;
return data.size();
}
#Override
public Object getValueAt(int row, int column)
{
return data.get(row)[column];
}
#Override
public void setValueAt(Object aValue, int row, int column)
{
data.get(row)[column] = aValue;
}
#Override
public Class<?> getColumnClass(int column)
{
return columnClasses[column];
}
#Override
public String getColumnName(int column)
{
return columnHeaders[column];
}
};
JTable table = new JTable(model);
table.setSurrendersFocusOnKeystroke(true);
TableColumn c1 = table.getColumnModel().getColumn(1);
TableColumn c2 = table.getColumnModel().getColumn(2);
JComboBox<Double> editorComboEditable = new JComboBox<>(new Double[]{1.0d, 2.0d, 3.0d});
editorComboEditable.setEditable(true);
c1.setCellEditor(new DefaultCellEditor(editorComboEditable));
c2.setCellEditor(new DefaultCellEditor(new JComboBox<>(new Double[]{1.0d, 2.0d, 3.0d})));
final JComboBox<Double> rendererComboEditable = new JComboBox<>(new Double[]{1.0d, 2.0d, 3.0d});
rendererComboEditable.setEditable(true);
final JComboBox<Double> rendererComboFixed = new JComboBox<>(new Double[]{1.0d, 2.0d, 3.0d});
c1.setCellRenderer(new TableCellRenderer()
{
#Override
public Component getTableCellRendererComponent(JTable t, Object value, boolean isSelected, boolean hasFocus, int row, int column)
{
rendererComboEditable.setSelectedItem(value);
return rendererComboEditable;
}
});
c2.setCellRenderer(new TableCellRenderer()
{
#Override
public Component getTableCellRendererComponent(JTable t, Object value, boolean isSelected, boolean hasFocus, int row, int column)
{
rendererComboFixed.setSelectedItem(value);
return rendererComboFixed;
}
});
frame.getContentPane().add(new JScrollPane(table));
frame.pack();
frame.setVisible(true);
}
}
Both of your issues be resolved by overriding the DefaultCellEditor#isCellEditable method and returning true
You may need to set JTable#setSurrendersFocusOnKeystroke to true as well
Update
The short answer is "It's messy". The long answer is "it's very messy"
I did a "continuos editing" process some time back. Basically, I overrode the Enter and Tab key bindings.
What I'd did was basically stop any active cell editor, take note of the current cell and then tried to finding the next editable cell, looping back around to the start (cell 0x0) if required.
When I found a editable cell, I called JTable#esitCellAt to start editing the cell.
To get the popup to be visible when the cell starts editing, you will need to override the addNotify method of the JComboBox and, using SwingUtilities#invokeLater, set the popup visible

JTable Calls Custom Cell Renderer Method... Continuously

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
aaaaach
you need doLayout(),
next level :-), then there you can to set Maximum visible rows for JTextComponents too, with little effort
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;
}
}
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
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;
}
}

How do I correctly use custom renderers to paint specific cells in a JTable?

I have a JTable component in my GUI which displays psuedocode of an algorithm. I want to highlight the current line of execution by changing the background of a particular cell and then changing the cell beneath and so on.
Right now my code changes the backgrounds on all cells in my JTable as pictured below:
The code I am using to archive this current state is as below:
class CustomRenderer extends DefaultTableCellRenderer
{
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
{
JLabel d = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if((row == 0) && (column == 0))
d.setBackground(new java.awt.Color(255, 72, 72));
return d;
}
}
I then call jTable2.setDefaultRenderer(String.class, new CustomRenderer()); in my constructor.
I assume that:
This method is being called on every String type table cell.
That this would only change the colour of the cell at position (0,0)
How do I fix my code so that only cell (0,0) is coloured?
This is not an answer (*), just too long for a comment on both answers: both are correct in that the else block is the important thingy to ensure that the default color is used for cell that are not supposed to be highlighted. They err slightly in how to reach that, both to the same overall effect: they miss any special coloring, like f.i. due to selection, focus, editable, dnd ...
They reach that "miss" by different means with slightly different effects
setBackground(Color.WHITE);
set's a fixed color which may or may not be the default "normal" table background
setBackground(null);
set's no color which leads to showing the "normal" background color - due to internal tricksery of the DefaultTableCellRenderer isOpaque implementation :-)
The basic reason for the problem (also known the infamous color memory, TM) is an unusually bad implementation of the default renderer which leaves it essentially un-extendable:
/**
* Overrides <code>JComponent.setBackground</code> to assign
* the unselected-background color to the specified color.
*
* JW: The side-effect is documented and looks innocent enough :-)
*/
public void setBackground(Color c) {
super.setBackground(c);
unselectedBackground = c;
}
// using that side-effect when configuring the colors in getTableCellRendererComp
// is what leads to the horrendeous problems
// in the following lines of the else (not selected, that is normal background color)
Color background = unselectedBackground != null
? unselectedBackground : table.getBackground();
super.setBackground(background);
Seeing that, the way out (other than using SwingX and its flexible, clean, powerful, consistent .. :-) renderer support is #Hovercraft's but inverse: first do the custom coloring (or null if none intended) then call super:
#Override
public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus, int row,
int column) {
if (myHighlightCondition) {
setBackground(Color.RED);
} else {
setBackground(null);
}
super.getTableCellRendererComponent(table, value, isSelected, hasFocus,
row, column);
return this;
}
(*) After all, this comment led to an answer, forgot that it's fixable on the custom renderer level :-)
BTW: Catching the "first" call to the renderer is highly brittle, there is no guaratee on which cell that will happen, might well be the last row of the last column.
You forgot your else part of your if block, the code that paints the background to the default if it is not the important row:
if (row == 0 && column == 0) {
d.setBackground(new java.awt.Color(255, 72, 72));
} else {
d.setBackground(null);
}
My SSCCE
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import javax.swing.table.DefaultTableCellRenderer;
public class TestJTable {
private static int highlightedRow = 0;
private static void createAndShowGui() {
String[] columnNames = {"Program"};
Object[][] rowData = {{"Row 1"}, {"Row 2"}, {"Row 3"}, {"Row 4"},
{"Row 1"}, {"Row 2"}, {"Row 3"}, {"Row 4"},
{"Row 1"}, {"Row 2"}, {"Row 3"}, {"Row 4"}};
final JTable myTable = new JTable(rowData , columnNames );
myTable.setDefaultRenderer(Object.class, new DefaultTableCellRenderer()
{
#Override
public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus, int row,
int column) {
Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus,
row, column);
if (row == highlightedRow && column == 0) {
c.setBackground(new java.awt.Color(255, 72, 72));
} else {
c.setBackground(null);
}
return c;
}
});
JFrame frame = new JFrame("TestJTable");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new JScrollPane(myTable));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
highlightedRow++;
int rowCount = myTable.getRowCount();
highlightedRow %= rowCount;
myTable.repaint();
}
}).start();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
Add an else clause to your if:
if ((row == 0) && (column == 0)) {
d.setBackground(new java.awt.Color(255, 72, 72));
}
else {
d.setBackground(Color.WHITE);
}
Remember that the same renderer instance is used to paint all the cells.

Categories

Resources