How to prevent JComboBox from becoming unresponsive when using a custom ListCellRenderer

前端 未结 2 1536
梦谈多话
梦谈多话 2020-12-12 01:41

I am making a font chooser using JComboBox and a custom ListCellRenderer. I want the JComboBox to display all available fonts, with ea

相关标签:
2条回答
  • 2020-12-12 02:14

    What happens is that the combo's internals try to find the preferred size dynamically. For doing so, it loops through all items in the list, feeds the renderer with the items to measure the rendering component's preferred size.

    You can prevent that by setting a prototypeValue for measuring, then the size is measured once using that prototype

     comboBox.setPrototypeDisplayValue(sampleFont);
    

    Edit: as @Boro detected, that's not enough - it sets the prototype for the comboBox itself only, not for the list in the popup (as it should, how crazily buggy can that ... possibly be). To hack around, we have to manually set it, here's a code snippet to play with

    public class ComboWithPrototype {
    
        private JComponent createContent() {
            final Font[] systemFonts = GraphicsEnvironment
                    .getLocalGraphicsEnvironment().getAllFonts();
    
            final JComboBox box = new JComboBox();
            box.setRenderer(new ComboBoxRenderer());
            box.setPrototypeDisplayValue(systemFonts[0]);
            Accessible a = box.getUI().getAccessibleChild(box, 0);
            if (a instanceof javax.swing.plaf.basic.ComboPopup) {
                JList popupList = ((javax.swing.plaf.basic.ComboPopup) a).getList();
                // route the comboBox' prototype to the list
                // should happen in BasicComboxBoxUI
                popupList.setPrototypeCellValue(box.getPrototypeDisplayValue());
            }
            Action action = new AbstractAction("set model") {
    
                @Override
                public void actionPerformed(ActionEvent e) {
                    box.setModel(new DefaultComboBoxModel(systemFonts));
                }
            };
            JComponent panel = new JPanel(new BorderLayout());
            panel.add(box);
            panel.add(new JButton(action), BorderLayout.SOUTH);
            return panel;
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    JFrame frame = new JFrame("");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.add(new ComboWithPrototype().createContent());
                    frame.setLocationRelativeTo(null);
                    frame.pack();
                    frame.setVisible(true);
                }
            });
        }
    

    Custom ListCellRenderer (slightly changed, to expect items of type Font)

    private class ComboBoxRenderer extends DefaultListCellRenderer {
    
        private Font baseFont = new JLabel("Test").getFont();
    
        @Override
        public Component getListCellRendererComponent(JList list, Object value,
                int index, boolean isSelected, boolean cellHasFocus) {
    
            super.getListCellRendererComponent(list, value, index, isSelected,
                    cellHasFocus);
            if (value instanceof Font) {
    
                Font font = (Font) value;
                setFont(new Font(font.getName(), baseFont.getStyle(), baseFont.getSize())); 
                setText(font.getName());
            }
    
            return this;
        }
    }
    
    0 讨论(0)
  • 2020-12-12 02:29

    @kleopatra than you for notice me but setPrototypeDisplayValue look like as lazy choise, my amedment

    import java.awt.BorderLayout;
    import java.awt.Component;
    import java.awt.Dimension;
    import java.awt.Font;
    import java.awt.GraphicsEnvironment;
    import java.awt.Point;
    import java.awt.Rectangle;
    import java.awt.event.ItemEvent;
    import java.awt.event.ItemListener;
    import javax.swing.DefaultListCellRenderer;
    import javax.swing.JComboBox;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JList;
    import javax.swing.JPopupMenu;
    import javax.swing.JScrollPane;
    import javax.swing.JViewport;
    import javax.swing.plaf.basic.BasicComboBoxRenderer;
    
    public class SystemFontDisplayer extends JFrame {
    
        private static final long serialVersionUID = 1L;
        private JComboBox fontsBox;
    
        public SystemFontDisplayer() {
    
            GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
            String[] fontFamilyNames = ge.getAvailableFontFamilyNames();
            fontsBox = new JComboBox(fontFamilyNames);
            fontsBox.setSelectedItem(0);
            fontsBox.setRenderer(new ComboRenderer(fontsBox));
            fontsBox.addItemListener(new ItemListener() {
    
                @Override
                public void itemStateChanged(ItemEvent e) {
                    if (e.getStateChange() == ItemEvent.SELECTED) {
                        final String fontName = fontsBox.getSelectedItem().toString();
                        fontsBox.setFont(new Font(fontName, Font.PLAIN, 16));
                    }
                }
            });
            fontsBox.setSelectedItem(0);
            fontsBox.getEditor().selectAll();
            add(fontsBox, BorderLayout.CENTER);
            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            setPreferredSize(new Dimension(400, 60));
            setLocation(200, 105);
            pack();
    
            java.awt.EventQueue.invokeLater(new Runnable() {
    
                @Override
                public void run() {
                    fontsBox.setPopupVisible(true);
                    fontsBox.setPopupVisible(false);
                }
            });
            setVisible(true);
        }
    
        public static void main(String arg[]) {
            java.awt.EventQueue.invokeLater(new Runnable() {
    
                @Override
                public void run() {
                    SystemFontDisplayer systemFontDisplayer = new SystemFontDisplayer();
                }
            });
        }
    
        private class ComboRenderer extends BasicComboBoxRenderer {
    
            private static final long serialVersionUID = 1L;
            private JComboBox comboBox;
            final DefaultListCellRenderer defaultRenderer = new DefaultListCellRenderer();
            private int row;
    
            private ComboRenderer(JComboBox fontsBox) {
                comboBox = fontsBox;
            }
    
            private void manItemInCombo() {
                if (comboBox.getItemCount() > 0) {
                    final Object comp = comboBox.getUI().getAccessibleChild(comboBox, 0);
                    if ((comp instanceof JPopupMenu)) {
                        final JList list = new JList(comboBox.getModel());
                        final JPopupMenu popup = (JPopupMenu) comp;
                        final JScrollPane scrollPane = (JScrollPane) popup.getComponent(0);
                        final JViewport viewport = scrollPane.getViewport();
                        final Rectangle rect = popup.getVisibleRect();
                        final Point pt = viewport.getViewPosition();
                        row = list.locationToIndex(pt);
                    }
                }
            }
    
            @Override
            public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
                super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
                if (list.getModel().getSize() > 0) {
                    manItemInCombo();
                }
                final JLabel renderer = (JLabel) defaultRenderer.getListCellRendererComponent(list, value, row, isSelected, cellHasFocus);
                final Object fntObj = value;
                final String fontFamilyName = (String) fntObj;
                setFont(new Font(fontFamilyName, Font.PLAIN, 16));
                return this;
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题