JComboBox 单元格渲染器失败,外观 Windows
JComboBox Cell Renderer Fails with Windows Look and Feel
我正在编写一个使用本地系统外观的 Java 应用程序。在程序中有一个 ListCellRenderer,它呈现彩色点(自定义 JComponment),然后是给定对象的一些文本。这在使用 Swing 的默认 Metal 外观时效果很好。
但是,当我使用 Windows 外观时,单元格在下拉列表中正确呈现,但所选项目(当用户未进行选择时显示的项目)一个不同的选项)只呈现文本并忽略彩色点。如果我更改渲染器以设置字体,则在下拉列表和所选项目中都会观察到正确的字体,因此我知道正在使用单元格渲染器,至少是部分使用。
我在网络上阅读了一些关于导致此类问题的不同 LAF 的帖子,但还没有遇到任何人在讨论我的特定问题。
万一有人好奇这里是代码:
.
@Override
public Component getListCellRendererComponent(JList<? extends ColoredDisplayable> jlist, ColoredDisplayable e, int i, boolean isSelected, boolean hasFocus) {
JPanel cell = new JPanel(new GridBagLayout());
cell.setOpaque(false);
JLabel label = new JLabel(e.getDisplayString());
label.setOpaque(false);
label.setBorder(BorderFactory.createEmptyBorder(1, 4, 1, 4));
label.setHorizontalAlignment(JLabel.LEFT);
Color deselectedBackground = cell.getBackground();
Color deselectedTextColor = cell.getForeground();
// LAYOUT COMPONENTS
// Dot
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.insets = INSETS;
gbc.anchor = GridBagConstraints.LINE_START;
gbc.weightx = 0.0f;
gbc.fill = GridBagConstraints.NONE;
cell.add(new Dot(e.getColor()), gbc);
// Label
gbc.gridx = 1;
gbc.weightx = 1.0f;
gbc.fill = GridBagConstraints.HORIZONTAL;
cell.add(label, gbc);
if (isSelected){
cell.setOpaque(true);
cell.setBackground(MetalLookAndFeel.getTextHighlightColor());
} else {
cell.setBackground(deselectedBackground);
cell.setForeground(deselectedTextColor);
}
return cell;
}
此外,这里是自定义组件的代码,如果有人想尝试一下,看看我是否只是在做一些愚蠢的事情:
public class Dot extends JComponent {
/** The size of the dot. */
private static final int SIZE = 10;
/** The size of the dot. */
private static final int PAD = 4;
private static final Dimension DIM = new Dimension(SIZE + PAD, SIZE + PAD);
/** The Color to render the dot. */
private final Color m_color;
/** The Dot itself. */
private static final Ellipse2D.Double DOT = new Ellipse2D.Double(PAD / 2, PAD / 2, SIZE, SIZE);
/**
* Creates a dot of the specified color.
* @param color the color to make the dot.
*/
public Dot(Color color) {
m_color = color;
}
@Override
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D)g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(m_color);
g2d.fill(DOT);
}
@Override
public Dimension getPreferredSize() {
return DIM;
}
}
编辑: 我刚刚在 Ubuntu 12.04 上测试了这个,单元格渲染器在那里按预期工作(尽管 JCombobox 没有像它那样渲染它的外边框如果没有应用自定义渲染器)。
编辑: 随着我越来越深入地研究这个问题,似乎 JComboBox 上的 setEditor 方法可能有些东西,但是当不可编辑时,渲染器应该是用作方法状态的 javadoc:
Sets the editor used to paint and edit the selected item in the JComboBox field. The editor is used only if the receiving JComboBox is editable. If not editable, the combo box uses the renderer to paint the selected item.
这似乎不是我所看到的行为。我必须做什么才能让 Windows LAF 的用户观察到我的单元格渲染器的所有部分?
深入研究,我发现对于 Windows LAF,我需要设置 ComboBoxEditor 并将 JComboBox 设置为可编辑,以便所选单元格正确呈现。
在我看来,这似乎是 bug/unintended 特定于 Windows 外观的函数,因为 JComboBox 的 setEditor 方法的 API 声明当不可编辑时,渲染器将是使用 - 在默认 Metal LAF 和 Ubuntu.
中都是 运行
除此之外,我不能像在 ListCellRenderer 中那样每次调用 getEditorComponent 时只让编辑器 return 一个新单元格。我认为这是有道理的。
此网站提供了一个如何创建编辑器的示例(尽管有点乏味):
JComboBox 和 BasicComboBox 的 API 也很有帮助:
最后,我的编辑代码:
public class ColoredDisplayableComboBoxEditor extends BasicComboBoxEditor {
private ColoredDisplayable m_cd = null;
private static final Insets INSETS = new Insets(3, 1, 3, 1);
private final JPanel m_cell;
private final JLabel m_label;
private final Dot m_dot;
public ColoredDisplayableComboBoxEditor() {
// INITIALIZE
// Panel
m_cell = new JPanel(new GridBagLayout());
m_cell.setOpaque(false);
// Label
m_label = new JLabel();
m_label.setOpaque(false);
m_label.setBorder(BorderFactory.createEmptyBorder(0, 4, 0, 4));
m_label.setHorizontalAlignment(JLabel.LEFT);
// Dot
m_dot = new Dot(Color.BLACK);
// LAYOUT
// Dot
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.insets = INSETS;
gbc.anchor = GridBagConstraints.LINE_START;
gbc.weightx = 0.0f;
gbc.fill = GridBagConstraints.NONE;
m_cell.add(m_dot, gbc);
// Label
gbc.gridx = 1;
gbc.weightx = 1.0f;
gbc.fill = GridBagConstraints.HORIZONTAL;
m_cell.add(m_label, gbc);
}
@Override
public Component getEditorComponent() {
return m_cell;
}
@Override
public Object getItem() {
System.out.println("getting item.");
return m_cd;
}
@Override
public void setItem(Object item) {
System.out.println("setting item.");
if (item instanceof ColoredDisplayable) {
ColoredDisplayable cd = (ColoredDisplayable)item;
if (!cd.equals(m_cd)) {
System.out.println("--item actually set.");
m_cd = cd;
m_label.setText(m_cd.getDisplayString());
m_dot.setColor(m_cd.getColor());
}
} else {
throw new IllegalArgumentException("Parameter item must be a ColoredDisplayable.");
}
}
}
我正在编写一个使用本地系统外观的 Java 应用程序。在程序中有一个 ListCellRenderer,它呈现彩色点(自定义 JComponment),然后是给定对象的一些文本。这在使用 Swing 的默认 Metal 外观时效果很好。
但是,当我使用 Windows 外观时,单元格在下拉列表中正确呈现,但所选项目(当用户未进行选择时显示的项目)一个不同的选项)只呈现文本并忽略彩色点。如果我更改渲染器以设置字体,则在下拉列表和所选项目中都会观察到正确的字体,因此我知道正在使用单元格渲染器,至少是部分使用。
我在网络上阅读了一些关于导致此类问题的不同 LAF 的帖子,但还没有遇到任何人在讨论我的特定问题。
万一有人好奇这里是代码:
.
@Override
public Component getListCellRendererComponent(JList<? extends ColoredDisplayable> jlist, ColoredDisplayable e, int i, boolean isSelected, boolean hasFocus) {
JPanel cell = new JPanel(new GridBagLayout());
cell.setOpaque(false);
JLabel label = new JLabel(e.getDisplayString());
label.setOpaque(false);
label.setBorder(BorderFactory.createEmptyBorder(1, 4, 1, 4));
label.setHorizontalAlignment(JLabel.LEFT);
Color deselectedBackground = cell.getBackground();
Color deselectedTextColor = cell.getForeground();
// LAYOUT COMPONENTS
// Dot
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.insets = INSETS;
gbc.anchor = GridBagConstraints.LINE_START;
gbc.weightx = 0.0f;
gbc.fill = GridBagConstraints.NONE;
cell.add(new Dot(e.getColor()), gbc);
// Label
gbc.gridx = 1;
gbc.weightx = 1.0f;
gbc.fill = GridBagConstraints.HORIZONTAL;
cell.add(label, gbc);
if (isSelected){
cell.setOpaque(true);
cell.setBackground(MetalLookAndFeel.getTextHighlightColor());
} else {
cell.setBackground(deselectedBackground);
cell.setForeground(deselectedTextColor);
}
return cell;
}
此外,这里是自定义组件的代码,如果有人想尝试一下,看看我是否只是在做一些愚蠢的事情:
public class Dot extends JComponent {
/** The size of the dot. */
private static final int SIZE = 10;
/** The size of the dot. */
private static final int PAD = 4;
private static final Dimension DIM = new Dimension(SIZE + PAD, SIZE + PAD);
/** The Color to render the dot. */
private final Color m_color;
/** The Dot itself. */
private static final Ellipse2D.Double DOT = new Ellipse2D.Double(PAD / 2, PAD / 2, SIZE, SIZE);
/**
* Creates a dot of the specified color.
* @param color the color to make the dot.
*/
public Dot(Color color) {
m_color = color;
}
@Override
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D)g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(m_color);
g2d.fill(DOT);
}
@Override
public Dimension getPreferredSize() {
return DIM;
}
}
编辑: 我刚刚在 Ubuntu 12.04 上测试了这个,单元格渲染器在那里按预期工作(尽管 JCombobox 没有像它那样渲染它的外边框如果没有应用自定义渲染器)。
编辑: 随着我越来越深入地研究这个问题,似乎 JComboBox 上的 setEditor 方法可能有些东西,但是当不可编辑时,渲染器应该是用作方法状态的 javadoc:
Sets the editor used to paint and edit the selected item in the JComboBox field. The editor is used only if the receiving JComboBox is editable. If not editable, the combo box uses the renderer to paint the selected item.
这似乎不是我所看到的行为。我必须做什么才能让 Windows LAF 的用户观察到我的单元格渲染器的所有部分?
深入研究,我发现对于 Windows LAF,我需要设置 ComboBoxEditor 并将 JComboBox 设置为可编辑,以便所选单元格正确呈现。
在我看来,这似乎是 bug/unintended 特定于 Windows 外观的函数,因为 JComboBox 的 setEditor 方法的 API 声明当不可编辑时,渲染器将是使用 - 在默认 Metal LAF 和 Ubuntu.
中都是 运行除此之外,我不能像在 ListCellRenderer 中那样每次调用 getEditorComponent 时只让编辑器 return 一个新单元格。我认为这是有道理的。
此网站提供了一个如何创建编辑器的示例(尽管有点乏味):
JComboBox 和 BasicComboBox 的 API 也很有帮助:
最后,我的编辑代码:
public class ColoredDisplayableComboBoxEditor extends BasicComboBoxEditor {
private ColoredDisplayable m_cd = null;
private static final Insets INSETS = new Insets(3, 1, 3, 1);
private final JPanel m_cell;
private final JLabel m_label;
private final Dot m_dot;
public ColoredDisplayableComboBoxEditor() {
// INITIALIZE
// Panel
m_cell = new JPanel(new GridBagLayout());
m_cell.setOpaque(false);
// Label
m_label = new JLabel();
m_label.setOpaque(false);
m_label.setBorder(BorderFactory.createEmptyBorder(0, 4, 0, 4));
m_label.setHorizontalAlignment(JLabel.LEFT);
// Dot
m_dot = new Dot(Color.BLACK);
// LAYOUT
// Dot
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.insets = INSETS;
gbc.anchor = GridBagConstraints.LINE_START;
gbc.weightx = 0.0f;
gbc.fill = GridBagConstraints.NONE;
m_cell.add(m_dot, gbc);
// Label
gbc.gridx = 1;
gbc.weightx = 1.0f;
gbc.fill = GridBagConstraints.HORIZONTAL;
m_cell.add(m_label, gbc);
}
@Override
public Component getEditorComponent() {
return m_cell;
}
@Override
public Object getItem() {
System.out.println("getting item.");
return m_cd;
}
@Override
public void setItem(Object item) {
System.out.println("setting item.");
if (item instanceof ColoredDisplayable) {
ColoredDisplayable cd = (ColoredDisplayable)item;
if (!cd.equals(m_cd)) {
System.out.println("--item actually set.");
m_cd = cd;
m_label.setText(m_cd.getDisplayString());
m_dot.setColor(m_cd.getColor());
}
} else {
throw new IllegalArgumentException("Parameter item must be a ColoredDisplayable.");
}
}
}