How to put jpg in a JComboBox? - java

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;
}
}

Related

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

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

JList highlight row issue

I'm trying to highlight all rows in a JList which have been "matched" with data elsewhere, and disable that row.
When I debug, I can see the correct data is set within cbNameIsMatched. However, what happens instead is that after I create the first match, each row I select in the JList is highlighted instead of the one with the "matched" index. The setEnabled is also setting for all items from the end of the list up to where I click in the list.
class MyListCellRenderer extends JLabel implements ListCellRenderer
{
public MyCopybookListCellRenderer()
{
setOpaque(false);
}
#Override
public Component getListCellRendererComponent(JList paramList, Object value,
int index, boolean isSelected, boolean cellHasFocus)
{
setText(value.toString());
if(isSelected)
{
setOpaque(true);
}
else
{
setOpaque(false);
}
if(cbNameIsMatched[index]==2)
{
setBackground(Color.YELLOW);
setEnabled(false);
}
myList.repaint();
return this;
}
You would do better extending DefaultListCellRenderer over JLabel as the former already takes care of everything and all you have to do is change the specific things you need. It gives you a "safety net" for the cases you didn't touch.
public class GetterText extends JFrame {
GetterText() {
JList<String> list = new JList<>(new String[]{"AAAA", "BBBB", "CCCC", "DDDD"});
list.setCellRenderer(new MyListCellRenderer());
getContentPane().add(list);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
pack();
setVisible(true);
}
private class MyListCellRenderer extends DefaultListCellRenderer {
#Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
label.setOpaque(isSelected); // Highlight only when selected
label.setOpaque(true); // Highlight always
if(index == 2) { // I faked a match for the second index, put you matching condition here.
label.setBackground(Color.YELLOW);
label.setEnabled(false);
}
return label;
}
}
public static void main(String[] args) {
new GetterText();
}
}
Edit: elaboration on the use of super
super gives a reference to the superclass which you can use to call its methods. When overriding a method of the superclass, calling that superclass's method means "do what you did before", or, "retain the implementation". This is good because you start from a point where everything works as the default and what you have left to do is tweak specific behaviors without the need to take care for all the others.
In this case, if you return label after
JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
you would get the default behavior because it is the one being used by the extending DefaultListCellRenderer. The arguments are the same in order to get the same result as the superclass would give. Then I go on to change this default JLabel. What you do is create a new JLabel with no default behavior. Note that I "cheat" here by knowing that the Component returned by DefaultListCellRenderer.getListCellRendererComponent is a JLabel.
ListCellRenderer acts like a stamp. The same instance with the same data is reused. You have to set the correct color every time.
if(cbNameIsMatched[index]==2)
{
setBackground(Color.YELLOW);
setEnabled(false);
}
else
{
setBackground(your default color);
setEnabled(true);
}

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:

Word wrap in JList items

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.

How do I combine a Combo Box with a Tree in Swing?

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();
}
}

Categories

Resources