4

I would like to put a JButton inside a JComboBox. This button lets users browse for files. The file the user selects gets added to the JComboBox list. How do I do this? Do I use some kind of a Renderer? Thank you.

EDIT: After reading more about ListCellRenderer I tried the following code:

JComboBox comboBox = new JComboBox(new String[]{"", "Item1", "Item2"});
ComboBoxRenderer renderer = new ComboBoxRenderer();
comboBox.setRenderer(renderer);

class ComboBoxRenderer implements ListCellRenderer {

    public Component getListCellRendererComponent(
            JList list,
            Object value,
            int index,
            boolean isSelected,
            boolean cellHasFocus) {

        JButton jbutton = new JButton("Browse");

        return jbutton;
    }
}

The problem with the above is that the Button "Browse" will be added 3 times and I want it to display only once and below it to display Item1 and Item2 as normal/regular combobox selection objects.

jadrijan
  • 1,438
  • 4
  • 31
  • 48

5 Answers5

5

I would avoid the JButton. It is perfectly possible to get the image of a JButton inside your combobox, but it will not behave itself as a button. You cannot click it, it never gets visually 'pressed' nor 'released', ... . In short, your combobox will contain an item which behaves unfamiliar to your users.

The reason for this is that the components you return in the getListCellRendererComponent method are not contained in the JCombobox. They are only used as a stamp. That also explains why you can (and should) reuse the Component you return in that method, and not create new components the whole time. This is all explained in the JTable tutorial in the part about Renderers and Editors (explained for a JTable but valid for all other Swing components which use renderers and editors).

If you really want an item in the combobox that allows to show a file chooser, I would opt for something similar to the following SSCCE:

import javax.swing.JComboBox;
import javax.swing.JFrame;
import java.awt.EventQueue;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;

public class ComboboxTest {

  public static void main( String[] args ) {
    EventQueue.invokeLater( new Runnable() {
      @Override
      public void run() {
        JFrame frame = new JFrame( "TestFrame" );
        JComboBox<String> comboBox = new JComboBox<>(new String[]{"Item1", "Item2"});
        final String browse = "<<BROWSE>>";
        comboBox.addItem( browse );
        comboBox.addItemListener( new ItemListener() {
          @Override
          public void itemStateChanged( ItemEvent e ) {
            if ( e.getStateChange() == ItemEvent.SELECTED && 
                browse.equals( e.getItem() ) ){
              System.out.println("Show filechooser");
            }
          }
        } );
        frame.add( comboBox );
        frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        frame.setVisible( true );
        frame.pack();
      }
    } );
  }
}
Robin
  • 36,233
  • 5
  • 47
  • 99
  • Thank you for your detailed answer Robin. The example that you posted above shows how to add an Event Listener to the browse component, but it is still displayed to the user as a regular JList text. I have seen applications with Buttons inside of ComboBoxes, whether they are written in Java or not I am not sure. – jadrijan Aug 24 '12 at 13:54
  • @jadrijan Of course it does not look like a button. That is what I am trying to say ... you can use a `JButton` but it will never behave like one. So better to not use a `JButton` imo – Robin Aug 24 '12 at 14:11
  • I understand you perfectly Robin. :) I just wanted to clarify that it is not against the Java "rules" to do what I have done. – jadrijan Aug 24 '12 at 14:23
2

After trying out many things I think I figured out the Answer, I am sure it will look very easy when you see it:

        JComboBox comboBox = new JComboBox(new String[]{"Item1", "Item2"});
        ComboBoxRenderer renderer = new ComboBoxRenderer();
        comboBox.setRenderer(renderer);
        comboBox.addItem("<<BROWSE>>");

class ComboBoxRenderer implements ListCellRenderer {

        @Override
        public Component getListCellRendererComponent(JList list, Object value, int index,         boolean isSelected, boolean cellHasFocus) {
            if (value.equals("<<BROWSE>>")) {
                JButton btn = new JButton("Browse");
                return btn;
            } else {
                JLabel lbl = new JLabel(value.toString());
                lbl.setOpaque(true);
                return lbl;
            }
        }
    }

You can now customize the button and labels any way you wish.

jadrijan
  • 1,438
  • 4
  • 31
  • 48
  • 2
    1. Do not create new Components, but reuse and adjust existing ones in your renderer 2. You cannot press the button by simply using a renderer. – Robin Aug 23 '12 at 21:21
1

Indeed, you will have to use a custom renderer as explained on http://docs.oracle.com/javase/tutorial/uiswing/components/combobox.html#renderer.

Tom Naessens
  • 1,827
  • 22
  • 38
  • 2
    And how will you press the button if you only have a renderer ? – Robin Aug 23 '12 at 21:22
  • @Robin: You don't change the renderer of the button, only the renderer of the JCombobox. The button will still function as it does outside a combobox. – Tom Naessens Aug 23 '12 at 21:28
  • 2
    Did you actually try this ? The components returned in the renderer are not contained in the `JComboBox`, so you will not be able to press the button – Robin Aug 23 '12 at 21:30
1

Depending on where you want to put the search button, you could take a look at the xswingx Prompt/Buddy API. You could use this to "buddy" the browse button with the editor field

Or you could simply add a browse button next to the combo box.

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • Thank you, I really want to put the button inside the combo box not next to it. I would have thought that there would be an easier way to do this instead of using a 3rd party api. – jadrijan Aug 23 '12 at 20:04
  • 1
    From memory, the layout of the comobox seems to be done by the UI delegate (look & feel), I could be wrong, but that's what I seem to remember – MadProgrammer Aug 23 '12 at 20:07
0

This is a very old problem, and these days I want to use swing to write a small application. And I meet same problem, but I finally got a way to make it.

After I read the source code of BasicComboPopup, it will check whether ComboBox is editable, if it is, pass action to editor. If you want to add JButton directly into ComboBox, you have to re-write all of ComboPopup and ComboUI. It is a huge work. But there is a easy way.

static class MEditor extends JButton implements ComboBoxEditor{
    //create a fake editor but it is Jbutton in fact.
    public MEditor(String text) {
        super(text);
        addMouseListener(new MouseAdapter() {
            @Override
            public void mouseReleased(MouseEvent e) {
                super.mouseClicked(e);
                System.out.println(text);
                //change it to anything you want.
            }
        });
    }

    @Override
    public Component getEditorComponent() {
        return this;
    }
    @Override
    public void setItem(Object anObject) {}

    @Override
    public Object getItem() {
        return null;
    }
    @Override
    public void selectAll() {}
    @Override
    public String toString(){
        return getText();// You have to keep this
     // otherwise the name will be object id in ComboBox
    }
}
static class MComboBox<E> extends JComboBox<E>{
    public MComboBox(){
        setEditable(true);
        addItemListener(e -> {
            int stateChange = e.getStateChange();
            Object item = e.getItem();// 
            if (stateChange == ItemEvent.SELECTED) {
               //just make MEditor into front after selected
                setEditor((MEditor)item);
                setSelectedItem(item);
            }
        });
    }

}

Now you can make the thing you want

    MComboBox<MEditor> test = new MComboBox<MEditor>();
    test.addItem(new MEditor("JButton 1"));
    test.addItem(new MEditor("JButton 2"));

I have tried this, it can work!!

tobin
  • 1
  • 2