I'm working on an app using the Nimbus Look and Feel. There's a table and one column contains buttons (using the Table Button Column from Rob Camick). That does work, but the result isn't what I had expected. I have tried to fix the look, but to no avail.
So the question is: how do I change the "background" (the area outside the rounded rectangle) of a Nimbus button? Preferably in a non-hacky way :-)
Using the default Table Column Button, the result looks like this:
As you can see, the background (and by this I mean the area outside the button's rounded rectangle) is wrong for the odd (white) rows. The code that produces this output is:
public Component getTableCellRendererComponent(
JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
{
if (isSelected) {
renderButton.setForeground(table.getSelectionForeground());
renderButton.setBackground(table.getSelectionBackground());
} else {
renderButton.setForeground(table.getForeground());
renderButton.setBackground(table.getBackground());
}
if (hasFocus) {
renderButton.setBorder( focusBorder );
} else {
renderButton.setBorder( originalBorder );
}
// <snip some code>
renderButton.setOpaque(true);
return renderButton;
}
The renderButton is an instance of a default JButton. I've tried messing with the background color of the button, but that didn't work out like I expected at first:
Color alternate = (Color)LookAndFeel.getDesktopPropertyValue("Table.alternateRowColor", Color.lightGray);
Color normal = (Color)LookAndFeel.getDesktopPropertyValue("Table.background", Color.white);
if (row % 2 == 0) {
renderButton.setBackground(normal);
} else {
renderButton.setBackground(alternate);
}
This produces:
So this time the buttons that look alright in the first image are now bad and vice versa. The button's inner backgrounds (the areas inside the rounded rectangles) do seem to have the correct color according to the background color property (which is what's really modified with the setBackground() call). But the area outside is all wrong. Alright, let's combine the two :
Color alternate = table.getBackground();
Color normal = (Color)LookAndFeel.getDesktopPropertyValue("Table.background", Color.white);
if (row % 2 == 0) {
renderButton.setBackground(normal);
} else {
renderButton.setBackground(alternate);
}
The result:
So now the "background" does look correct, but the buttons don't look like Nimbus buttons any more. How do I make the "background" have the correct color while still looking like Nimbus buttons?
Below's a hacky way, following up on #Piro's suggestion: using a JPanel with the button as child component. Which in itself is a nice idea, given that we don't really want to touch the "inner" background visuals of the button.
Here the hack comes when forcing Nimbus internals to not use a JPanel's default background for filling its area but instead use the background of the given panel instance This needs relying on implementation details, particularly the lookup mechanism of a background color. That happens in SynthStyle.getColor():
// If the developer has specified a color, prefer it. Otherwise, get
// the color for the state.
Color color = null;
if (!id.isSubregion()) {
if (type == ColorType.BACKGROUND) {
color = c.getBackground();
}
....
}
if (color == null || color instanceof UIResource) {
// Then use what we've locally defined
color = getColorForState(context, type);
}
Translated: it does indeed query the instance's color, but overrules it with the default if the instance color is a UIResource - which typically is the case if used as a renderer. So the trick out (tried unsuccessfully by SynthBooleanRenderer, but that's another story ;-) is to make the instance color not a UIResource. An additional quirk is that being UIResource is necessary to ensure the striping color - which is not of type UIResource, haha - be applied ... intuitive, isn't it ...
public class RendererPanel implements TableCellRenderer {
private JComponent panel;
private JButton button;
public RendererPanel() {
panel = new JPanel(new BorderLayout());
panel.setBorder(BorderFactory.createEmptyBorder(3, 10, 2, 10));
button = new JButton();
panel.add(button);
}
#Override
public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus, int row,
int column) {
// suggestion by Piro - use background of default
DefaultTableCellRenderer dt = (DefaultTableCellRenderer) table.getDefaultRenderer(Object.class);
dt.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
// first try: set the color as-is - doesn't work
// panel.setBackground(dt.getBackground());
// second try: set color as not ui-resource
// that's working because at this point we already have the color that will be used
// let's hinder synth background color searching to fall back to component defaults
panel.setBackground(new Color(dt.getBackground().getRGB()));
// hack: unwrap ui-resource as needed
// updateBackground(isSelected ? table.getSelectionBackground() : table.getBackground(), row);
button.setText(String.valueOf(value));
return panel;
}
private void updateBackground(Color color, int row) {
Color hack = row % 2 == 0 ? unwrap(color) : color;
panel.setBackground(hack);
}
private Color unwrap(Color c) {
if (c instanceof UIResource) {
return new Color(c.getRGB());
}
return c;
}
}
Screenshot: with unwrap hack
Screenshot: using default colors (from the renderer installed for Object.class)
The non-hacky way out might be (didn't try here, but remember having done once) to register a Region with the style, similarly to what NimbusDefaults does internally:
register(Region.PANEL, "Table:\"Table.cellRenderer\"");
Problem here being that there's no public api to do so (or could be that I simply don't know enough about Synth ;-)
Do not set background to JButton. Use JPanel to wrap JButton and set background to JPanel. This would be probably obvious if you used more buttons in one JTable column.
To set correct background color of JPanel i did (you should):
Keep reference to original renderer
Let original renderer render its own component (for every rendering)!
Use background of rendered component to set background of JPanel (for every rendering)!
This way you don't have to choose correct color yourself
Also you have to override paintComponent to correctly paint white background of JPanel:
#Override
protected void paintComponent(Graphics g) {
Color background = getBackground();
setBackground(new Color(background.getRGB()));
super.paintComponent(g);
}
Edit: as #kleopatra suggests you don't have to override paintComponent, only set background color as not-uiresource (shown in complete example)
Here is complete example:
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;
import javax.swing.table.TableCellRenderer;
public class Test {
public static void main(String[] args) throws Throwable {
for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
UIManager.setLookAndFeel(info.getClassName());
break;
}
}
String[] columnNames = new String[]{"c1"};
Object[][] data = new Object[4][1];
data[0][0] = "First";
data[1][0] = "Second";
data[2][0] = "Third";
data[3][0] = "Fourth";
JTable table = new JTable(data, columnNames){
#Override
public javax.swing.table.TableCellRenderer getCellRenderer(int row, int column) {
final TableCellRenderer ori = super.getCellRenderer(row, column);
final TableCellRenderer mine = new TableCellRenderer() {
#Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
Component c = ori.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
JPanel p = new JPanel();
if(value == null){
value = "";
}
p.add(new JButton(value.toString()));
p.setBackground(new Color(c.getBackground().getRGB()));
return p;
}
};
return mine;
};
};
table.setRowHeight(50);
JFrame f = new JFrame();
f.add(table);
f.setVisible(true);
f.pack();
}
}
Result:
Related
this is an example of what I wrote so far:
import javax.swing.*;
import java.awt.*;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class program {
JFrame win = new JFrame("bla bla");
final private String[] animals = { "dog", "cat", "mouse" };
private void Start() {
JPanel superior = new JPanel();
superior.setLayout(new GridLayout(3, 3));
win.getContentPane().add(superior, BorderLayout.PAGE_START);
final JComboBox<String> comboBox = new JComboBox<String>(animals);
((JLabel) comboBox.getRenderer()).setHorizontalAlignment(SwingConstants.CENTER);
superior.add(comboBox);
win.setSize(440, 290);
win.setResizable(false);
win.setLocationRelativeTo(null);
win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
win.setVisible(true);
}
public static void main(String args[]) {
program window = new program();
window.Start();
}
}
I've a single jpg for each item of animals String array in a folder called jpg placed on the same level of (default package). I'm using eclipse.
My idea was to make a JComboBox able to display only jpgs, while using strings with certain mouse click events I've already coded (but not reported just to make it short).
I've read this, this and this, but I can't really get the job done :(
Could anyone explain me how to get what I want, maybe modifying my code so I can study it?
You'll need to supply a custom ListCellRenderer to the combobox which is capable of displaying an image (and other information as you need)
See Providing a custom renderer for more details
You can load a image using the ImageIO API. You may need to wrap the result in a ImageIcon in order to render it more easily, but that will depend on you API implementation
I would recommend using a DefaultListCellRenderer as it extends from JLabel and will make you life easier
Really simple example
I don't have enough information to form a fully runnable example, but essentially, the values added to the combo box model should, in some way, contain a reference to the image you want to load.
This way, when required, you can extract the image and display it using the cell renderer...
public class ImageCellRenderer extends DefaultListCellRenderer {
public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (value instanceof ??) {
ImageIcon icon = ...;
setIcon(icon);
}
return this;
}
}
And apply the renderer...
JComboBox cb = new JComboBox();
cb.setRenderer(new ImageCellRenderer());
Updated
Now assuming that the images are named [animal].jpg (so dog would be dog.jpg) you should be able to build a simple Map, mapping the name to the animal image...
// List of animals...
final private String[] animals = { "dog", "cat", "mouse" };
/*...*/
// Map of animal icons...
Map<String, Icon> mapImages = new HashMap<>();
// Build the icon image mapping
for (String animal : animals) {
mapImages.put(animal, new ImageIcon(ImageIO.read(getClass().getResource("/" + animal + ".jpg))))
}
// Create a new cell renderer, passing the mappings
ImageCellRenderer renderer = new ImageCellRenderer(mapImages);
// Create a new combo box
JComboBox<String> comboBox = new JComboBox<String>(animals);
// Apply the renderer
comboBox.setRenderer(renderer);
/*...*/
public class ImageCellRenderer extends DefaultListCellRenderer {
// Icon mappings
private Map<String, Icon> mapImages
public ImageCellRenderer(Map<String, Icon> mapImages) {
// Make a new reference to the icon mappings
this.mapImages = new HashMap<>(mapImages);
setHorizontalAlignment(SwingConstants.CENTER);
}
public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (value instanceof String) {
// Look up the icon associated with the animal...
Icon icon = mapImages.get(value.toString());
setIcon(icon);
}
return this;
}
}
I am putting some icons in my jtable's cell but problem is it is changing it's background color to table's background color....i want it to change it to row's background color...and also when it is selected it does't show the selection...Here is my code how i am setting icons....
Users user;
ConsoleUsersListTbl.getColumnModel().getColumn(1).setCellRenderer(new ImageRender());
DefaultTableModel userTableModel = (DefaultTableModel) ConsoleUsersListTbl.getModel();
for (int i = 0; i < userList.size()-1; i++) {
user = userList.get(i);
javax.swing.ImageIcon image_icon = new javax.swing.ImageIcon(user.getUser_image());
if (image_icon.getIconWidth() > 32 || image_icon.getIconWidth() > 32) {
InputStream in = new ByteArrayInputStream(user.getUser_image());
BufferedImage buff_image;
try {
buff_image = ImageIO.read(in);
int type = buff_image.getType() == 0 ? BufferedImage.TYPE_INT_ARGB : buff_image.getType();
BufferedImage resizedImage = resizeImage(buff_image, type);
image_icon.setImage(resizedImage);
userTableModel.setValueAt(image_icon, i, 1);
} catch (IOException ex) {
Logger.getLogger(MainFrame.class.getName()).log(Level.SEVERE, null, ex);
}
} else {
userTableModel.setValueAt(image_icon, i, 1);
}
and here is the out put......
Edit1
here is my Image Render class ;and making setOpaque true makes my icons white..
public class ImageRender extends DefaultTableCellRenderer {
JLabel lable = new JLabel();
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
lable.setIcon((ImageIcon)value);
lable.setOpaque(true);
return lable;
}
}
I think you need to implement the custom cell renderer as discussed here and also described here. The renderer has access to information if the current cell is focused or selected, so you can adjust background, foreground or even content any way you want. Mind that components may be opaque (have they own background) or not (the parent background or other content is visible through it). This is controlled through setOpaque(boolean).
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;
}
}
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.
I'm making a custom ListCellRenderer. I know that you can have different dimensions for each individual cell. But now I want to have a different dimension for the selected cell. Somehow, the JList is caching the dimension for each individual cell the first time it has to calculate bounds for each cell.
This is my code:
public class Test {
static class Oh extends JPanel {
public Oh() {
setPreferredSize(new Dimension(100, 20));
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
}
}
static class Yeah extends JPanel {
private boolean isSelected;
public Yeah(boolean isSelected) {
setPreferredSize(new Dimension(100, 100));
this.isSelected = isSelected;
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
//setSize(100, 100); // doesn't change the bounds of the component
//setBounds(0, 0, 100, 100); // this doesn't do any good either.
if (isSelected) g.setColor(Color.GREEN);
else g.setColor(Color.BLACK);
g.fillRect(0, 0, getWidth(), getHeight());
}
}
public static void main(String[] args) {
JFrame f = new JFrame();
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
f.setSize(800, 500);
Vector<Integer> ints = new Vector<Integer>();
for (int i = 0; i < 100; i++) {
ints.add(i);
}
JList list = new JList(ints);
list.setCellRenderer(new ListCellRenderer() {
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
if (isSelected || ((Integer) value) == 42) return new Yeah(isSelected);
else return new Oh();
}
});
//list.setPrototypeCellValue(null);
//list.setFixedCellHeight(-1);
f.add(new JScrollPane(list));
f.setVisible(true);
}
}
In the comments you can see what I've already tried.
I've already searched quite long and found a lot of useless articles, some of them touch the ListCellRenderer/dynamic height thing, but they only work because the height stays the same for the individual cells. My heights are changing, so how do I do this?
Basically, there are two aspects of the problem, both located in the ui delegate
it fails to configure the renderer to its real state when measuring, that is ignores the selection (and focus) completely
it is notoriously stubborn against being forced to re-calculate the cached cell sizes: it has no public api to do so and only does voluntarily on model changes.
The remedy to fix the first is indeed the renderer: implement to ignore the given selected flag and query the list for the real selection, as outlined by #Andy. In code, using the OP's components
ListCellRenderer renderer = new ListCellRenderer() {
Yeah yeah = new Yeah(false);
Oh oh = new Oh();
#Override
public Component getListCellRendererComponent(JList list,
Object value, int index, boolean isSelected,
boolean cellHasFocus) {
// ignore the given selection index, query the list instead
if (list != null) {
isSelected = list.isSelectedIndex(index);
}
if (isSelected || ((Integer) value) == 42) {
yeah.isSelected = isSelected;
return yeah;
}
return oh;
}
};
list.setCellRenderer(renderer);
To fix the second, a custom ui delegate (as suggested in others answers as well) is a possible solution. Though some work in the general case, if supporting multiple LAFs is needed.
A less intrusive but slightly dirty method to force the ui into voluntarily update its cache is to send a fake ListDataEvent on selectionChange:
ListSelectionListener l = new ListSelectionListener() {
ListDataEvent fake = new ListDataEvent(list, ListDataEvent.CONTENTS_CHANGED, -1, -1);
#Override
public void valueChanged(ListSelectionEvent e) {
JList list = (JList) e.getSource();
ListDataListener[] listeners = ((AbstractListModel) list.getModel())
.getListDataListeners();
for (ListDataListener l : listeners) {
if (l.getClass().getName().contains("ListUI")) {
l.contentsChanged(fake);
break;
}
}
}
};
list.addListSelectionListener(l);
BTW, JXList of the SwingX project has a custom ui delegate - mainly for supporting sorting/filtering - with public api to re-calculate the cache, then the above ListSelectionListener would be simplified (and clean :-) to
ListSelectionListener l = new ListSelectionListener() {
#Override
public void valueChanged(ListSelectionEvent e) {
((JXList) e.getSource()).invalidateCellSizeCache();
}
};
list.addListSelectionListener(l);
I just implemented this feature. The problem is, that the cell renderer is asked twice for rendering a cell. In the first round all list entries are rendered without selection, then the selected cells are rendered again using selection. So if you provide a preferred size in the first round, it is cached and also used for the second round.
The trick is to ignore the isSelected boolean parameter in the getListCellRendererComponent and to figure out the selection state by checking if list.getSelectedIndices() contains the given index.
But, I still have the problem, that after the list is made visible, the height of the rendered components are sometimes to large/small. After resizing the list by mouse everything is fine again. I played around with validate/revalidate, repaint, reset of cached heights, but nothing worked. Swing is sometimes a bit strange...
The JList has no ability to change size of cell depending on selection or whatever. The list use "cached" sizes. If there is new cellRenderer provided this sizes are recounted and applied within all cells in list. I think the reason is performance for list with a lot of entries. The possible solution is to write own ListUI implementation which is able to use different sizes for selected and unselected cells. This brings also possibility to adjust size of cells around selection by logarithm or other interpolation. I hope you have a big reason why to do this. It is a lot of work!
I've been tearing my hair out about this stupid JList row height problem.
I have a cell renderer which sets a variable row height for every row - problem is that JList keeps a cache of the heights.
Using the other answers, I think I've struck on the holy grail. Here it is:
Use a simplified version of the BasicListUI as created by Jaap:
public class BetterListUI extends BasicListUI {
public void triggerUpdate() {
updateLayoutState();
}
}
Then when you create a JList - extend it like this :
betterListUI = new BetterListUI();
myJList = new JList() {
#Override
public void repaint(long tm, int x, int y, int width, int height) {
betterListUI.triggerUpdate();
super.repaint(tm, x, y, width, height);
}
};
myJList.setUI(betterListUI);
You may need to put a guard around the triggerUpdate during creation depending on your circumstances.
Thanks to Rastislav Komara I've been able to solve this quite easily:
I've created an inner class that extends BasicListUI and created public method that is called on ListSelectionListener.valueChanged:
private class MyRenderer implements ListCellRenderer {
public int listSelectedIndex = -1;
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
boolean cellHasFocus) {
if (index == listSelectedIndex)
return new Yeah(isSelected);
else
return new Oh();
}
}
MyRenderer lcr = new MyRenderer();
private class MyListUI extends BasicListUI {
public void triggerUpdate() {
lcr.listSelectedIndex = list.getSelectedIndex();
updateLayoutState();
list.revalidate();
}
}
The updateLayoutState method is normally triggered when the JList height changes.
The only "insane" thing I'm doing here is that my renderer needs to know what the selected index is. This is because the updateLayoutState method doesn't use the selected index in it's height calculations.
Somehow using list.getSelectedIndex() inside getListCellRendererComponent doesn't work well.
Edit:
Check also the anser by nevster and kleopatra, they look way smarter, try them first...
The JList is probably "caching" your cell renderer. Try to attach a ListSelectionListener, and set the renderer again when selection is changed.
...
addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent event) {
if(event.getValueIsAdjusting() == false) {
list.setCellRenderer(new MyRenderer());
}
}
)
...
this is a simple solution:
public class VariableHeightListUI extends BasicListUI {
#Override
public void paint(Graphics g, JComponent c) {
updateLayoutState();
super.paint(g, c);
}
}
of course you need write your own implementation of ListCellRenderer, and according to different selection state of list element, you can set different prefer height of returned Component.
Only one issue need to go on is : when you select an element of List FIRST time, not draw correctly. but after then, all work well.
hope this can help you.