using a custom Swing JComponent in a TableCellRenderer - java

OK, I know how to make a simple custom JComponent. I know how to override a TableCellRenderer. I can't seem to combine the two.
Here's a sample JComponent I created:
public static class BarRenderer extends JComponent
{
final private double xmin;
final private double xmax;
private double xval;
public BarRenderer(double xmin, double xmax)
{
this.xmin=xmin;
this.xmax=xmax;
}
#Override protected void paintComponent(Graphics g)
{
super.paintComponent(g);
Rectangle r = g.getClipBounds();
g.drawRect(r.x, r.y,
(int)(r.width * ((xval-xmin)/(xmax-xmin))), r.height);
}
public void setXval(double x) {
this.xval = x;
repaint();
}
public double getXval() { return xval; }
}
It works fine as a standalone JComponent. I call setXval(something) and it updates just fine. (edit: I have a Swing Timer that updates the data periodically)
But if this component is something I return in TableCellRenderer.getTableCellRendererComponent(), then it only repaints when I click on the cell in question. What gives? I must be leaving out something really simple.

For performance reasons a JTable reuses renderer components to paint multiple cells - so when you see the component in the JTable it isn't actually there in the traditional sense of a Component in a Container which is present at a location. This means that calling repaint() on the renderer component does nothing.
The most effective option would be to store the Integer value of the bar in your TableModel. Your TableCellRenderer would then look something like this:
public class BarTableCellRenderer implements TableCellRenderer {
private final BarRenderer rendererComponent = new BarRenderer(0, 10);
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
rendererComponent.setXval((Integer)value);
return rendererComponent;
}
}
Then you could change the Integer in your TableModel and it would trigger a repaint of the bar (you may need a TableModel.fireTableCellUpdated dependent on the TableModel implementation you are using).

Both of you (Russ Hayward and Andrew) helped, the key was essentially to do the following:
store the state to be made visible in the TableModel itself, not in the renderer
make sure that when the TableModel's state changes, fireTableCellUpdated() is called
have only one TableCellRenderer object and one JComponent for my custom column (not one per cell)
within TableCellRenderer.getTableCellRendererComponent() store the cell's state for purposes of being rendering soon after (long-term storage is in the TableModel)
provide that state to the JComponent
return the JComponent
override JComponent.PaintComponent()
one convenient possibility is for a custom renderer to extend JComponent and implement TableCellRenderer, then in TableCellRenderer.getTableCellRendererComponent() you store the cell's state and return this;
Here's the relevant excerpt of my code that now works:
class TraceControlTableModel extends AbstractTableModel {
/* handle table state here */
// convenience method for setting bar value (table model's column 2)
public void setBarValue(int row, double x)
{
setValueAt(x, row, 2);
}
}
// one instance of BarRenderer will be set as the
// TableCellRenderer for table column 2
public static class BarRenderer extends JComponent
implements TableCellRenderer
{
final private double xmin;
final private double xmax;
private double xval;
public BarRenderer(double xmin, double xmax)
{
super();
this.xmin=xmin;
this.xmax=xmax;
}
#Override protected void paintComponent(Graphics g)
{
super.paintComponent(g);
Rectangle r = g.getClipBounds();
g.drawRect(r.x, r.y,
(int)(r.width * ((xval-xmin)/(xmax-xmin))), r.height);
}
#Override
public Component getTableCellRendererComponent(JTable arg0,
Object value,
boolean isSelected, boolean hasFocus,
int row, int col)
{
// save state here prior to returning this object as a component
// to be painted
this.xval = (Double)value;
return this;
}
}

If you make a table with say 3 rows, each having a different Xval, then does it initially renderer correctly, meaning each cell has a different looking bar?
When you say it does not repaint unless you click it, has something happened to your underlying data that should have caused the visual display of the data (the rendered bar) to change?
If the data changed, but the table does not immediatley re-render, then I would say that your TableModel is not working properly.
underlying data changes -> TableModel changes -> fires TableModelEvent -> JTable re-renders
Look at the TableModel tuturial: http://java.sun.com/docs/books/tutorial/uiswing/components/table.html#data
to make sure you are doing everything correct.

Related

How to decorate a TableCellRenderer with Graphics related instructions?

I'm implemting TableCellRenderers using the Decorator design-pattern.
All works nice and well as long as all I need is to decorate the returned component from the decorated renderer in such manner that can be performed inside the scope of getTableCellRendererComponent(..).
But how can I decorate the returned component for such cases which need the Graphics object in the paint process? In particular - inside his paintComponent(Graphics g) method? For example, when I want to draw a certain line where a simple setBorder(..) will not be suffice:
import java.awt.Color;
import java.awt.Component;
import javax.swing.JTable;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.DefaultTableCellRenderer;
public class MyTableCellRendererDecorator extends DefaultTableCellRenderer {
private TableCellRenderer decoratedRenderer;
private Component decoratedComponent;
public MyTableCellRendererDecorator(TableCellRenderer decoratedRenderer) {
super();
this.decoratedRenderer = decoratedRenderer;
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
this.decoratedComponent = decoratedRenderer.getTableCellRendererComponent(table, value,
isSelected, hasFocus, row, column);
//an example for a decoration which works fine
decoratedComponent.setBackground(Color.red);
return decoratedComponent;
}
/**
* This code will NOT be executed, because the paint will be called on the returned component
* from getTableCellRendererComponent() and NOT on this JLabel in which this renderer subclasses.
*/
#Override
public void paintComponent(Graphics g) {
decoratedComponent.paint(g);
//an example for a needed decoration in paintComponent()
Rectangle bounds = g.getClipBounds();
g.setColor(Color.BLUE);
g.drawLine(0, 0, bounds.width, bounds.height);
}
}
I had 2 different solutions in mind:
1. Introduce an interface called DecoratedTableCellRenderer:
import javax.swing.table.TableCellRenderer;
public interface DecoratedTableCellRenderer extends TableCellRenderer {
public void setPostPaintComponentRunnable(Runnable postPaintComponentRunnable);
}
So now MyTableCellRendererDecorator will receive a DecoratedTableCellRenderer in his constructor instead of a simple TableCellRenderer, and the responsability for decorating inside paintComponent moves to the decorated class. If we assume a renderer is a JComponent which paints itself, this can be done by overriding paintComponent(..) and applying postPaintComponentRunnable.run() after his own paint code.
But, what if I want to support such decoration renderers which will apply on any TableCellRenderer in which I may not be able to modify?
2. Using java's reflection and a dynamic proxy to instantiate a new ComponentUI delegate to decoratedComponent, which will perform each method as its original ComponentUI object, only with a decorated version of paint(Graphics g, JComponent c).
This will keep the decoration responsibilty in the decorating class, but dynamic proxies are always somewhat hard to read and maintain in my perspective, I would be very happy if I will find a more elegant idea.
As it turns out, the original sample code you gave is close to correct because, in fact, the returned component is this. That is, getTableCellRendererComponent returns the DefaultTableCellRenderer it was called on, so any overridden methods in your subclass of DefaultTableCellRenderer will be called during the painting of the component.
Here's the code for DefaultTableCellRenderer that shows what I'm talking about:
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
//...
return this;
}
Therefore, if you override the paint(Graphics gfx) method and call your super classes getTableCellRendererComponent you will be able to draw whatever you want in the cell.
So your getTableCellRendererComponent should read like this:
#Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
JLabel component = super.getTableCellRendererComponent(table, value,
isSelected, hasFocus, row, column);
//an example for a decoration which works fine
component.setBackground(Color.red);
return component;
}
And use the paint method in your subclass of DefaultTableCellRenderer instead of paintComponent like this:
/**
* This code WILL be executed.
*/
#Override
public void paint(Graphics g) {
super.paint(g);
//an example for a needed decoration in paintComponent()
Rectangle bounds = g.getClipBounds();
g.setColor(Color.BLUE);
g.drawLine(0, 0, bounds.width, bounds.height);
}

Set the button "background" of a Nimbus button

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:

Table Cell Editor issue

I'm building a custom table cell editor so it adjusts row height during editing. I have this code, but instead of resizing the cell it seams to resize the whole panel, or the frame. When I try to enter a character in a cell the main frame width narrows down to a couple of pixels.
Can anyone see the problem?
class MyTableCellEditor extends AbstractCellEditor implements TableCellEditor {
MyTextpane component = new MyTextpane();
MyTable table;
private int row;
private int col;
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected,
int rowIndex, int vColIndex) {
((MyTextpane) component).setText((String) value);
component.addKeyListener(new KeyListener1());
this.table =(MyTable) table;
this.row = rowIndex;
this.col = vColIndex;
return component;
}
public Object getCellEditorValue() {
return ((MyTextpane) component).getText();
}
public class KeyListener1 implements KeyListener {
#Override
public void keyTyped(KeyEvent ke) {
}
#Override
public void keyPressed(KeyEvent ke) {
}
#Override
public void keyReleased(KeyEvent ke) {
adjustRowHeight(table, row, col);
}
private java.util.List<java.util.List<Integer>> rowColHeight = new ArrayList<java.util.List<Integer>>();
private void adjustRowHeight(JTable table, int row, int column) {
//The trick to get this to work properly is to set the width of the column to the
//textarea. The reason for this is that getPreferredSize(), without a width tries
//to place all the text in one line. By setting the size with the with of the column,
//getPreferredSize() returnes the proper height which the row should have in
//order to make room for the text.
int cWidth = table.getTableHeader().getColumnModel().getColumn(column).getWidth();
setSize(new Dimension(cWidth, 1000));
int prefH = getPreferredSize().height;
while (rowColHeight.size() <= row) {
rowColHeight.add(new ArrayList<Integer>(column));
}
java.util.List<Integer> colHeights = rowColHeight.get(row);
while (colHeights.size() <= column) {
colHeights.add(0);
}
colHeights.set(column, prefH);
int maxH = prefH;
for (Integer colHeight : colHeights) {
if (colHeight > maxH) {
maxH = colHeight;
}
}
if (table.getRowHeight(row) != maxH) {
table.setRowHeight(row, maxH);
}
}
}
}
have look at
my answer about doLayout(could be fired from CellEditor)
or (more than confortable way to use TextUtils) comment by #kleopatra about getPreferredSize
this could (very) confusing the users,
because I miss JScrollPane, there have to override MaxSize, max size is height & weight for JScrollPane, otherwise part of CellEditor can going outside of screeens bounds .........,
don't do that, put there JScrollPane with JTextComponents, override PreferredSize for CellEditor,
everything are wrong, my view,
create applications modal popup window (based only on JDialog, becasue JWindow doesn't alloved input to the JTextComponent) with JTextComponent, implements there KeyBindings for ESC key, the same for lost Fucus for JDialog, then could be undecorated without any issue
put there Save JButton
output from Save Button reditect to the selected cell, you can't lost focus from application modal inside JTable
contents should be formatted, filtered, modified one JDialog for all cells from JTable
As an alternative to resizing the row while editing, consider TablePopupEditor, which uses JTextArea.

Java Swing: JList with ListCellRenderer selected item different height

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.

Pass events in custom JTable

Hi I have a class called ColorChooser (in the net.java.dev.colorchooser.ColorChooser package)
This is a custom component used to select colors. What I want is to display a JTable with ColorChoosers in the second column. So I created my own TableCellRenderer and it works:
#SuppressWarnings("serial")
class ColorChooserTableRenderer extends DefaultTableCellRenderer {
public static List<ColorChooser> colors;
public ColorChooserTableRenderer(int rows) {
colors = new ArrayList<ColorChooser>(rows);
for (int i = 0; i<rows ; i ++) {
colors.add(new ColorChooser());
}
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
return colors.get(row);
}
}
I register this in my table :
JTable t = new JTable(5,3);
t.getColumn(t.getColumnName(1)).setCellRenderer(new ColorChooserTableRenderer(5));
The display is good. It even displays the tool tip of the ColorChoosers when i hover my mouse over one of them. The problem is that the ColorChoosers do not receive MouseEvents.
Normally when you press and hold the mouse on a ColorChooser, you get a pop up window that you can use to select a color. When in the JTable the ColorChooser component does not receive the mouse event.
Any solutions?
Edit: The question can be easily modified to this:
Can you please give me a small example of a table containing JButtons in the second column that actually work? You know, buttons that can be pressed?
This sounds vaguely familiar as I have been using table cell renderers for other purposes.
My understanding is that TableCellRenderer is only used to render the component; a component does not actually exist at each of the cells.
So you'd probably have to somehow forward mouse events from the JTable itself to the ColorChooser.
edit: p.s., see my question -- also for custom table cell rendering, you only need 1 instance of the component itself for the entire column, if the column is rendered with the same logic. Don't store persistent state in the TableCellRenderer, store it in the TableModel instead, and use that state immediately prior to rendering when you handle the call to getTableCellRendererComponent().
A renderer only paints the component on the screen and does not allow for interaction. What you need is to also implement a TableCellEditor. It is recommend that you inherit the AbstractCellEditor and you'll save some work. Check out the java tutorial for tables.
Example:
public class MyTableCellRenderer implements TableCellRenderer
{
private JButton button = new JButton("Press Me");
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
return button;
}
}
public class MyTableCellEditor extends AbstractCellEditor implements TableCellEditor
{
private JButton button;
public MyTableCellEditor()
{
button = new JButton("Press Me");
button.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
System.out.println("buttonPressed");
}
});
}
public Object getCellEditorValue() {
return null;
}
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
return button;
}
}

Categories

Resources