What is the Swing-equivalent to HTML <optgroup>
Asked Answered
O

4

10

I want my JComboBox to group multiple options together, similar to the HTML optgroup:

<select>  
 <optgroup label="A">  
  <option/>
  <option/>  
 </optgroup>
</select>  

I could not find any solution for this in Swing. Manipulating the UI-Renderer for the Combobox seems to be a bad idea, as it's OS & L&F-dependent (and they are private so cannot extend).

Observance answered 8/3, 2011 at 10:59 Comment(0)
D
14

Consider the following implementation as a basic guide how to apply custom styling and create non-selectable items:

public class ExtendedComboBox extends JComboBox {

    public ExtendedComboBox() {
        setModel(new ExtendedComboBoxModel());
        setRenderer(new ExtendedListCellRenderer());
    }

    public void addDelimiter(String text) {
        this.addItem(new Delimiter(text));
    }

    private static class ExtendedComboBoxModel extends DefaultComboBoxModel {
        @Override
        public void setSelectedItem(Object anObject) {
            if (!(anObject instanceof Delimiter)) {
                super.setSelectedItem(anObject);
            } else {
                int index = getIndexOf(anObject);
                if (index < getSize()) {
                    setSelectedItem(getElementAt(index+1));
                }
            }
        }

    }

    private static class ExtendedListCellRenderer 
                    extends DefaultListCellRenderer {

        @Override
        public Component getListCellRendererComponent(JList list, Object value,
                        int index, boolean isSelected, boolean cellHasFocus) {
            if (!(value instanceof Delimiter)) {
                return super.getListCellRendererComponent(list, value, index,
                        isSelected, cellHasFocus);
            } else {
                JLabel label = new JLabel(value.toString());
                Font f = label.getFont();
                label.setFont(f.deriveFont(f.getStyle() 
                           | Font.BOLD | Font.ITALIC));
                return label;
            }
        }
    }

    private static class Delimiter {
        private String text;

        private Delimiter(String text) {
            this.text = text;
        }

        @Override
        public String toString() {
            return text.toString();
        }
    }
}
Dewhurst answered 8/3, 2011 at 11:37 Comment(2)
Yes this example does almost exacly what i have asked for, I got it working. Thanks a lot.Observance
This got me on the right track too, but I would like to understand how to override DefaultListSelectionModel instead of DefaultComboBoxModel, to prevent the Delimiter from being selected. I have used GlazedLists to create an EventList and a SeparatorList based on the EventList, and I am creating a JList using the DefaultEventListModel as model (passing in my SeparatorList) and a custom CellRenderer. All is displaying nicely, now I just have to prevent selection of the Separator cells... The DefaultListSelectionModel is quite different than the DefaultComboBoxModel...Chapel
O
7

You can do this in a custom renderer, as discussed in How to Use Combo Boxes: Providing a Custom Renderer.

Oeildeboeuf answered 8/3, 2011 at 11:19 Comment(2)
Great suggestion as well. I forgot about the Java Tutorial (download.oracle.com/javase/tutorial)Outclass
Yes, that tutorial helped me a lot, Thanks.Observance
O
5

I don't believe that there is one simple way of doing this, but there is a way to do it.

I would implement a data model class that indicates the grouping that you've describe above. Place instances of those data models in your javax.swing.ComboBoxModel implementation instance.

You can then implement a javax.swing.ListCellRenderer to format the output as you like with indents for the text data. You may just want to extend the javax.swing.DefaultListCellRenderer or possibly borrow its implementation wholesale from the Java source.

As for the L&F you should be able to stay within normal guidelines by using the above methods and you won't have to fight with figuring out how to implement it. Look at the default Swing components they will provide a lot of insight in to how to deal with L&F.

Additionally, I think there are mechanisms (you'll have to forgive me, it's been YEARS since I've done full Swing development) to allow you to determine if an item is selectable or not.

Outclass answered 8/3, 2011 at 11:17 Comment(0)
C
2

I was wanting this myself today, and I've spent the day figuring it out piecing things together to implement a similar model with a JList rather than with the suggested JComboBox. I've finally come up with a solution using GlazedLists EventList and SeparatorList with the corresponding DefaultEventListModel. I override the CellRenderer and the DefaultListSelectionModel. In the end I posted my own answer to my own question on this: How to prevent selection of SeparatorList.Separator in a JList?

Here's my final working code:

public class MyFrame extends javax.swing.JFrame {
    private final EventList<BibleVersion> bibleVersions;
    private final SeparatorList<BibleVersion> versionsByLang;
    private boolean[] enabledFlags;

    public MyFrame(){
        bibleVersions = new BasicEventList<>();
        bibleVersions.add(new BibleVersion("CEI2008", "Testo della Conferenza Episcopale Italiana", "2008", "Italian"));
        bibleVersions.add(new BibleVersion("LUZZI", "Diodati Nuova Riveduta - Luzzi", "1927", "Italian"));
        bibleVersions.add(new BibleVersion("NVBSE", "Nova Vulgata - Bibliorum Sacrorum Editio", "1979", "Latin"));
        bibleVersions.add(new BibleVersion("NABRE", "New American Bible - Revised Edition", "2011", "English"));
        bibleVersions.add(new BibleVersion("KJV", "King James Version", "1611", "English"));
        versionsByLang = new SeparatorList<>(bibleVersions, new VersionComparator(),1, 1000);
        int listLength = versionsByLang.size();
        enabledFlags = new boolean[listLength];

        ListIterator itr = versionsByLang.listIterator();
        while(itr.hasNext()){
            enabledFlags[itr.nextIndex()] = !(itr.next().getClass().getSimpleName().equals("GroupSeparator"));
        }
        jList = new javax.swing.JList();
        jList.setModel(new DefaultEventListModel<>(versionsByLang));
        jList.setCellRenderer(new VersionCellRenderer());
        jList.setSelectionModel(new DisabledItemSelectionModel());
        ListSelectionModel listSelectionModel = jList.getSelectionModel();
        listSelectionModel.addListSelectionListener(new SharedListSelectionHandler());

    }

    public static class BibleVersion {
        private String abbrev;
        private String fullname;
        private String year;
        private String lang;

        public BibleVersion(String abbrev, String fullname, String year, String lang) {
            this.abbrev = abbrev;
            this.fullname = fullname;
            this.year = year;
            this.lang = lang;
        }        

        public String getAbbrev() {
            return abbrev;
        }

        public void setAbbrev(String abbrev) {
            this.abbrev = abbrev;
        }

        public String getFullname() {
            return fullname;
        }

        public void setFullname(String fullname) {
            this.fullname = fullname;
        }

        public String getYear() {
            return year;
        }

        public void setYear(String year) {
            this.year = year;
        }

        public String getLang() {
            return lang;
        }

        public void setLang(String lang) {
            this.lang = lang;
        }

        @Override
        public String toString() {
            return this.getAbbrev() + " — " + this.getFullname() + " (" + this.getYear() + ")"; //To change body of generated methods, choose Tools | Templates.
        }                

    }

    private static class VersionComparator implements Comparator<BibleVersion> {

        @Override
        public int compare(BibleVersion o1, BibleVersion o2) {
            return o1.getLang().compareTo(o2.getLang());
        }            

    }

    private static class VersionCellRenderer extends DefaultListCellRenderer{

        @Override
        public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
            JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);

            if (value instanceof SeparatorList.Separator) {
                SeparatorList.Separator separator = (SeparatorList.Separator) value;
                BibleVersion bibleversion = (BibleVersion)separator.getGroup().get(0);
                String lbl = "-- " + bibleversion.getLang() + " --";
                label.setText(lbl);
                label.setFont(label.getFont().deriveFont(Font.BOLD));
                label.setBackground(Color.decode("#004400"));
                label.setBorder(BorderFactory.createEmptyBorder(0,5,0,0));
                label.setEnabled(false);
            } else {
                label.setFont(label.getFont().deriveFont(Font.PLAIN));
                label.setBorder(BorderFactory.createEmptyBorder(0,15,0,0));
            }

            return label;
        }
    }

private class DisabledItemSelectionModel extends DefaultListSelectionModel {

    private static final long serialVersionUID = 1L;

    @Override
    public void setSelectionInterval(int index0, int index1) {
        if(index0 < index1){
            for (int i = index0; i <= index1; i++){
                if(enabledFlags[i]){
                    super.addSelectionInterval(i, i);
                }
            }
        }
        else if(index1 < index0){
            for (int i = index1; i <= index0; i++){
                if(enabledFlags[i]){
                    super.addSelectionInterval(i, i);
                }
            }
        }
        else if(index0 == index1){
            if(enabledFlags[index0]){ super.setSelectionInterval(index0,index0); }
        }
    }

    @Override
    public void addSelectionInterval(int index0, int index1) {
        if(index0 < index1){
            for (int i = index0; i <= index1; i++){
                if(enabledFlags[i]){
                    super.addSelectionInterval(i, i);
                }
            }
        }
        else if(index1 < index0){
            for (int i = index1; i <= index0; i++){
                if(enabledFlags[i]){
                    super.addSelectionInterval(i, i);
                }
            }
        }
        else if(index0 == index1){
            if(enabledFlags[index0]){ super.addSelectionInterval(index0,index0); }
        }
    }        

}

private class SharedListSelectionHandler implements ListSelectionListener {
    @Override
    public void valueChanged(ListSelectionEvent e) {
        ListSelectionModel lsm = (ListSelectionModel)e.getSource();
        StringBuilder output = new StringBuilder();
        int firstIndex = e.getFirstIndex();
        int lastIndex = e.getLastIndex();
        boolean isAdjusting = e.getValueIsAdjusting();
        output.append("Event for indexes ");
        output.append(firstIndex);
        output.append(" - ");
        output.append(lastIndex);
        output.append("; isAdjusting is ");
        output.append(isAdjusting);
        output.append("; selected indexes:");

        if (lsm.isSelectionEmpty()) {
            output.append(" <none>");
        } else {
            // Find out which indexes are selected.
            int minIndex = lsm.getMinSelectionIndex();
            int maxIndex = lsm.getMaxSelectionIndex();
            for (int i = minIndex; i <= maxIndex; i++) {
                if (lsm.isSelectedIndex(i)) {
                    output.append(" ");
                    output.append(i);
                }
            }
        }
        output.append(System.getProperty("line.separator"));
        System.out.println(output.toString());
    }
}


}
Chapel answered 22/2, 2015 at 3:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.