This article explains how to add separators to the list of a JComboBox.
Generally there are two different approaches - model dependent and model independent. First we want to take a look at the model independent solution.
Model independent solution
The basic idea is to create a special renderer which uses a JPanel as container, a JLabel for the text and a JSeparator at the bottom of the panel. An array which takes the index numbers for the separators is passed to the constructor of the renderer .
JComboBox combo = new JComboBox(new String[]{"Item 1", "Item 2", "Item 3", "Item 1-1", "Item 1-2", "Item 2-1"});
combo.setRenderer(new SeparatorRenderer(combo.getRenderer(), 2, 4));
...
//
// Renderer Class
//
private static class SeparatorRenderer implements ListCellRenderer
{
private ListCellRenderer delegate;
private JPanel container;
private JLabel label;
private HashSet<Integer> separatorIndexes;
private boolean cellRendererSelectionBackgroundEnabled;
private boolean isSelected;
public SeparatorRenderer(ListCellRenderer delegate, Integer... separatorIndexes)
{
this.delegate = delegate;
this.separatorIndexes = new HashSet<Integer>(Arrays.asList(separatorIndexes));
cellRendererSelectionBackgroundEnabled = SyntheticaLookAndFeel.getBoolean("Synthetica.cellRenderer.selectionBackground.enabled", null, false);
container = new JPanel();
label = new JLabel()
{
protected void paintComponent(Graphics g)
{
//required for special background rendering enabled themes like SyntheticaClassy
if (cellRendererSelectionBackgroundEnabled && isSelected)
{
String imagePath = SyntheticaLookAndFeel.getString("Synthetica.comboBox.listSelectionBackground", null);
Insets sInsets = SyntheticaLookAndFeel.getInsets("Synthetica.comboBox.listSelectionBackground.insets", null, false);
ImagePainter imagePainter = new ImagePainter(g, 0, 0, getWidth(), getHeight(), imagePath, sInsets, sInsets, ImagePainter.STRETCHED, ImagePainter.STRETCHED);
imagePainter.draw();
}
super.paintComponent(g);
}
};
container.setLayout(new BorderLayout());
container.setOpaque(false);
container.add(label);
container.add(new JSeparator(), BorderLayout.SOUTH);
}
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus)
{
JLabel c = (JLabel)delegate.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (index >= 0 && separatorIndexes.contains(index))
{
this.isSelected = isSelected;
label.setText(c.getText());
label.setForeground(c.getForeground());
label.setBackground(c.getBackground());
label.setBorder(c.getBorder());
label.setOpaque(isSelected && !cellRendererSelectionBackgroundEnabled);
return container;
}
return c;
}
}
Because themes like SyntheticaClassy do some special background rendering for selected items, we have to override #paintComponent() of our custom renderer label and add related painting code - this is not needed for regular themes.
Model dependent solution
In the model dependent approach separator placeholders are part of the ComboBox model - we use the minus character in the demo application to represent separators. We also have to override #setSelectedItem() to make the separator unselectable. The renderer simply returns a JSeparator instance instead of the separator placeholder.
private static final String SEPARATOR_MARK = "-";
...
JYComboBox combo = new JYComboBox()
{
@Override
public void setSelectedItem(Object item)
{
if (!SEPARATOR_MARK.equals(item.toString()))
super.setSelectedItem(item);
}
};
combo.setModel(new DefaultComboBoxModel(new String[]{"Item 1", "Item 2", "Item 3", SEPARATOR_MARK, "Item 1-1", "Item 1-2", SEPARATOR_MARK, "Item 2-1"}));
final ListCellRenderer l = combo.getRenderer();
combo.setRenderer(new ListCellRenderer()
{
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus)
{
Component c = l.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (index >= 0 && SEPARATOR_MARK.equals(value.toString()))
return new JSeparator();
return c;
}
});
This works fine except the fact that the key binding (up/down) doesn't work as expected. To solve the issue we have to create and install a separator aware UI-delegate. The base class (SyntheticaJYComboBoxUI) is part of our SyntheticaAddons package.
comboBox.setUI(new SeparatorComboBoxUI());
...
//
//Separator aware UI-delegate class
//
private static class SeparatorComboBoxUI extends SyntheticaJYComboBoxUI
{
@Override
protected void selectNextPossibleValue()
{
int i = comboBox.getSelectedIndex();
if (i < comboBox.getModel().getSize()-1)
{
if (SEPARATOR_MARK.equals(comboBox.getItemAt(i+1)))
i++;
comboBox.setSelectedIndex(i+1);
comboBox.repaint();
}
}
@Override
protected void selectPreviousPossibleValue()
{
int i = comboBox.getSelectedIndex();
if (i > 0)
{
if (SEPARATOR_MARK.equals(comboBox.getItemAt(i-1)))
i--;
comboBox.setSelectedIndex(i-1);
comboBox.repaint();
}
}
}
Download Demo Sourcecode
Related Links