简体   繁体   中英

Decorate a JComboBox editor component with a JLayer

I'd like to paint some additional information in the text field used as the default editor component of a JComboBox using a JLayer . To do this, I'd need to set the layer as the editor of the combo box through JComboBox.setEditor(ComboBoxEditor) but that does not seem to be possible, since JLayer is final and therefore cannot implement ComboBoxEditor interface.

Is there a way to decorate a JComboBox editor component with a JLayer ?

PS: The information I'd like to paint are cursor-like lines at certain text offsets within the text field, which is trivial for a JTextField or a JTextArea but not for an editable JComboBox (its editor).

Edit: This is the closest I've come to, after reading @camickr's answer, but not satisfied with it. The relevant part is extending BasicComboBoxEditor .

import java.awt.*;
import java.util.*;
import java.util.List;
import javax.swing.*;
import javax.swing.plaf.LayerUI;
import javax.swing.plaf.basic.BasicComboBoxEditor;
import javax.swing.text.*;

public class GenericDecorateWithJLayer extends JFrame {

    private static final String SAMPLE_TEXT = "Hello, world!";

    private final Map<JComponent, List<Integer>> componentToPositions;

    public GenericDecorateWithJLayer() {
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLayout(new GridBagLayout());

        componentToPositions = new HashMap<JComponent, List<Integer>>();

        GridBagConstraints gbc;

        JLabel label1 = new JLabel("label1:");
        gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 0;
        add(label1, gbc);

        JTextField textfield1 = new JTextField(20);
        textfield1.setText(SAMPLE_TEXT);
        componentToPositions.put(textfield1, Arrays.asList(new Integer[]{5}));
        gbc = new GridBagConstraints();
        gbc.gridx = 1;
        gbc.gridy = 0;
        add(textfield1, gbc);

        JLabel label2 = new JLabel("label2:");
        gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 1;
        add(label2, gbc);

        JTextField textfield2 = new JTextField(20);
        textfield2.setText(SAMPLE_TEXT);
        componentToPositions.put(textfield2, Arrays.asList(new Integer[]{6}));
        gbc = new GridBagConstraints();
        gbc.gridx = 1;
        gbc.gridy = 1;
        add(textfield2, gbc);

        JLabel label3 = new JLabel("label3:");
        gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 2;
        add(label3, gbc);

        JTextArea textarea1 = new JTextArea(5, 20);
        textarea1.setText(SAMPLE_TEXT);
        componentToPositions.put(textarea1, Arrays.asList(new Integer[]{7}));
        gbc = new GridBagConstraints();
        gbc.gridx = 1;
        gbc.gridy = 2;
        JScrollPane scroll1 = new JScrollPane(textarea1);
        add(scroll1, gbc);

        JLabel label4 = new JLabel("label4:");
        gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 3;
        add(label4, gbc);

        JComboBox combobox1 = new JComboBox(new Object[]{SAMPLE_TEXT, "one", "two", "three"});
        combobox1.setEditable(true);
        combobox1.setSelectedItem(SAMPLE_TEXT);
        componentToPositions.put(combobox1, Arrays.asList(new Integer[]{8}));
        gbc = new GridBagConstraints();
        gbc.gridx = 1;
        gbc.gridy = 3;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        add(combobox1, gbc);

        pack();
        setLocationRelativeTo(null);

        replaceWithJLayer(textfield1);
        replaceWithJLayer(textfield2);
        replaceWithJLayer(textarea1);
        replaceWithJLayer(combobox1);
    }

    /**
     * Intended to decorate legacy components.
     * 
     * @param component 
     */
    private void replaceWithJLayer(JComponent component) {
        Container parent = component.getParent();
        if (component instanceof JComboBox) {
            JComboBox cbb = (JComboBox) component;
            cbb.setEditor(new MyComboBoxEditor(componentToPositions.get(cbb)));
        } else if (parent.getLayout() instanceof GridBagLayout) {
            GridBagLayout layout = (GridBagLayout) parent.getLayout();
            for (int i = 0; i < parent.getComponentCount(); i++) {
                Component candidate = parent.getComponent(i);
                if (candidate == component) {
                    GridBagConstraints gbc = layout.getConstraints(component);
                    parent.remove(i);
                    JLayer<JComponent> layer = new JLayer<JComponent>(
                            component,
                            new MyLayerUI(
                                    component,
                                    componentToPositions.get(component)));
                    parent.add(layer, gbc, i);
                    break;
                }
            }
        } else if (parent instanceof JViewport) {
            JViewport viewport = (JViewport) parent;
            JLayer<JComponent> layer = new JLayer<JComponent>(
                    component,
                    new MyLayerUI(
                            component, componentToPositions.get(component)));
            viewport.setView(layer);
        }
    }

    public static void main(String[] args)
            throws ClassNotFoundException, InstantiationException,
            IllegalAccessException, UnsupportedLookAndFeelException {
        for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
            if ("Nimbus".equals(info.getName())) {
                UIManager.setLookAndFeel(info.getClassName());
            }
            System.out.println(info.getName());
        }

        SwingUtilities.invokeLater(new Runnable() {

            public void run() {
                new GenericDecorateWithJLayer().setVisible(true);
            }
        });
    }

    private static class MyLayerUI extends LayerUI<JComponent> {

        private final JComponent component;
        private final List<Integer> positions;

        public MyLayerUI(JComponent component, List<Integer> positions) {
            this.component = component;
            this.positions = positions;
        }

        @Override
        public void paint(Graphics g, JComponent c) {
            // paint the layer as is
            super.paint(g, c);

            // fill it with the translucent green
            g.setColor(new Color(0, 128, 0, 128));

            // paint positions
            JTextComponent textComponent = (JTextComponent) component;
            for (Integer position : positions) {
                try {
                    Rectangle rect = textComponent.modelToView(position);
                    g.fillRect(rect.x, rect.y, rect.width, rect.height);
                } catch (BadLocationException ex) {
                    // no-op
                }
            }
        }
    }

    private static class MyComboBoxEditor extends BasicComboBoxEditor {

        private final JLayer<JComponent> layer;

        public MyComboBoxEditor(List<Integer> positions) {
            super();

            layer = new JLayer<JComponent>(editor, new MyLayerUI(editor, positions));
        }

        @Override
        public Component getEditorComponent() {
            return layer;
        }

    }
} 

The information I'd like to paint are cursor-like lines at certain text offsets within the text field, which is trivial for a JTextField

You can set your own editor for the combo box using the setEditor(...) method.

So you can extend the BasicComboBoxEditor and override the createEditorComponent() method to return an instance of your custom JTextField that does the custom painting.

Or simpler option is to override the getEditorComponent() method of BasicComboBoxEditor :

import java.awt.*;
import javax.swing.*;
import javax.swing.plaf.LayerUI;
import javax.swing.plaf.basic.BasicComboBoxEditor;
//import javax.swing.plaf.metal.MetalComboBoxEditor;
import javax.swing.text.*;

public class ComboEditorJLayerTest {
  public JComponent makeUI() {
    JComboBox<String> comboBox = new JComboBox<>(new String[] {"aaaaaaa", "bbb"});
    comboBox.setEditable(true);
    comboBox.setEditor(new BasicComboBoxEditor() {
      private Component editorComponent;
      //@see javax/swing/plaf/synth/SynthComboBoxUI.java
      @Override public JTextField createEditorComponent() {
        JTextField f = new JTextField("", 9);
        f.setName("ComboBox.textField");
        return f;
      }
      @Override public Component getEditorComponent() {
        if (editorComponent == null) {
          JTextComponent tc = (JTextComponent) super.getEditorComponent();
          editorComponent = new JLayer<JTextComponent>(tc, new ValidationLayerUI());
        }
        return editorComponent;
      }
    });
    JPanel p = new JPanel();
    p.add(comboBox);
    return p;
  }
  public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {
      @Override public void run() {
        createAndShowGUI();
      }
    });
  }
  public static void createAndShowGUI() { 
    try {
      for (UIManager.LookAndFeelInfo laf : UIManager.getInstalledLookAndFeels()) {
        if ("Nimbus".equals(laf.getName())) {
          UIManager.setLookAndFeel(laf.getClassName());
        }
      }
    }catch(Exception e) {
      e.printStackTrace();
    }
    JFrame f = new JFrame();
    f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    f.getContentPane().add(new ComboEditorJLayerTest().makeUI());
    f.setSize(320, 240);
    f.setLocationRelativeTo(null);
    f.setVisible(true);
  }
}

//@see http://docs.oracle.com/javase/tutorial/uiswing/examples/misc/FieldValidatorProject/src/FieldValidator.java
class ValidationLayerUI extends LayerUI<JTextComponent> {
  @Override public void paint(Graphics g, JComponent c) {
    super.paint(g, c);
    JLayer jlayer = (JLayer) c;
    JTextComponent tc = (JTextComponent) jlayer.getView();
    if (tc.getText().length() > 6) {
      Graphics2D g2 = (Graphics2D) g.create();
      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
      int w = c.getWidth();
      int h = c.getHeight();
      int s = 8;
      int pad = 4;
      int x = w - pad - s;
      int y = (h - s) / 2;
      g2.setPaint(Color.RED);
      g2.fillRect(x, y, s + 1, s + 1);
      g2.setPaint(Color.WHITE);
      g2.drawLine(x, y, x + s, y + s);
      g2.drawLine(x, y + s, x + s, y);
      g2.dispose();
    }
  }
}

可能可以通过扩展CellRenderer来完成

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM