I`ve got a mysterious problem with my custom JTable and a custom TableRenderer.
In 95%-99,99% it works perfectly, but sometimes the renderer just stops doing his job, and leaves a portion of the table (which is inside a JScrollPane) blank.
The problem case looks like that:
In all other cases, and after a slight resize of the window, the Table look like that:
Now both columns has a TextAreaCellRenderer associated to, which works as follows:
public class TextAreaCellRenderer extends JTextArea implements TableCellRenderer {
private final Color evenColor = new Color(252, 248, 202);
public TextAreaCellRenderer() {
super();
setLineWrap(true);
setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
}
#Override
public Component getTableCellRendererComponent(final JTable table, final Object value, final boolean isSelected, final boolean hasFocus, final int row, final int column) {
if (isSelected) {
setForeground(table.getSelectionForeground());
setBackground(table.getSelectionBackground());
} else {
setForeground(table.getForeground());
setBackground(table.getBackground());
setBackground((row % 2 == 0) ? evenColor : getBackground());
}
setWrapStyleWord(true);
setFont(table.getFont());
setText((value == null) ? "" : value.toString());
return this;
}
}
I also have to override the doLayout method of the JTable to be able to calculate the hight of a cell depending on the content. The custom table looks like that:
public class MediaMetaDataTable extends JTable {
#Override
public void doLayout() {
TableColumn col = getColumnModel().getColumn(1);
for (int row = 0; row < getRowCount(); row++) {
Component c = prepareRenderer(col.getCellRenderer(), row, 1);
if (c instanceof JTextArea) {
JTextArea a = (JTextArea) c;
int h = getPreferredHeight(a) + getIntercellSpacing().height;
if (getRowHeight(row) != h) {
setRowHeight(row, h);
}
}
}
super.doLayout();
}
private int getPreferredHeight(final JTextComponent c) {
Insets insets = c.getInsets();
View view = c.getUI().getRootView(c).getView(0);
int preferredHeight = (int) view.getPreferredSpan(View.Y_AXIS);
return preferredHeight + insets.top + insets.bottom;
}
}
The table is instantiated once with the following parameters:
metaTable = new MediaMetaDataTable();
metaTable.setModel(new MediaMetaDataTableModel());
metaTable.setEnabled(false);
metaTable.setShowGrid(false);
metaTable.setTableHeader(null);
metaTable.getColumnModel().getColumn(0).setCellRenderer(new TextAreaCellRenderer());
metaTable.getColumnModel().getColumn(1).setCellRenderer(new TextAreaCellRenderer());
metaTable.setPreferredScrollableViewportSize(new Dimension(-1, -1));
metaTable.setShowHorizontalLines(false);
metaTable.setShowVerticalLines(false);
Each time the data to show changes i update table by replacing the underlying models data:
List<MediaMetaData> metaInformation = mediaSearchHit.getMetaInformation();
if (metaInformation != null) {
((MediaMetaDataTableModel) metaTable.getModel()).replaceMetaInfos(metaInformation);
}
On update the model itself fires a table data changed event:
public class MediaMetaDataTableModel extends AbstractTableModel {
private List<MediaMetaData> metaInfos = new LinkedList<MediaMetaData>();
public static final int COL_INDEX_NAME = 0;
public static final int COL_INDEX_VALUE = 1;
public void replaceMetaInfos(final List<MediaMetaData> metaInfos) {
this.metaInfos = null;
this.metaInfos = metaInfos;
fireTableDataChanged();
}
...
Now does anybody has a idea, what causes the described rendering problem?
Thanks for any advices.
I also have to override the doLayout method of the JTable to be able
to calculate the hight of a cell depending on the content.
To achieve this goal there's no need to override doLayout() method. I think the simplest way to do this is by adding the text area used to render the cell content into a JPanel with BorderLayout and set the row height based on the panel's preferred size. This way the layout manager will do the trick for you and all the cell's content will be visible:
#Override
public Component getTableCellRendererComponent(...) {
...
JPanel contentPane = new JPanel(new BorderLayout());
contentPane.add(this);
table.setRowHeight(row, contentPane.getPreferredSize().height); // sets row's height
return contentPane;
}
As #mKorbel pointed out, there's no need to make the renderer extend from JTextArea: a single variable will work. Keeping this in mind take a look to this implementation based on your work:
class TextAreaRenderer implements TableCellRenderer {
private JTextArea renderer;
private final Color evenColor = new Color(252, 248, 202);
public TextAreaRenderer() {
renderer = new JTextArea();
renderer.setLineWrap(true);
renderer.setWrapStyleWord(true);
renderer.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if (isSelected) {
renderer.setForeground(table.getSelectionForeground());
renderer.setBackground(table.getSelectionBackground());
} else {
renderer.setForeground(table.getForeground());
renderer.setBackground((row % 2 == 0) ? evenColor : table.getBackground());
}
renderer.setFont(table.getFont());
renderer.setText((value == null) ? "" : value.toString());
JPanel contentPane = new JPanel(new BorderLayout());
contentPane.add(renderer);
table.setRowHeight(row, contentPane.getPreferredSize().height); // sets row's height
return contentPane;
}
}
Screenshot
If I had to guess I would say it could be a concurency problem. Are you doing everything in the GUI-Thread? If yes, it can't be a concurency problem. Otherwhise try to call everything with Thread.InvokeLater() in an inital debug step, if you don't encounter the error anymore after a long time of testing, you know the cause of the problem.
In a second step you would then check exactly where it is necessary to make the calls with invokelater() and where not (because you shouldn't do that all the time, because it leads to very poor performance.
As I said, just a wild guess... It can of youre just be another bug. Are you using Java7? There are millions of Bugs in Swing with java 7 code (just all the code that from oracle came).
Related
I have a JFileChooser. I am trying to add a zoom feature to the files JList.
I would like to change the scale factor of the file name and of the file icon, for each element of the list.
How could we achieve this ?
Should I make a custom renderer like here [JList custom renderer example] (http://www.codejava.net/java-se/swing/jlist-custom-renderer-example)
or change the list Model ?
Well, I found out some ugly lazy hacks to do it.
It might not be just what you want, but it's a good starting point (and fairly simple):
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.plaf.basic.BasicListUI;
public class TJFileChooserDemo {
//Obtains the (first) JList which is found inside the component/container:
public static JList getFirstJList(final Component component) {
if (component instanceof JList)
return (JList) component;
if (component instanceof Container)
for (int i=0; i<((Container)component).getComponentCount(); ++i) {
final JList list = getFirstJList(((Container)component).getComponent(i));
if (list != null)
return list;
}
return null;
//As you can see, it's a bit lazy hack, which has to run for every JFileChooser once at start-up.
}
private static final double SCALE_STEP_SIZE = 0.125; //Smaller values of this makes zooming slower. Greater values makes zooming faster.
private static double scaleFactor = 1;
public static class TJListCellRenderer extends DefaultListCellRenderer {
public TJListCellRenderer() {
//Ensure every pixel is painted starting from the top-left corner of the label:
super.setVerticalAlignment(JLabel.TOP);
super.setHorizontalAlignment(JLabel.LEFT);
//We need to do this, because the scaling in paintComponent() is also relative to the top-left corner.
}
#Override
public void paintComponent(final Graphics g) {
//setRenderingHints here? Probably for ANTIALIAS...
((Graphics2D)g).scale(scaleFactor, scaleFactor); //Let's scale everything that is painted afterwards:
super.paintComponent(g); //Let's paint the (scaled) JLabel!
}
#Override
public Dimension getPreferredSize() {
final Dimension superPrefDim = super.getPreferredSize(); //Handles automatically insets, icon size, text font, etc.
final double w = superPrefDim.width * scaleFactor, //And we just scale the preferred size.
h = superPrefDim.height * scaleFactor; //And we just scale the preferred size.
return new Dimension((int)w + 5, (int)h + 5); //Add 5 extra pixels to spare.
}
#Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
// System.out.println(value.getClass()); //Something ugly...
return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
}
}
public static class TJListUI extends BasicListUI {
#Override
public void updateLayoutState() {
super.updateLayoutState(); //Just make the following method public.
/*Note: this is not really needed here:
The method could remain protected, but in the case you want this
code to be a bit more reusable, then you shall make it public.*/
}
}
public static void main(final String[] args) {
final JFileChooser jfc = new JFileChooser();
jfc.setDialogType(JFileChooser.OPEN_DIALOG);
final TJListUI ui = new TJListUI();
final JList list = getFirstJList(jfc);
list.setUI(ui);
list.setCellRenderer(new TJListCellRenderer());
final JButton buttonZoomIn = new JButton("Zoom in"),
buttonZoomOut = new JButton("Zoom out"),
buttonResetZoom = new JButton("Reset zoom");
buttonZoomIn.addActionListener(e -> {
scaleFactor = scaleFactor + SCALE_STEP_SIZE;
ui.updateLayoutState(); //Read the preferred sizes from the cell renderer.
list.revalidate(); //Update the JScrollPane.
list.repaint(); //Repaint the list.
});
buttonZoomOut.addActionListener(e -> {
scaleFactor = Math.max(scaleFactor - SCALE_STEP_SIZE, SCALE_STEP_SIZE); //Do not allow underflow.
ui.updateLayoutState(); //Read the preferred sizes from the cell renderer.
list.revalidate(); //Update the JScrollPane.
list.repaint(); //Repaint the list.
});
buttonResetZoom.addActionListener(e -> {
scaleFactor = 1;
ui.updateLayoutState(); //Read the preferred sizes from the cell renderer.
list.revalidate(); //Update the JScrollPane.
list.repaint(); //Repaint the list.
});
final JPanel buttons = new JPanel(); //FlowLayout.
buttons.add(buttonZoomIn);
buttons.add(buttonZoomOut);
buttons.add(buttonResetZoom);
final JPanel panel = new JPanel(new BorderLayout());
panel.add(buttons, BorderLayout.PAGE_START);
panel.add(jfc, BorderLayout.CENTER);
final JFrame frame = new JFrame("JFileChooser's JList cell sizes demo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
Alternatively you can check my answer here about individually resizable cells of a JList.
You can also probably add the JFileChooser's buttons for zooming in/out as an accessory. Read this simple example for how to do it.
Test this code, and I am waiting for comments...
In the end, I realized scaling the text wasn't needed.
To obtain the image files thumbnail, I used the code in making JFileChooser show image thumbnails - check BoffinbraiN answer.
Then for scaling :
1) add an ActionListener to the buttons of ThumbnailFileChooser.
public class ZoomListener implements ActionListener {
private boolean zoomIn = false;
private IconScaleManager iconScaleManager = null;
public ZoomListener(boolean zoom, IconScaleManager renderer) {
zoomIn = zoom;
iconScaleManager = renderer;
}
#Override
public void actionPerformed(ActionEvent e) {
iconScaleManager.scaleButton(zoomIn);
}
}
2) ActionListener::actionPerformed() calls a scale method of a ScaleManager.
#Override
public void actionPerformed(ActionEvent e) {
iconScaleManager.scaleButton(zoomIn);
}
3) The ScaleManager method changes and update the cells of the ThumbnailFileChooser's Jlist (the list is an attribute of the ScaleManager)
public class IconScaleManager {
static final int[] iconScales = new int[]{ 16, 32, 64, 128, 256, 512, 1024, 2048 };
private int scaleIndex = 4;
private JList fileList = null;
public IconScaleManager(JList list) {
fileList = list;
setFixedCellDimension();
}
public void scaleButton(boolean zoomIn) {
if (zoomIn && scaleIndex < iconScales.length - 1) {
scaleIndex++;
setFixedCellDimension();
} else if (!zoomIn && 0 < scaleIndex) {
scaleIndex--;
setFixedCellDimension();
}
}
private void setFixedCellDimension() {
fileList.setFixedCellWidth(iconScales[scaleIndex]);
fileList.setFixedCellHeight(iconScales[scaleIndex]);
}
}
Thank you #thanopi57 for your help. I didn't really use what you provided, but I appreciate your support.
Also, I will have to make sure that it works, because there might not be a JList for all JFileChooser
I hope someone can help me out. Iam trying to create a "countrycombobox" with icons in Java Swing. I found some stuff, but nothing did work for me. Maybe the problem is, that Iam still "new" to Java.
I just want it simple like this: http://www.zomex.com/libs/images/layout/whmcs-template-language-select-w-flags-eco.jpg
Just the flags in front of the countrys.
I would really appreciate a working example. I really wonder, that there is no standard option or a good code snippet(used Google a lot to find help here) for stuff like this.
I found a better example and wanna share my stuff with you. There is just one problem left, that I dont get it sized.
package view;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class CountryComboBox extends JPanel {
ImageIcon[] images;
String[] imgStrings = {"de"};
/*
* Despite its use of EmptyBorder, this panel makes a fine content
* pane because the empty border just increases the panel's size
* and is "painted" on top of the panel's normal background. In
* other words, the JPanel fills its entire background if it's
* opaque (which it is by default); adding a border doesn't change
* that.
*/
public CountryComboBox() {
super(new BorderLayout());
//Load the images and create an array of indexes.
images = new ImageIcon[imgStrings.length];
Integer[] intArray = new Integer[imgStrings.length];
for (int i = 0; i < imgStrings.length; i++) {
intArray[i] = new Integer(i);
images[i] = createImageIcon("/res/" + imgStrings[i] + ".png");
if (images[i] != null) {
images[i].setDescription(imgStrings[i]);
}
}
//Create the combo box.
JComboBox imgList = new JComboBox(intArray);
ComboBoxRenderer renderer= new ComboBoxRenderer();
imgList.setRenderer(renderer);
imgList.setMaximumRowCount(3);
//Lay out the demo.
add(imgList, BorderLayout.PAGE_START);
//setBorder(BorderFactory.createEmptyBorder(20,20,20,20));
}
/** Returns an ImageIcon, or null if the path was invalid. */
protected static ImageIcon createImageIcon(String path) {
java.net.URL imgURL = CountryComboBox.class.getResource(path);
if (imgURL != null) {
return new ImageIcon(imgURL);
} else {
System.err.println("Couldn't find file: " + path);
return null;
}
}
class ComboBoxRenderer extends JLabel
implements ListCellRenderer {
private Font uhOhFont;
public ComboBoxRenderer() {
setOpaque(true);
setHorizontalAlignment(CENTER);
setVerticalAlignment(CENTER);
}
/*
* This method finds the image and text corresponding
* to the selected value and returns the label, set up
* to display the text and image.
*/
#Override
public Component getListCellRendererComponent(
JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus) {
//Get the selected index. (The index param isn't
//always valid, so just use the value.)
int selectedIndex = ((Integer)value).intValue();
if (isSelected) {
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
} else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
//Set the icon and text. If icon was null, say so.
ImageIcon icon = images[selectedIndex];
String img = imgStrings[selectedIndex];
setIcon(icon);
if (icon != null) {
setText(img);
setFont(list.getFont());
} else {
setUhOhText(img + " (no image available)",
list.getFont());
}
return this;
}
//Set the font and text when no image was found.
protected void setUhOhText(String uhOhText, Font normalFont) {
if (uhOhFont == null) { //lazily create this font
uhOhFont = normalFont.deriveFont(Font.ITALIC);
}
setFont(uhOhFont);
setText(uhOhText);
}
}
}
I call it in a JPanel with absolute layout:
JComponent newContentPane = new CountryComboBox();
newContentPane.setOpaque(true); //content panes must be opaque
newContentPane.setBounds(10, 75, 50, 26);
contentPane.add(newContentPane);
setBounds isnt working, just to get the right position. I cant size it with this.
Best regards
Acanis
im been looking around on the internet for about 5 hours now to debug on this issue ive been having. and Basically i haven't been able to find anywhere where a person tries to add a new JLabel for each row in a specific column.
functionality explanation: i get a file url, i take the postFix which is usually xlsx or
doc - this postfix i want to display in a JLabel in coalition with a Excel or Doc
Icon -.- but what my current code does is just paint the same JLabel over and over again because it only sets the CellRenderer one time and uses it on all the rows dispite the fact that im setting it in a for-loop for each iteration - all my code is correct for this functionality up until the renderer only gets called once.
so my question goes as follows - how do i add a new JLabel for each row
in a Column ? -
my code follows.
my TableCellRenderer:
public class JLabelRenderer extends DefaultTableCellRenderer {
private static final long serialVersionUID = -166379583761969293L;
// private String fileExtension;
// private JLabel label;
private LogEntry log;
private JLabel label;
public JLabelRenderer(LogEntry log) {
label = new JLabel();
System.out.println("makeing a new JLabelRenderer");
this.log = log;
}
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
label = (JLabel) super.getTableCellRendererComponent(table, value,
isSelected, hasFocus, row, column);
System.out.println(log.getFileExtension());
if (log.getFileExtension().equalsIgnoreCase("xlsx")) {
label.setIcon((ImageIcon) Pictures.getXlsx());
label.setText(log.getFileExtension());
} else if (log.getFileExtension().equalsIgnoreCase("doc")) {
label.setIcon((ImageIcon) Pictures.getDoc());
label.setText(log.getFileExtension());
} else if (log.getFileExtension().equalsIgnoreCase("docx")) {
label.setIcon((ImageIcon) Pictures.getDoc());
label.setText(log.getFileExtension());
} else if (log.getFileExtension().equalsIgnoreCase("pdf")) {
label.setIcon((ImageIcon) Pictures.getPdf());
label.setText(log.getFileExtension());
}
value = label;
return label;
}
#Override
public void setHorizontalAlignment(int alignment) {
super.setHorizontalAlignment(alignment);
}
public void setLog(LogEntry log) {
this.log = log;
}
}
where i make my model:
(i have alot more code adding actionListeners to right-click functionality and what not
but thats hardly relevant)
public void makeLogModel()
{
logModel = new DefaultTableModel();
//addCellEditorListener(this);
logModel.setColumnIdentifiers(new String[]{"Lavet Dato", "Lavet Af", "Beskrivelse", "Sidst Redigeret Dato", "Sidst Redigeret Af", "Fil Type"});
setAutoCreateRowSorter(true);//allows to sort through the information.
setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
setModel(logModel);
}
Where the magic was suppose to happen(update the log table after a search on logs)
public void updateLogTable(ArrayList<LogEntry> entryList)
{
logModel.setRowCount(entryList.size());
for(int i = 0; i < logModel.getRowCount(); i++)
{
setRowHeight(i, 30);
}
int row = 0;
for(LogEntry log : entryList)
{
logModel.setValueAt(log.getCreateDate(), row, 0);
logModel.setValueAt(log.getMadeBy(), row, 1);
logModel.setValueAt(log.getDescription(), row, 2);
logModel.setValueAt(log.getLastEdited(), row, 3);
logModel.setValueAt(log.getLastEditedBy(), row, 4);
labelRenderer = new JLabelRenderer(log);
getColumn("Fil Type").setCellRenderer(labelRenderer);
logModel.setValueAt(new JLabel(), logRow, 5);
row++;
}
}
i have read the Java documentation for components and Editors- but none of the
code examples are for a JLabel - probably because CellRenderer extends JLabel..
i have allso sniffed up the info that the 'value' parameter in the getTableCellRendererComponent() method is the one that is supposed to be set dynamically somehow... any surgestions would be greatly apriciatet, feel free to ask any questions.
thanks
logModel.setValueAt(new JLabel(), logRow, 5);
don't put JLabel, any JComponent to the model, XxxTableModel is designated to nest value for Renderer or Editor only, more in Oracle tutorial How to use Tables - Creating a Table Model
Renderer or Editor visually represents real JComponents, more in Oracle tutorial How to use Tables - Concepts: Editors and Renderers
label.setIcon((ImageIcon) Pictures.getXlsx());
Icon should be placed into local variables or array, list, whatever of Icons, don't load any FileIO from Renderer, renderer can be called many times per one second, e.g. from all mouse, keys and methods inplemented in APIs
Seems your problem in DefaultTableCellRenderer implementation.
You create renderer like JLabelRenderer(LogEntry log) because of, for all cells you have one instance of LogEntry, and log.getFileExtension() in getTableCellRendererComponent() returns same result for all rows.
In case of TableCellRenderer you need to use value parametr from getTableCellRendererComponent() method to determine extension and LogEntry instance.
Examine Concepts: Editors and Renderers.
Here is simple example for you, I use color instead of icons:
import java.awt.Color;
import java.awt.Component;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
public class TestFrame extends JFrame{
public TestFrame(){
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
init();
pack();
setVisible(true);
}
private void init() {
JTable table = new JTable(new Object[][]{
{1,"doc"},
{2,"xlsx"},
{3,"abc"}
},new Object[]{"nmb","extension"});
table.getColumnModel().getColumn(1).setCellRenderer(getRenderer());
add(new JScrollPane(table));
}
private TableCellRenderer getRenderer() {
return new DefaultTableCellRenderer(){
#Override
public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus,
int row, int column) {
Component tableCellRendererComponent = super.getTableCellRendererComponent(table, value, isSelected, hasFocus,row, column);
if("doc".equals(value)){
tableCellRendererComponent.setBackground(Color.YELLOW);
} else if("xlsx".equals(value)){
tableCellRendererComponent.setBackground(Color.GREEN);
} else {
tableCellRendererComponent.setBackground(isSelected ? table.getSelectionBackground() : table.getBackground());
}
return tableCellRendererComponent;
}
};
}
public static void main(String... strings) {
new TestFrame();
}
}
Also don't put Component's to TableModel like here logModel.setValueAt(new JLabel(), logRow, 5); just value.
I created a custom ListCellRenderer that extends Jidesoft's StyledLabel. The JList my renderer is used in is a fixed width, so in my renderer I attempt to shorten text based on this width. I call setText in getListCellRendererComponent, which sometimes works. When I select a cell, the text acts as if I had never shortened it at all. There is no branching related to the setText and text-shortening.
I attempted to use the solutions in this somewhat-related answer discussing custom rendering and cell heights, but it didn't work consistently on select either.
edit: some code
public class CustomListCellRenderer extends StyledLabel implements ListCellRenderer {
public Component getListCellRendererComponent(
JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus) {
setText(shortenName(value.toString(), 150));
return this;
}
private String shortenName(String name, int width) {
if(this.getGraphics() != null) {
final FontMetrics fontMetrics = this.getFontMetrics(this.getFont());
String end = name.substring(name.length()/2);
String beginning = name.substring( 0, name.length()/2);
int stringWidth = SwingUtilities.computeStringWidth(fontMetrics, name);
if(stringWidth < width)
return name;
do {
end = end.substring(1);
beginning = beginning.substring(0, beginning.length() - 1);
stringWidth = SwingUtilities.computeStringWidth(fontMetrics, beginning + "..." + end);
} while (stringWidth > width);
return beginning + "..." + end;
}
return name;
}
public static void main(String[] args) {
JFrame frame = new JFrame();
JList list = new JList(new String[] {
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
"zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"});
list.setCellRenderer(new CustomListCellRenderer());
frame.getContentPane().add(list);
frame.setVisible(true);
}
}
In your implementation of ListCellRenderer you're relying on the getGraphics() of the label. Sometimes, getGraphics() is null which is OK, but you're not entering the if(this.getGraphics() != null) condition and simply returning the unmodified string. That is why you get inconsistent results. Commenting out this condition solved the problem in the posted code. You should not rely on getGraphics() its value is out of your control.
I have a created a following renderer which renders the JTree with checkboxes and I want to add different color and icon to different nodes. How do I do it? Please help me. Thank you in advance.
class CheckTreeCellRenderer extends JPanel implements TreeCellRenderer {
private CheckTreeSelectionModel selectionModel;
private TreeCellRenderer delegate;
private TristateCheckBox checkBox = new TristateCheckBox("",null,true);
public static final State NOT_SELECTED = new State();
public static final State SELECTED = new State();
public static final State DONT_CARE = new State();
public CheckTreeCellRenderer(TreeCellRenderer delegate, CheckTreeSelectionModel selectionModel) {
this.delegate = delegate;
this.selectionModel = selectionModel;
setLayout(new BorderLayout());
setOpaque(false);
checkBox.setState(Boolean.TRUE);
revalidate();
checkBox.setOpaque(false);
}
public Component getTreeCellRendererComponent
(JTree tree, Object value, boolean selected, boolean expanded,
boolean leaf, int row, boolean hasFocus) {
Component renderer = delegate.getTreeCellRendererComponent
(tree, value, selected, expanded, leaf, row, hasFocus);
TreePath path = tree.getPathForRow(row);
if(path!=null){
if(selectionModel.isPathSelected(path, true)) {
checkBox.setState(Boolean.TRUE);
}
else {
checkBox.setState
(selectionModel.isPartiallySelected(path) ? null : Boolean.FALSE);
}
}
setBackground(Color.pink);
removeAll();
add(checkBox, BorderLayout.WEST);
add(renderer, BorderLayout.CENTER);
return this;
}
}
The best place to learn about TreeCellRenderers is from the tutorial (at the bottom of the page).
Instead of adding renderer to BorderLayout.CENTER, you can just add a different icon of whatever color you like.
In order for your setBackground(Color.PINK) to have any visible effect, you should change the setOpaque(false) to setOpaque(true) in your constructor. That said, I second #John's suggestion that you read up on renderers in the Sun tutorials.