Execute an action when an item on the combobox is selected [closed]
Asked Answered
N

3

5

I have a jcombobox containing item1 and item2, also I have a jtextfield.. when I select item1 on my jcombobox I want 30 to appear on my jtextfield while 40 if Item2 was selected... How do I do that?

Narcosynthesis answered 2/3, 2013 at 10:18 Comment(1)
#8654401Mcnully
G
14

this is how you do it with ActionLIstener

import java.awt.FlowLayout;
import java.awt.event.*;

import javax.swing.*;

public class MyWind extends JFrame{

    public MyWind() {
        initialize();
    }

    private void initialize() {
        setSize(300, 300);
        setLayout(new FlowLayout(FlowLayout.LEFT));
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        final JTextField field = new JTextField();
        field.setSize(200, 50);
        field.setText("              ");

        JComboBox comboBox = new JComboBox();
        comboBox.setEditable(true);
        comboBox.addItem("item1");
        comboBox.addItem("item2");

        //
        // Create an ActionListener for the JComboBox component.
        //
        comboBox.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) {
                //
                // Get the source of the component, which is our combo
                // box.
                //
                JComboBox comboBox = (JComboBox) event.getSource();

                Object selected = comboBox.getSelectedItem();
                if(selected.toString().equals("item1"))
                field.setText("30");
                else if(selected.toString().equals("item2"))
                    field.setText("40");

            }
        });
        getContentPane().add(comboBox);
        getContentPane().add(field);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new MyWind().setVisible(true);
            }
        });
    }
}
Gereron answered 2/3, 2013 at 10:43 Comment(1)
I like your answer, one very minor nit pick, I might suggest you do "Item 1".equals(selected) instead - while highly unlikely, it will protect against possible NullPointerExceptions ;)Deuced
D
3

The simple solution would be to use a ItemListener. When the state changes, you would simply check the currently selected item and set the text accordingly

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class TestComboBox06 {

    public static void main(String[] args) {
        new TestComboBox06();
    }

    public TestComboBox06() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException ex) {
                } catch (InstantiationException ex) {
                } catch (IllegalAccessException ex) {
                } catch (UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Test");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }

        });
    }

    public class TestPane extends JPanel {

        private JComboBox cb;
        private JTextField field;

        public TestPane() {
            cb = new JComboBox(new String[]{"Item 1", "Item 2"});
            field = new JTextField(12);

            add(cb);
            add(field);

            cb.setSelectedItem(null);

            cb.addItemListener(new ItemListener() {
                @Override
                public void itemStateChanged(ItemEvent e) {
                    Object item = cb.getSelectedItem();
                    if ("Item 1".equals(item)) {
                        field.setText("20");
                    } else if ("Item 2".equals(item)) {
                        field.setText("30");
                    }
                }
            });
        }

    }

}

A better solution would be to create a custom object that represents the value to be displayed and the value associated with it...

Updated

Now I no longer have a 10 month chewing on my ankles, I updated the example to use a ListCellRenderer which is a more correct approach then been lazy and overriding toString

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.EventQueue;
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.JList;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class TestComboBox06 {

    public static void main(String[] args) {
        new TestComboBox06();
    }

    public TestComboBox06() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException ex) {
                } catch (InstantiationException ex) {
                } catch (IllegalAccessException ex) {
                } catch (UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Test");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }

        });
    }

    public class TestPane extends JPanel {

        private JComboBox cb;
        private JTextField field;

        public TestPane() {
            cb = new JComboBox(new Item[]{
                new Item("Item 1", "20"), 
                new Item("Item 2", "30")});
            cb.setRenderer(new ItemCelLRenderer());
            field = new JTextField(12);

            add(cb);
            add(field);

            cb.setSelectedItem(null);

            cb.addItemListener(new ItemListener() {
                @Override
                public void itemStateChanged(ItemEvent e) {
                    Item item = (Item)cb.getSelectedItem();
                    field.setText(item.getValue());
                }
            });
        }

    }

    public class Item {
        private String value;
        private String text;

        public Item(String text, String value) {
            this.text = text;
            this.value = value;
        }

        public String getText() {
            return text;
        }

        public String getValue() {
            return value;
        }

    }

    public class ItemCelLRenderer extends DefaultListCellRenderer {

        @Override
        public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
            super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); //To change body of generated methods, choose Tools | Templates.
            if (value instanceof Item) {
                setText(((Item)value).getText());
            }
            return this;
        }

    }

}
Deuced answered 2/3, 2013 at 10:40 Comment(10)
wrapper objects with overridden toString (instead of a proper renderer) are evil - here it might appear as halfway justifyable because you seem to need another property for anothe view ... which is a home-grown problem, because the factoring of the business object is wrong, to start with: Item itself might be the business object, and its toString overridden with a complete description instead of with something relevant to a particular view ;-)Noto
@Noto I totally agree, it was a fast cut due to a limited amount of time to actually code (no excuse), bad meDeuced
Don't forget to include the KeySelectionManager so the items in the combo box can be selected by key stroke as well. As soon as you use a custom renderer you break the default functionality of the combo box.Languish
I still don't understand the problem with a general purpose Item class. It is specifically designed to be used with a "toString()" type of renderer and it contains additional information that can be used for processing once it is selected. If the class is well documented to support this type of functionality I don't see the problem. Almost every single HTML combobox contains this type of information. I don't see why you need to keep creating custom objects every time you need to do this. I think I need to be hit on the head a few more times before I will understand this.Languish
@Languish I think every implementation is different. The use of a cell render in this case (from the perspective of the OP) MAY be overkill (hard to ascertain from the question) but by using a custom object, we marry the text/view representation with that of its associated value, it makes it easier to add, change & remove elements. I also agree with kleo, toString should be used to provide stateful information about the object and renderers should be used to format the object for display. It's unlikely you would us the Date#toString value to render dates all the timeDeuced
Note sure how add/change/remove is easier? If you take your Item class and add a getDisplayText() method and then create a custom renderer and custom KeySelectionManager that takes advantage of this new method, would you then use the Item class for different combo boxes. Say for example you have an address form and you want to have "state" and "country" combo boxes. Would you reuse the Item class or create a StateItem and CountryItem (along with unique renderer and KeySelectionManager).Languish
@Languish it would depend on the requirements. Is thinking that add it "custom" object is easier then adding a String and the having to define new logic for it in the listenerDeuced
let us continue this discussion in chatLanguish
@Languish personally, I hate chatting, especially in different timezones ;-) The base problem is that you are adding the Item (which is tweaked for View reasons) to the Model, that is mixing data and view: that mixing is against the very grain of what MVC (and its Swing variant) stands for, doing so intentionally is a complete no-go. Not having the object itself in the data will effect your complete application chain: each and every line of code that accesses the elements in the combo/model must be aware of the wrapper (aka: Item), including binding such as view updates on data mutations.Noto
@Languish custom renderer will break default KeySelectionManager - true but not the fault of the renderer ;-) The default implementation of the keySelectionManager is ... crappy in assuming the renderer will use the toString, basically not thinking about effects of custom renderers.Noto
N
1

Not an answer to the original question, but an example to the how-to-make-reusable and working custom renderers without breaking MVC :-)

// WRONG
public class DataWrapper {
   final Data data;
   final String description;
   public DataWrapper(Object data, String description) {
       this.data = data;
       this.description = description;
   }
   ....
   @Override
   public String toString() {
       return description;
   } 
}
// usage
myModel.add(new DataWrapper(data1, data1.getName());

It is wrong in a MVC environment, because it is mixing data and view: now the model doesn't contain the data but a wrapper which is introduced for view reasons. That's breaking separation of concerns and encapsulation (every class interacting with the model needs to be aware of the wrapped data).

The driving forces for breaking of rules were:

  • keep functionality of the default KeySelectionManager (which is broken by a custom renderer)
  • reuse of the wrapper class (can be applied to any data type)

As in Swing a custom renderer is the small coin designed to accomodate for custom visual representation, a default manager which can't cope is ... broken. Tweaking design just to accommodate for such a crappy default is the wrong way round, kind of upside-down. The correct is, to implement a coping manager.

While re-use is fine, doing so at the price of breaking the basic architecture is not a good bargin.

We have a problem in the presentation realm, let's solve it in the presentation realm with the elements designed to solve exactly that problem. As you might have guessed, SwingX already has such a solution :-)

In SwingX, the provider of a string representation is called StringValue, and all default renderers take such a StringValue to configure themselves:

StringValue sv = new StringValue() {
     @Override
     public String getString(Object value) {
        if (value instanceof Data) {
            return ((Data) value).getSomeProperty();
        }
        return TO_STRING.getString(value);
     }
};
DefaultListRenderer renderer = new DefaultListRenderer(sv);

As the defaultRenderer is-a StringValue (implemented to delegate to the given), a well-behaved implementation of KeySelectionManager now can delegate to the renderer to find the appropriate item:

public BetterKeySelectionManager implements KeySelectionManager {

     @Override
     public int selectionForKey(char ch, ComboBoxModel model) {

         ....
         if (getCellRenderer() instance of StringValue) {
              String text = ((StringValue) getCellRenderer()).getString(model.getElementAt(row));
              ....
         } 
     }

}

Outlined the approach because it is easily implementable even without using SwingX, simply define implement something similar and use it:

  • some provider of a string representation
  • a custom renderer which is configurable by that provider and guarantees to use it in configuring itself
  • a well-behaved keySelectionManager with queries the renderer for its string represention

All except the string provider is reusable as-is (that is exactly one implemenation of the custom renderer and the keySelectionManager). There can be general implementations of the string provider, f.i. those formatting value or using bean properties via reflection. And all without breaking basic rules :-)

Noto answered 3/3, 2013 at 13:1 Comment(8)
Last night I actually found a posting where you gave the same example. I didn't understand it then and I don't understand it now. Sometimes I need a concrete example before it sinks in. It seems to me like you are just creating another fancy wrapper class that invokes the getString() method instead of the toString() method. How would you use MadProgrammer's Item class and your StringValue class to add items to a ComboBoxModel? Also, since only the renderer know about the text, how do you add the ietms in sorted order. Does your StringValue class implement Comparable?Languish
There an many uses for a value/description pairs. For example a Product. "1" is a Hammer (to hit me on the head with), "2" is a Screwdriver. Somewhere you need to keep this mapping of value/description. So to say that an Object can't have these two properties together doesn't make any sense to me. Now that these two properties are together in a class you can choose which of the properties you want displayed in the combo box by using an appropriate renderer.Languish
Also, chances are your product will have multiple descriptions for different languages. So when you populate the Item class you will populate it with the appropriate description directly from your Database or resource file or whatever you use.Languish
@Languish you are mixing concerns again and to me it seems now that you are doing it intentionally. If you don't want to follow MVC, that's what you don't want and I'm not going to discuss that :-) Just repeating: The value (== bean, having arbitrary properties) itself belongs into the (Swing) data realm, unchanged for view reasons. The string representation in the combo is-a problem of the (Swing) view realm, so solve it there, f.i. by factoring the production of the string to use into a class which does nothing else.Noto
I'm not doing it intentionally. I am not always the best abstract thinker and sometimes need concrete examples. I still have no idea how to use your StringValue class. With regards to MVC. I honestly don't see the problem with having the value and description in the class which is added to the Model (eg. the value represents a "product id" and the description is the "description" of the product). Then you can have customized views, one of the product and one of the description.Languish
I down loaded Swingx and now understand the StringValue class and its use with a renderer and KeySelectionManager (KSM). I have versions of the renderer and KSM that use reflection. Not as flexible as your 3 class approach since it only supports rendering of a single property. I see JXComboBox doesn't support a sorted view, or am I missing something? So while I understand the view should be responsible for this, I'm guessing everybody cheats by just loading the combo box in sorted order.Languish
@Languish you'r right, JXComboBox doesn't support sorting - somehow we never made it to the tweaked uis (as we have done for JXList) that are required to fully support it :-)Noto
Right, so my point is we take tweaks or short cuts and do external sorting before adding to the model. Now if I'm making a GUI builder and I want to be able to create and assign properties to a JLabel. I might use a JTextField for the text, a JCheckBox for opaque and a JComboBox for alignment (Left, Center, Right). If I select Left how do you map this to JLabel.LEFT so you can set the alignment property? I doubt you use if/else like the accepted answer here.Languish

© 2022 - 2024 — McMap. All rights reserved.