JCombobox, Editor and Renderer related - java

As a JCombobox ListCellRenderer, I have a class like this one:
class ZComboBoxRenderer extends JPanel implements ListCellRenderer{
private ZGrid grid;
public ZComboBoxRenderer(ZGrid grid) {
setLayout(new BorderLayout());
this.grid = grid;
add(new JScrollPane(grid), BorderLayout.CENTER);
}
public ZGrid getGrid(){
return grid;
}
#Override
public Component getListCellRendererComponent(JList list, Object value,
int index, boolean isSelected, boolean cellHasFocus) {
grid.fetchSQL();
return this;
}
}
ZGrid here, extends JTable.
As a ListCellRendererComponent, I provide a JPanel which has a ZGrid inside, to the JCombobox. The problem is, in its list, this ZGrid is painting properly. But it is also being painted inside the Editor of JCombobox. I have uploaded an image to show this better.
Is there a way to separate Editor from List?
alt text http://img444.imageshack.us/img444/564/soex.jpg

From what I understand, you are implementing a custom Renderer for your JComboBox, and though it correctly renders the contents of your dropdown, it completely messes up the current value of the combo box.
I see two options at your disposal:
you can extend the UI component for your JComboBox and override the paint method to get a custom representation of your grid for your current value view. This would be a pretty quick proof of concept, but it poses issues as you would need to extend every UI (metal, windows, mac, etc) that you expect your app to be running with.
you can roll your own dropdown, and make it look like a JComboBox. This would not be that difficult to do as a POC as well, but the complexity here is to handle the different keyboard inputs that influence the selection and navigation around the combo box.

Related

Strange tab-order with editable JComboBoxes

I've got a JFrame that looks like this:
It's got two JTextFields on it, one JComboBox between them and a JPanel at the bottom (that you can't see).
One of the features of the JComboBox is that it can be given a custom editor. These implement the ComboBoxEditor interface. In each of the following three cases, the GUI looks exactly the same, and I would have expected them all to behave exactly the same:
I do not specify a custom editor, and use the default one.
I create a custom editor whose editor component is a JTextField.
I create a custom editor whose editor component is a JPanel with a JTextField on it (using a BorderLayout).
When the editor for the editable combo box is set to the default, pressing Tab moves the focus from the top JTextField into the editing area on the JComboBox and then into the other JTextField. If I create a custom editor whose editor component is a JTextField and otherwise does what you would expect, the same thing happens.
BUT, if I instead create a custom editor whose editor component is a JPanel with a JTextField added to it, the focus makes one additional stop. If the focus is on the top JTextField, then pressing Tab moves the focus to the little arrow at the right of the editable combo box before moving into the text area.
Why is this happening? The focus never moves on to the JPanel at the bottom of the frame, so why does the presence of a JPanel holding the JTextField affect the tab order on the combo box?
The following is an S(-ish)SCCE, which has one text field and all three types of combo box on it:
import javax.swing.*;
import java.awt.event.*;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
public class ComboBoxTest extends JFrame
{
private JPanel layoutPanel;
private JTextField meaninglessTextField;
private JComboBox defaultEditorComboBox;
private JComboBox textFieldEditorComboBox;
private JComboBox panelEditorComboBox;
public ComboBoxTest()
{
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
layoutPanel = new JPanel();
layoutPanel.setLayout(new BoxLayout(layoutPanel, BoxLayout.Y_AXIS));
meaninglessTextField = new JTextField();
defaultEditorComboBox = new JComboBox(); // Just a default JComboBox.
defaultEditorComboBox.setEditable(true);
textFieldEditorComboBox = new JComboBox();
textFieldEditorComboBox.setEditable(true);
textFieldEditorComboBox.setEditor(new TextFieldEditor());
panelEditorComboBox = new JComboBox();
panelEditorComboBox.setEditable(true);
panelEditorComboBox.setEditor(new PanelEditor());
layoutPanel.add(Box.createRigidArea(new Dimension(500,0)));
layoutPanel.add(meaninglessTextField);
layoutPanel.add(defaultEditorComboBox);
layoutPanel.add(textFieldEditorComboBox);
layoutPanel.add(panelEditorComboBox);
Container contentPane = getContentPane();
contentPane.add(layoutPanel, BorderLayout.CENTER);
pack();
}
public static void main(String[] args)
{
java.awt.EventQueue.invokeLater(new Runnable() {
#Override
public void run()
{
new ComboBoxTest().setVisible(true);
}
});
}
private class PanelEditor extends JPanel implements ComboBoxEditor
{
public JTextField inputTextField = new JTextField();
public PanelEditor()
{
setLayout(new BorderLayout());
add(inputTextField, BorderLayout.CENTER);
}
#Override
public String getItem()
{
return inputTextField.getText();
}
#Override
public void setItem(Object newText)
{
if (newText != null) {
inputTextField.setText(newText.toString());
}
else {
inputTextField.setText("");
}
}
#Override
public Component getEditorComponent()
{
return this;
}
#Override
public void removeActionListener(ActionListener listener)
{
inputTextField.removeActionListener(listener);
}
#Override
public void addActionListener(ActionListener listener)
{
inputTextField.addActionListener(listener);
}
#Override
public void selectAll()
{
inputTextField.selectAll();
}
}
private class TextFieldEditor extends PanelEditor implements ComboBoxEditor
{
// The same, except that the editor component is now just the JTextField
// rather than the whole panel.
public TextFieldEditor()
{
}
#Override
public JTextField getEditorComponent()
{
return inputTextField;
}
}
}
Note: this behaviour becomes a problem if I want to add a JLabel to the editor. Then I have to put a JPanel there to hold both the label and the text field.
The basic problem is that the combo's ui delegate can't handle compound editor components. There are several places where it assumes that the editor component is the target of whatever configuration it needs to do. The concrete mis-behaviour here is that it explicitly sets the editor's focusability to that of the combo itself
// in BasicComboBoxUI
protected void configureEditor() {
....
editor.setFocusable(comboBox.isFocusable());
....
]
The implications
by default, the panel's focusable is true because the combo's is true
forcing the panel's focusable to false in its constructor has no effect (the ui resets it later on and whenever the LAF is switched)
disabling combo's focusable disables the panel's as well
To fix on the level of the editor, you can implement its isFocusable to return false unconditionally:
private class PanelEditor extends JPanel implements ComboBoxEditor
public boolean isFocusable() {
return false;
}
...
}
An aside: for code hygiene, better not extend a view to implement its role as ComboBoxEditor (even though here you need a subclassed JPanel to avoid the problem, so it's arguably borderline :-) - instead implement the editor and let it use the tweaked panel.
Also beware that you might stumble into more problems with the compound editor (check the code of BasicComboUI for more places where it assumes a plain childless component), so you might consider not doing it at all but think of a different way to achieve your requirement.
Try this:
public PanelEditor()
{
// other code...
addFocusListener(new FocusAdapter()
{
#Override
public void focusGained(FocusEvent e)
{
inputTextField.requestFocusInWindow();
}
});
}
The focus isn't transferring to the JPanel; it's transferring to the JComboBox itself.
You can stop a component from receiving the focus by using its setFocusable method. If you add the line
setFocusable(false)
to the constructor of the PanelEditor in the example above, then the strange behaviour is still there, since the PanelEditor implements JPanel, so the setFocusable method of the JPanel overrides that of the JComboBox. Since the setFocusable method of a JPanel essentially does nothing, nothing changes.
If instead you add the line
panelEditorComboBox.setFocusable(false)
to the constructor of the JFrame itself then the JComboBox will not be able to receive the focus, but the JTextField inside the editor will. This isn't a perfect fix, since it would be better if the editor itself were responsible for turning off the focusability of the JComboBox, so you could always pass in the parent JComboBox as a parameter to the constructor of the editor, and have the focusability turned off there.
I don't know why the behaviour is different when you've got a JTextField as the editor. Some weird Swing thing.

How do I add a JLabel[] to a JTable?

I have an array of JLabels which I want to add to a JTable. I tried using
myJTable.add(myJLabelArray);
Hoping it would work, but it doesn't (Obviously, otherwise I wouldn't be here).
Can somebody please help?
Using add method is not the way to add components to a JTable. Components should never be added directly to a JTable or its TableModel.
JLabels are just Swing components that render text.
You can use a TableCellRenderer. Have a look at Editors & Renderers
You cannot just add myJTable.add(myJLabelArray). As Reimeus pointed out use Renderers
jTable1.getColumnModel().getColumn(0).setCellRenderer(new Renderer()); //set column1 with jlabel
Your render should extend DefaulttableCellRenderer
class Renderer extends DefaultTableCellRenderer {
JLabel lbl = new JLabel();
//ImageIcon icon = new ImageIcon(getClass().getResource("sample.png"));
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column) {
lbl.setText("hello");
//lbl.setIcon(icon);
return lbl;
}
}

Color of the dropdown control and border in an uneditable JComboBox

The background color of the selected item in an uneditable JComboBox is a sort of blue:
I know that you can change it to a different color, such as white, for example with the following code:
jComboBox1.setRenderer(new DefaultListCellRenderer() {
#Override
public void paint(Graphics g) {
setBackground(Color.WHITE);
setForeground(Color.BLACK);
super.paint(g);
}
});
That gives you something like this:
However, if you double-click on that combo-box, some of it turns gray (the part with the triangle and the border):
Is there a way to stop these parts from turning gray when you double-click on it?
Note that, if you call super.paint() first, the whole thing turns dark (including the part behind "Select..."), so that doesn't help.
Not sure what exactly OP is trying to achieve, but here is my JComboBox coloring recipe:
static public void main(String[] args) {
JFrame window = new JFrame("Coloring ComboBox");
window.setSize(170, 150);
window.setLocationRelativeTo(null);
window.setLayout(null);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//---textArea is just for focus switching
JTextArea textArea = new JTextArea();
textArea.setBounds(5, 5, 140, 25);
window.add(textArea);
UIManager.put("ComboBox.selectionBackground", Color.magenta); //---focused background color
//---see comboBox's UIDefaults for more tweaks
JComboBox<String> coloredCombo = new JComboBox<String>(new String[]{"Dog", "Cat", "Bird"});
coloredCombo.setEditable(false);
coloredCombo.setUI(new BasicComboBoxUI() {
#SuppressWarnings({"serial"})
#Override
protected ComboPopup createPopup() {
return new BasicComboPopup(coloredCombo) {
{
//---style popup anyway you like
this.setBorder(BorderFactory.createLineBorder(Color.green, 2));//---popup's border color
}
};
}
#Override
protected JButton createArrowButton() {
//---style arrow button anyway you like
JButton result = new JButton();
result.setBackground(Color.orange);//---button's color
return result;
}
});
coloredCombo.setBorder(BorderFactory.createLineBorder(Color.red, 2));//---border color
coloredCombo.setBackground(Color.yellow); //---not focused background color
coloredCombo.setRenderer(new ListCellRenderer<String>() {
#Override
public Component getListCellRendererComponent(JList<? extends String> list, String value, int index,
boolean isSelected, boolean cellHasFocus) {
JLabel result = new JLabel(value);
result.setOpaque(true);
result.setBackground(isSelected ? Color.cyan : Color.blue); //---item background color
return result;
}
});
coloredCombo.setBounds(5, 35, 140, 25);
window.add(coloredCombo);
window.setVisible(true);
}
Of course, this is just an axample, I recommend you to create a fancy custom class to reuse.
A couple things:
The appearance of the combobox (the display area, the arrow, the drop down) are LAF dependent. Your screen shots suggest WinXP. If you must support any other LAFs, be sure to test that as well, because what works for one LAF may not work for another. I have found this to be particularly true for JComboBoxes.
Like Twister suggests, changing the color by overriding the paint() method likely isn't the best way to do this. Just set the background/foreground color of the combobox itself. If you want to change the color of the dropdown itself (I'm not clear if you want to do this or not), then add a custom renderer that overrides getListCellRendererComponent to set the background/foreground.
public static class CustomRenderer extends DefaultListCellRenderer {
#Override
public Component getListCellRendererComponent(JList list, Object value,
int index, boolean isSelected, boolean cellHasFocus) {
super.getListCellRendererComponent(list, value, index, isSelected,
cellHasFocus);
setBackground(Color.WHITE);
setForeground(Color.BLACK);
return this;
}
}
The appearance of the grey triangle and border is because the combo box now has focus. You can just make it not focusable, and the coloring will go away. This may not be the behavior you want, however.
JComboBox combo = new JComboBox(new Object[]{"Dog", "Cat", "Bird"});
combo.setBackground(Color.WHITE);
combo.setForeground(Color.BLACK);
combo.setFocusable(false);
First, you should not set the foreground and background in the paint method. You should override the getListCellRendererComponent of your renderer in order to customize it. The default renderer change its aspect if you it has the focus or if it is selected. If you do not want those features reimplements the method.
Then if you add a line border to your renderer (setBorder(BorderFactory.createLineBorder(Color.black)) you will see that what is drawn is not part of your renderer but the combobox itself. So you might have to customize the UI
The swing code that calls getListCellRendererComponent, then calls setForeground and setBackground on the returned component (depending on the whether the component is selected and/or focused). I assume this is for some legacy behavior. Unfortunately, it defeats the purpose of my setting it in the renderer.
I've had some good results with this approach:
The code below circumvents changing the foreground and background by overriding the fg/bg setters to do nothing, then I just call the super implementations to set the colors I want.
public static class CustomRenderer extends DefaultListCellRenderer {
public Component getListCellRendererComponent(JList list, Object value,
int index, boolean isSelected, boolean cellHasFocus) {
super.getListCellRendererComponent(list, value, index, isSelected,
cellHasFocus);
super.setBackground(Color.WHITE);
super.setForeground(Color.BLACK);
return this;
}
public void setForeground(Color c) {}
public void setBackground(Color c) {}
}
Addendum:
The grey border is probably just that, a border. Try the same approach, but also override setBorder.

Background color of the selected item in an uneditable JComboBox

The background color of the selected item in an uneditable JComboBox is a sort of blue:
Is there any way to make this a different color, such as white, for example?
This should work
jComboBox1.setRenderer(new DefaultListCellRenderer() {
#Override
public void paint(Graphics g) {
setBackground(Color.WHITE);
setForeground(Color.BLACK);
super.paint(g);
}
});
The background assigned by the renderer is overriden by the selection background color of the JList that is used in the popup for the combo box. Check out the "paintCurrentValue" method of the BasicComboBoxUI class. So the workaround would be:
JComboBox comboBox = new JComboBox(...);
Object child = comboBox.getAccessibleContext().getAccessibleChild(0);
BasicComboPopup popup = (BasicComboPopup)child;
JList list = popup.getList();
list.setSelectionBackground(Color.RED);
This will affect the rendering of the popup as well. If you don't want it to affect the popup then you will need to create a custom renderer to specifically set the background of selected items.
Have you tried writing your own, custom, ListCellRenderer?
When that method is asked to provide a cell-rendering component you get the following arguments:
public Component getListCellRendererComponent(JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus) {
This works for me:
myComboBox.setBackground(Color.RED);
myComboBox.repaint();

How do I customize a JComboBox so that the pop-up is a JTree (instead of a list)?

I am trying to create a combo box so that I can put whatever control I prefer within the pop-up, in my specific case a JTree. Having a look at how the JComboBox is implement, the pop-up is really created by the UI delegate. The problem in changing that is that it would need to be re-implemented for each look and feel, which is something I do not want to do...
I basically want a component that it has the look and feel of a JComboBox (in the current look and feel) and the popup is a JTree (in the current look and feel).
What's the easiest way to do that?
JComboBox itself can't do what you want. If you're absolutely wedded to the concept of having it act like a JComboBox, you could make a JButton pop up a JPanel on click. Then the JPanel could have whatever you want inside it (JTree, etcetera).
I've had a go at producing something that would do something like this.
At first, I tried to implement something along the lines suggested by Varun, but it was proving to be a bit messy, and I get a little nervous when I start playing with ComponentUI objects (I'd rather leave that sort of thing to the L&F). If anyone has a good example of doing this, I'd be interested to see it.
So then I tried the button approach... and thought I would share the code with the SO community:
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import javax.swing.plaf.basic.BasicInternalFrameUI;
import javax.swing.plaf.metal.MetalComboBoxIcon;
public class MockJComboBox extends JPanel {
private boolean _isPoppedUp = false;
public MockJComboBox(String label, final JComponent toShow) {
setLayout(new BorderLayout());
JLabel jLabel = new JLabel(label);
jLabel.setBackground(Color.WHITE);
add(jLabel, BorderLayout.CENTER);
Icon icon = new MetalComboBoxIcon();
final JInternalFrame popup = new JInternalFrame(null, false, false, false, false);
final JPanel panel = new JPanel(new BorderLayout());
panel.add(new JScrollPane(toShow), BorderLayout.CENTER);
if(!(System.getProperty("os.name").startsWith("Mac OS"))){
BasicInternalFrameUI ui = (BasicInternalFrameUI) popup.getUI();
ui.getNorthPane().setPreferredSize(new Dimension(0,0));
}
popup.setBorder(null);
popup.setContentPane(panel);
popup.pack();
popup.setVisible(true);
final JButton dropDownButton = new JButton(icon);
dropDownButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
_isPoppedUp = !_isPoppedUp;
Container parent = getParent();
if (_isPoppedUp) {
popup.setLocation(panel.getX(), panel.getY() + panel.getHeight());
popup.setSize(panel.getWidth(), toShow.getHeight());
parent.add(popup);
} else {
parent.remove(popup);
parent.repaint();
}
}
});
add(dropDownButton, BorderLayout.EAST);
}
public boolean isPoppedUp() {
return _isPoppedUp;
}
}
If you spot any bugs or have suggestions on how to improve this code, I'd be grateful for your comments!
You just need to extend the BasicComboBoxUI and then override required methods like
public static ComponentUI createUI( JComponent c)
and
protected ComboPopup createPopup()
Creating a custom ComboPopup would require you to put some effort where you can't use the BasicComboPopUp because it extends JPopUpMenu
public class BasicComboPopup extends JPopupMenu implements ComboPopup
So in your case you may want to extend JTree and implement ComboPopup.
I'm doubt about "The problem in changing that is that it would need to be re-implemented for each look and feel" part. I don't think that there will be a problem of re-implementation.
The BasicComboPopup looks differently in different look and feels because it is a JPopupMenu which in turn will have UI delegates. So if you simply extend a JTree you should not be having problems with different look and feels.
The answer to use a button that pops up a JPanel with a JTree is correct. In response to Carcassi's comment, you can use a custom TableCellRenderer to change it so that it does not look like the traditional button.
Further web research revealed that Jidesoft, who describe themselves as "a professional Java and Swing component provider" produce a package called JIDE Grids, which includes AbstractComboBox - the description for which suggests it would do this.
However, it's a paid-for package and I haven't tried it... If anyone has used this, could they comment on the experience?

Categories

Resources