Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8339378

AquaComboBoxButton causes content to overlap the arrow button in JComboBox.

XMLWordPrintable

      ADDITIONAL SYSTEM INFORMATION :
      MacOS 14.6.1, Java 21

      A DESCRIPTION OF THE PROBLEM :
      When using JComboBox within a JTable with the macOS Look and Feel, the drop-down arrow and value rendering inside the combo box button are misaligned. This issue causes elements like icons or ellipses (three dots) to overlap with the arrow button, leading to a visually incorrect user interface.


      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Run the provided Java source code on a macOS system with the default macOS Look and Feel.
      Observe the rendering of the JComboBox editors within the table.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      The arrow button in the JComboBox should be clearly visible and not obstructed by the content (text or icon) within the combo box button.
      ACTUAL -
      In the first column, ellipsis coming from the truncated text overlap with the arrow button, causing visual clutter.
      In the second column, the custom icon overlaps with the arrow button, making both elements hard to distinguish.

      ---------- BEGIN SOURCE ----------
      import java.awt.BorderLayout;
      import java.awt.Color;
      import java.awt.Component;
      import java.awt.Font;
      import java.awt.FontMetrics;
      import java.awt.Graphics;

      import javax.swing.DefaultCellEditor;
      import javax.swing.DefaultListCellRenderer;
      import javax.swing.Icon;
      import javax.swing.JComboBox;
      import javax.swing.JFrame;
      import javax.swing.JList;
      import javax.swing.JPanel;
      import javax.swing.JScrollPane;
      import javax.swing.JTable;
      import javax.swing.SwingUtilities;
      import javax.swing.table.DefaultTableModel;

      public class ComboBoxInTable {
          public static void main(String[] args) {
              SwingUtilities.invokeLater(() -> {
                  JFrame frame = new JFrame("Table with JComboBox Editor");
                  frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                  frame.setSize(800, 200);

                  DefaultTableModel model = new DefaultTableModel(new Object[][]{{"Option 1", "Option 2", "Option 3"}},
                          new Object[]{"Text", "Icons", "Workaround"});
                  JTable table = new JTable(model);

                  table.getColumnModel().getColumn(0).setCellEditor(createEditorWithText());
                  table.getColumnModel().getColumn(1).setCellEditor(createEditorWithIcons());
                  table.getColumnModel().getColumn(2).setCellEditor(createEditorWithWorkAround());
                  table.setRowHeight(table.getRowHeight() + 10);
                  // Add table to frame
                  frame.add(new JScrollPane(table), BorderLayout.CENTER);

                  // Display the frame
                  frame.setVisible(true);
              });
          }


          private static DefaultCellEditor createEditorWithText() {
              // Set up the JComboBox as an editor for the single cell
              String[] comboBoxItems = {"Option 1", "Option 2", "Option 3"};
              JComboBox<String> comboBox = new JComboBox<>(comboBoxItems);
              comboBox.setEditable(true);
              DefaultCellEditor cellEditor = new DefaultCellEditor(comboBox);
              cellEditor.setClickCountToStart(1);
              return cellEditor;
          }

          private static DefaultCellEditor createEditorWithIcons() {
              // Set up the JComboBox as an editor for the single cell
              String[] comboBoxItems = {"Option 1", "Option 2", "Option 3"};
              JComboBox<String> comboBox = new JComboBox<>(comboBoxItems);
              comboBox.setEditable(true);
              DefaultListCellRenderer comboboxItemRendererWithIcon = new DefaultListCellRenderer() {
                  private static final long serialVersionUID = 1L;

                  @Override
                  public Component getListCellRendererComponent(JList<?> list, Object value,
                          int index, boolean isSelected, boolean cellHasFocus) {
                      Component me = this;
                      Object displayedValue = index == -1 ? new Icon() {

                          @Override
                          public void paintIcon(Component c, Graphics g, int x, int y) {
                              g.setColor(Color.GREEN);
                              g.fillRect(x, y, getIconWidth(), getIconHeight());
                              g.setColor(Color.WHITE);
                              Font font = getFont();
                              FontMetrics fontMetrics = g.getFontMetrics(font);
                              g.drawString("abc", x, y + fontMetrics.getAscent());
                         }

                          @Override
                          public int getIconWidth() {
                              return me.getWidth();
                           }

                          @Override
                          public int getIconHeight() {
                              return me.getHeight();
                          }
                      } : value;
                      Component listCellRendererComponent = super.getListCellRendererComponent(list, displayedValue, index, isSelected, cellHasFocus);
                      return listCellRendererComponent;
                  }

              };
              comboBox.setRenderer(comboboxItemRendererWithIcon);
              DefaultCellEditor cellEditor = new DefaultCellEditor(comboBox);
              cellEditor.setClickCountToStart(1);
              return cellEditor;
          }

          private static final JPanel TRANSPARENT_RENDERER = new JPanel();
          static {
              TRANSPARENT_RENDERER.setOpaque(false);
          }
         private static DefaultCellEditor createEditorWithWorkAround() {
              // Set up the JComboBox as an editor for the single cell
              String[] comboBoxItems = {"Option 1", "Option 2", "Option 3"};
              JComboBox<String> comboBo = new JComboBox<>(comboBoxItems);
              comboBo.setEditable(true);
              DefaultListCellRenderer comboboxItemRendererWithIcon = new DefaultListCellRenderer() {
                  private static final long serialVersionUID = 1L;

                  @Override
                  public Component getListCellRendererComponent(JList<?> list, Object value,
                          int index, boolean isSelected, boolean cellHasFocus) {
                      if(index == -1) {
                          return TRANSPARENT_RENDERER;
                      }

                      return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
                  }
              };
              comboBo.setRenderer(comboboxItemRendererWithIcon);
              DefaultCellEditor cellEditor = new DefaultCellEditor(comboBo);
              cellEditor.setClickCountToStart(1);
              return cellEditor;
          }
      }
      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      A temporary workaround is to render a transparent component when the index is -1 to avoid overlapping the arrow button, as demonstrated in the third column of the example.

      FREQUENCY : always


            dnguyen Damon Nguyen
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Created:
              Updated: