I have a JList with very long item names that cause the horizontal scroll-bar to appear in scroll-pane.
Is there anyway that I can word wrap so that the whole whole item name appears in 2 rows yet can be selected in one click? I.E it should still behave as a single item but be displayed in two rows.
Here is what I did after seeing the example below
I added a new class to my project MyCellRenderer and then I went added MyList.setCellRenderer(new MyCellRenderer(80)); in the post creation code of my List. Is there anything else I need to do?
Yep, using Andrew's code, I came up with something like this:
import java.awt.Component;
import javax.swing.*;
public class JListLimitWidth {
public static void main(String[] args) {
String[] names = { "John Smith", "engelbert humperdinck",
"john jacob jingleheimer schmidt" };
MyCellRenderer cellRenderer = new MyCellRenderer(80);
JList list = new JList(names);
list.setCellRenderer(cellRenderer);
JScrollPane sPane = new JScrollPane(list);
JPanel panel = new JPanel();
panel.add(sPane);
JOptionPane.showMessageDialog(null, panel);
}
}
class MyCellRenderer extends DefaultListCellRenderer {
public static final String HTML_1 = "<html><body style='width: ";
public static final String HTML_2 = "px'>";
public static final String HTML_3 = "</html>";
private int width;
public MyCellRenderer(int width) {
this.width = width;
}
#Override
public Component getListCellRendererComponent(JList list, Object value,
int index, boolean isSelected, boolean cellHasFocus) {
String text = HTML_1 + String.valueOf(width) + HTML_2 + value.toString()
+ HTML_3;
return super.getListCellRendererComponent(list, text, index, isSelected,
cellHasFocus);
}
}
You can also compute dynamically the width (instead of a fixed value):
String text = HTML_1 + String.valueOf(**list.getWidth()**) + HTML_2 + value.toString() + HTML_3;
So if the panel resizes the list, wrapping remains correct.
Update
And the result looks like this:
It can be done even easier. You can create JList by consatructor with ListModel. In CustomListModel extends AbstractListModel, getElementAt() method can returns String with same html-formatted text. So this way do the same without cell renderer modification.
Related
I'm trying to display Strings in rows and columns. Since they differ in length and represent parts of a continuous text, I would like each component to only take up the size it needs to show the whole String.
So far, I managed to load the individual items into the list, limit the number of rows and make them wrap horizontally. I now wrote a custom ListCellRenderer as I figured this could solve my problem but it doesn't work the way I had hoped. The two Strings that are part of each item are neither centered nor displayed above one another (in fact, they are not readable at all because they seem to be drawn outside the cell) and they all have the same size. What am I doing wrong?
This is what my Renderer looks like:
public class ElementRenderer extends JPanel implements ListCellRenderer<Element> {
private Element element;
#Override
public Component getListCellRendererComponent(JList<? extends Element> list, Element value, int index,
boolean isSelected, boolean cellHasFocus) {
element = (Element) value;
if (element.getTag().equals("N/A"))
setEnabled(false);
else
setEnabled(true);
if (isSelected)
setBorder(BorderFactory.createLoweredSoftBevelBorder());
else
setBorder(BorderFactory.createRaisedSoftBevelBorder());
return this;
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (!isEnabled())
setBackground(Color.GRAY);
String word = element.getWord();
String tag = element.getTag();
FontMetrics fm = getFontMetrics(DataManager.getCurrentFont());
Rectangle bounds = getVisibleRect();
int y = bounds.y + ((bounds.height - 2*fm.getHeight())/2) + fm.getAscent();
g.drawString(word, bounds.x + (bounds.width - fm.stringWidth(word))/2, y);
g.drawString(tag, bounds.x + (bounds.width - fm.stringWidth(tag))/2, y + fm.getHeight());
}
#Override
public Dimension getPreferredSize() {
FontMetrics fm = getFontMetrics(DataManager.getCurrentFont());
int wordwidth = fm.stringWidth(element.getWord());
int tagwidth = fm.stringWidth(element.getTag());
if (tagwidth > wordwidth)
wordwidth = tagwidth;
return new Dimension(17+wordwidth, 3+fm.getHeight()*2);
}
Well the simplest solution I could find without messing with the JList's LayoutManager, nor its ListUI, is indeed to create a custom ListCellRenderer, but with the following logic:
The custom ListCellRenderer is a JPanel with a DefaultListCellRenderer in it.
The custom ListCellRenderer, as a JPanel, will fill the entire cell space.
The DefaultListCellRenderer will be centered inside our custom ListCellRenderer. We can achieve this by setting the LayoutManager of the JPanel to GridBagLayout. Since there will be added only a single component in it (ie the DefaultListCellRenderer which is a JLabel), it will be centered.
Follows sample code:
import java.awt.Component;
import java.awt.GridBagLayout;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.ListCellRenderer;
public class Main {
public static class DynamicWidthListCellRenderer extends JPanel implements ListCellRenderer {
private final DefaultListCellRenderer delegate;
public DynamicWidthListCellRenderer() {
super(new GridBagLayout());
delegate = new DefaultListCellRenderer();
super.add(delegate);
}
#Override
public Component getListCellRendererComponent(final JList list,
final Object value,
final int index,
final boolean isSelected,
final boolean cellHasFocus) {
delegate.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
//super.setBackground(delegate.getBackground()); //Test this out also...
return this;
}
}
public static void main(final String[] args) {
final JList<String> list = new JList<>(new String[]{"abc", "abcdefghijklmnop", "abcdefg", "a"});
list.setCellRenderer(new DynamicWidthListCellRenderer());
final JFrame frame = new JFrame("List variable width cells");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(list);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
And sample screenshot (with a selected element):
With a JCheckBox as an Editor in a JTable column, I would like to ignore mouseclicks in the space left and right of a CheckBox in a TableCell.
I have found a discussion from 2011 on the Oracle forum, but the problem was not solved there: https://community.oracle.com/thread/2183210
This is the hack I've realized so far, the interesting part begins atclass CheckBoxEditor:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.util.EventObject;
import javax.swing.DefaultCellEditor;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
/**
* Trying to set the Checkbox only if clicked directly on the box of the CheckBox. And ignore clicks on the
* remaining space of the TableCell.
*
* #author bobndrew
*/
public class JustCheckOnCheckboxTable extends JPanel
{
private static final int CHECK_COL = 1;
private static final Object[][] DATA = { { "One", Boolean.TRUE }, { "Two", Boolean.FALSE },
{ "Three", Boolean.TRUE }, { "Four", Boolean.FALSE }, { "Five", Boolean.TRUE },
{ "Six", Boolean.FALSE }, { "Seven", Boolean.TRUE }, { "Eight", Boolean.FALSE },
{ "Nine", Boolean.TRUE }, { "Ten", Boolean.FALSE } };
private static final String[] COLUMNS = { "Number", "CheckBox" };
private final DataModel dataModel = new DataModel( DATA, COLUMNS );
private final JTable table = new JTable( dataModel );
public JustCheckOnCheckboxTable()
{
super( new BorderLayout() );
this.add( new JScrollPane( table ) );
table.setRowHeight( table.getRowHeight() * 2 );
table.setPreferredScrollableViewportSize( new Dimension( 250, 400 ) );
TableColumn checkboxColumn = table.getColumnModel().getColumn( 1 );
checkboxColumn.setCellEditor( new CheckBoxEditor() );
}
private class DataModel extends DefaultTableModel
{
public DataModel( Object[][] data, Object[] columnNames )
{
super( data, columnNames );
}
#Override
public Class<?> getColumnClass( int columnIndex )
{
if ( columnIndex == 1 )
{
return getValueAt( 0, CHECK_COL ).getClass();
}
return super.getColumnClass( columnIndex );
}
}
class CheckBoxEditor extends DefaultCellEditor
{
private final JCheckBox checkBox;
public CheckBoxEditor()
{
super( new JCheckBox() );
checkBox = (JCheckBox) getComponent();
checkBox.setHorizontalAlignment( JCheckBox.CENTER );
System.out.println( "the checkbox has no size: " + checkBox.getSize() );
}
#Override
public boolean shouldSelectCell( final EventObject anEvent )
{
System.out.println( "\nthe checkbox fills the TableCell: " + checkBox.getSize() );
//Throws NullPointerException: System.out.println( checkBox.getIcon().getIconWidth() );
System.out.println( "always JTable :-( " + anEvent.getSource() );
MouseEvent ev =
SwingUtilities.convertMouseEvent( ((ComponentEvent) anEvent).getComponent(), (MouseEvent) anEvent,
getComponent() );
System.out.println( "Position clicked in TableCell: " + ev.getPoint() );
System.out.println( "always JCheckBox :-( " + getComponent().getComponentAt( ev.getPoint() ) );
Point middleOfTableCell = new Point( checkBox.getWidth() / 2, checkBox.getHeight() / 2 );
System.out.println( "middleOfTableCell: " + middleOfTableCell );
Dimension preferredSizeOfCheckBox = checkBox.getPreferredSize();
int halfWidthOfClickArea = (int) (preferredSizeOfCheckBox.getWidth() / 2);
int halfHeightOfClickArea = (int) (preferredSizeOfCheckBox.getHeight() / 2);
if ( (middleOfTableCell.getX() - halfWidthOfClickArea > ev.getX() || middleOfTableCell.getX() + halfWidthOfClickArea < ev.getX())
|| (middleOfTableCell.getY() - halfHeightOfClickArea > ev.getY() || middleOfTableCell.getY() + halfHeightOfClickArea < ev.getY()) )
{
stopCellEditing();
}
return super.shouldSelectCell( anEvent );
}
}
private static void createAndShowUI()
{
JFrame frame = new JFrame( "Direct click on CheckBox" );
frame.add( new JustCheckOnCheckboxTable() );
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible( true );
}
public static void main( String[] args )
{
java.awt.EventQueue.invokeLater( new Runnable()
{
#Override
public void run()
{
createAndShowUI();
}
} );
}
}
What I like about this solution:
all TableCell behaviour is correct: selecting, MouseOver, EditModes, ...
What I don't like about it:
the hardcoded size of the JCheckBox (int halfWidthOfClickArea)
where can I get the dimensions of an unpainted component?
Or are there better ways to achieve this Table and CheckBox-behaviour?
EDIT:
I changed the sourcecode following the advice of camickr and added a vertical hitzone for tables with higher RowHeights.
But so far I forgot to mention the main reason for my question... ;-)
I'm calling stopCellEditing() in the method shouldSelectCell(..).
Is it ok to decide there about more than the Cell-Selection?
where can I get the dimensions of an unpainted component?
System.out.println(checkBox.getPreferredSize() );
I think that stopping the selection of the actual in the shouldSelectCell() method is kind of a roundabout method of doing this, and converting mouse events seems weird.
Instead, a much cleaner approach would be to make the checkbox not fill the entire cell, so that it only gets selected if you press directly on the "checkbox" part of it.
This behavior can be accomplished by putting your JCheckbox inside a JPanel and center it without stretching it. To do this, we can make the JPanel's layout manager a GridBagLayout. See how when using GridBagLayout, the inner content doesn't stretch:
(from this StackOverflow answer)
So now, if you click in the empty space surrounding it, you will be clicking on a JPanel, so you won't be changing the JCheckBox's value.
The code for your CheckBoxEditor turns out like this in the end:
class CheckBoxEditor extends AbstractCellEditor implements TableCellEditor {
private static final long serialVersionUID = 1L;
private final JPanel componentPanel;
private final JCheckBox checkBox;
public CheckBoxEditor() {
componentPanel = new JPanel(new GridBagLayout()); // Use GridBagLayout to center the checkbox
componentPanel.setOpaque(false);
checkBox = new JCheckBox();
checkBox.setOpaque(false);
componentPanel.add(checkBox);
}
#Override
public Object getCellEditorValue() {
return Boolean.valueOf(checkBox.isSelected());
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
if (value instanceof Boolean) {
checkBox.setSelected(((Boolean) value).booleanValue());
} else if (value instanceof String) {
checkBox.setSelected(value.equals("true"));
}
if (isSelected) {
setForeground(table.getSelectionForeground());
setBackground(table.getSelectionBackground());
} else {
setForeground(table.getForeground());
setBackground(table.getBackground());
}
return componentPanel;
}
}
(Note that you can't be extending from a DefaultCellEditor anymore - in the code above, you're now having to extend from an AbstractCellEditor and implement a TableCellEditor).
I think that this version of your CheckBoxEditor does what you want - if you click in the empty space around the check box, nothing happens. The check box only becomes check if you click directly on it.
Also, by using a JPanel, you don't have to do any MouseEvent calculations and (to me, at least), the code looks much cleaner and it's easier to see what's going on.
EDIT1:
I read your comment and I found a solution: Why not leave the editor as it is, but then just make a cell renderer that derives from the DefaultTableCellRenderer? Then, in your CheckBoxEditor use the same borders and backgrounds as the renderer.
This should achieve the effect you want (I've moved common code into outer class methods so I don't have to repeat them):
private static void setCheckboxValue(JCheckBox checkBox, Object value) {
if (value instanceof Boolean) {
checkBox.setSelected(((Boolean) value).booleanValue());
} else if (value instanceof String) {
checkBox.setSelected(value.equals("true"));
}
}
private static void copyAppearanceFrom(JPanel to, Component from) {
if (from != null) {
to.setOpaque(true);
to.setBackground(from.getBackground());
if (from instanceof JComponent) {
to.setBorder(((JComponent) from).getBorder());
}
} else {
to.setOpaque(false);
}
}
class CheckBoxEditor extends AbstractCellEditor implements TableCellEditor {
private static final long serialVersionUID = 1L;
private final JPanel componentPanel;
private final JCheckBox checkBox;
public CheckBoxEditor() {
componentPanel = new JPanel(new GridBagLayout()); // Use GridBagLayout to center the checkbox
checkBox = new JCheckBox();
checkBox.setOpaque(false);
componentPanel.add(checkBox);
}
#Override
public Object getCellEditorValue() {
return Boolean.valueOf(checkBox.isSelected());
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
setCheckboxValue(checkBox, value);
TableCellRenderer renderer = table.getCellRenderer(row, column);
Component c = renderer.getTableCellRendererComponent(table, value, true, true, row, column);
copyAppearanceFrom(componentPanel, c);
return componentPanel;
}
}
class CheckBoxRenderer extends DefaultTableCellRenderer {
private static final long serialVersionUID = 1L;
private final JPanel componentPanel;
private final JCheckBox checkBox;
public CheckBoxRenderer() {
componentPanel = new JPanel(new GridBagLayout()); // Use GridBagLayout to center the checkbox
checkBox = new JCheckBox();
checkBox.setOpaque(false);
componentPanel.add(checkBox);
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
setCheckboxValue(checkBox, value);
copyAppearanceFrom(componentPanel, this);
return componentPanel;
}
}
Then, you have to set the renderer along with the editor in your constructor:
checkboxColumn.setCellEditor(new CheckBoxEditor());
checkboxColumn.setCellRenderer(new CheckBoxRenderer());
Here's a couple of screenshots comparing the two methods:
Your original method: JPanel and JCheckBox method:
I can barely see a difference :)
IMHO, I still think that just using the plain Java table API's is cleaner than calculating checks based on mouse pointer positions, but the choice is up to you.
I hope this helped!
EDIT2:
Also, if you want to be able to toggle using the spacebar I think that you can just add a key binding to the componentPanel in the CheckBoxEditor constructor:
class CheckBoxEditor extends AbstractCellEditor implements TableCellEditor {
// ...
public CheckBoxEditor() {
// ...
componentPanel.getInputMap().put(KeyStroke.getKeyStroke("SPACE"), "spacePressed");
componentPanel.getActionMap().put("spacePressed", new AbstractAction() {
private static final long serialVersionUID = 1L;
#Override
public void actionPerformed(ActionEvent e) {
checkBox.setSelected(!checkBox.isSelected());
}
});
// ...
}
// ...
}
I'm not sure if you can use Drag-and-Drop with boolean values. I tried dragging "true" onto the checkboxes in the original version, but nothing happened, so I don't think you have to worry about DnD.
Your code has a bug. Try pressing a cell editor's checkbox, drag outside of the cell and release the mouse button. Then click somewhere OUTSIDE the checkbox. The cell is being edited. I guess it means shouldSelectCell is not the right solution for you.
I think you should consider disabling the cell editors for the entire column, and implementing a custom MouseAdapter on th JTable to calculate the checkbox's location and change the model itself.
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;
}
}
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;
}
}
For my application, I want a Combo Box that displays its elements when dropped down as a Tree. Problem is, I'm not versed well enough in Swing to know how to go about doing this. At least without ending up writing a new widget from scratch, or something to that effect.
How would I do something like this without creating one from scratch?
I think I would implement this as a JTree component in a JViewPort, followed by an expansion button. When collapsed, it would look like a combo box. When you click the expansion button, the viewport would expand, allowing you to scroll and select a node in the JTree. When you selected the node, the view port would collapse back to only show the selected node and the expansion button.
Hey, guess what! This is your lucky day.
I've used this framework in the past. It is very complete. I didn't know they have this
already.
JIDE Soft
alt text http://img89.imageshack.us/img89/8324/combotreejj1.png
Is not too expensive, but it will take you some time to understand the API ( it is not that is complex, but they've created a LOT of new stuff )
Override the getListCellRendererComponent methode and create the components in level order.
For every tree level move the painted string 3 spaces to right.
Example:
1
. a
. b
2
. c
The original implementation you can look from
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 pet = petStrings[selectedIndex];
setIcon(icon);
if (icon != null) {
setText(pet);
setFont(list.getFont());
} else {
setUhOhText(pet + " (no image available)",
list.getFont());
}
return this;
}
You can create a ComboBoxEditor whose component ( returned by getEditorComponent ) is a JTree
Although you may have already tried that.
I don't know how would it look like. Post an screenshot if you make it work. :)
EDIT
I give it a quick dirty try. Its awful, but is a start.
alt text http://img120.imageshack.us/img120/2563/yiakxk2.png
Here's the code, for what is worth. :(
Probably you should start thinking in alternatives. What about a fake Combo that is a JButton without border when pushed a hidden panel will appear with the tree displayed.
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
public class ComboTree {
public static void main( String [] args ) {
JComboBox c = new JComboBox( new String [] { "Hello", "there"});
c.setModel( new CustomComboModel() );
c.setEditor( new TreeComboEditor() );
c.setRenderer( new TreeComboEditor() );
JFrame frame = new JFrame();
frame.add( c , BorderLayout.NORTH ) ;
frame.pack();
frame.setVisible( true );
}
}
class CustomComboModel implements ComboBoxModel {
public Object getSelectedItem() { return ":P"; }
public void setSelectedItem(Object anItem) {}
public void addListDataListener(ListDataListener l) {}
public Object getElementAt(int index) { return "at " + index ; }
public int getSize() { return 2; }
public void removeListDataListener(ListDataListener l) {}
}
class TreeComboEditor implements ComboBoxEditor, ListCellRenderer {
// Editor interface
public void addActionListener(ActionListener l) {}
public Component getEditorComponent() {
return new JTree() ;
}
public Object getItem() { return "";}
public void removeActionListener(ActionListener l) {}
public void selectAll() {}
public void setItem(Object anObject) {}
// Render interface
public Component getListCellRendererComponent(JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus) {
return new JTree();
}
}