通过Vaadin 7中的枚举创建一个OptionGroup(单选按钮)?

Make an OptionGroup (radio buttons) from an enum in Vaadin 7?

我有一个Java枚举,带有用于所需显示文本的getter。 如何使用它来填充Vaadin 7中的OptionGroup?


在Vaadin 7中,可以通过以下三种方法进行此操作:

好。

  • 我建立的一个类,EnumBackedOptionGroup。 Vaadin 7中OptionGroup的子类。
  • 简短而轻松地滚动自己。
  • 自己动手即可。
  • 好。

    OptionGroup的子类

    这是我编写的OptionGroup的新子类的源代码。

    好。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    package com.basilbourque;

    import com.vaadin.ui.OptionGroup;
    import java.util.Arrays;
    import java.util.Collection;
    import java.util.function.Function;
    import org.slf4j.LoggerFactory;

    /**
     * A subclass of the Vaadin 7 OptionGroup (radio buttons or bunch of checkboxes) widget, taking as its set of options
     * the instances of an Enum.
     *
     * In canonical usage, pass the class of your Enum and a reference to the method to be called for obtaining a textual
     * label for display to the user.
     *
     * Alternatively, if your Enum overrides the `toString` method, you may pass only the class of the Enum without a
     * Function. This approach is not recommended per the class documentation which explains `toString` should only be used
     * for debugging message. Nevertheless, some people override `toString` to provide a user-readable label, so we support
     * this.
     *
     * Even if your Enum does not override `toString` you may choose to omit passing the Function argument. As a default,
     * the Enum’s built-in `toString` method will be called, returning the"name" of the Enum’s instance. This is handy for
     * quick-and-dirty prototyping. Again, neither I nor the class doc recommend this approach for serious work.
     *
     * If you want to display a subset of your enum’s instances rather than all, pass a Collection.
     *
     * This source code available under terms of ISC License.  https://en.wikipedia.org/wiki/ISC_license
     *
     * Copyright (c) 2015, Basil Bourque
     * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby
     * granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS
     * PROVIDED"AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
     * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT,
     * OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
     * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
     * SOFTWARE.
     *
     * @author Basil Bourque
     * @version 2015-08-27T21:00:00Z
     * @since 2015-08-27T21:00:00Z
     */
    public class EnumBackedOptionGroup<T extends Enum> extends OptionGroup
    {

        final org.slf4j.Logger logger = LoggerFactory.getLogger( this.getClass() );

        /**
         * Constructor. The usual constructor for automatically detecting all the instances of an enum for use as the
         * options in a Vaadin 7 OptionGroup. Pass a function to be called for providing each option’s displayed labeling.
         *
         * Example usage:
         *
         * myRadios = new EnumBackedOptionGroup<DogBreed>("Choose breed:" , DogBreed.class , DogBreed :: getTitle );
         *
         * @param caption
         * @param enumClass
         * @param f
         */
        public EnumBackedOptionGroup ( final String caption , final Class< T > enumClass , final Function<T , String> f ) {
            super( caption );
            Function<T , String> func = f;
            // If passed a null for the Function, fallback to using 'toString'.
            if ( func == null ) {
                func = T -> T.toString();
            }
            this.buildAndAssignCaptions( enumClass , func );
        }

        /**
         * Constructor. Similar to usual constructor, but here you may additionally pass a Collection of the subset of Enum
         * instances.
         *
         * For use where business logic dictates that you give only some of the Enum values an options rather than all of
         * them. The omitted options are effectively hidden from the user.
         *
         * @param caption
         * @param enumClass
         * @param enumValues
         * @param f
         */
        public EnumBackedOptionGroup ( final String caption , final Class< T > enumClass , final Collection< T > enumValues , final Function<T , String> f ) {
            super( caption );
            Function<T , String> func = f;
            // If passed a null for the Function, fallback to using 'toString'.
            if ( func == null ) {
                func = T -> T.toString();
            }
            Collection< T > ev = enumValues;
            // Handle where calling method passed us a null or empty collection.
            if ( ( ev == null ) || ev.isEmpty() ) {
                this.buildAndAssignCaptions( enumClass , f ); // Fallback to assiging all the instances of enum as options in our OptionGroup.
            } else {
                this.addItems( enumValues );  // Add the passed subset of instances of the enum as items backing our OptionGroup.
                this.assignCaptions( enumValues , f );
            }
        }

        /**
         * Constructor. Similar to the usual constructor, but omits the method for providing on-screen labeling. Instead
         * uses the 'toString' method defined either explicitly in the Enum subclass or implicitly calls to the Enum class’
         * own 'toString'.
         *
         * Not recommended, as the Enum documentation strongly suggests the 'toString' method on an Enum be used only for
         * debugging. Nevertheless this is handy for quick-and-dirty prototyping.
         *
         * @param caption
         * @param enumClass
         */
        public EnumBackedOptionGroup ( final String caption , final Class< T > enumClass ) {
            super( caption );
            // User passed no Function to call for getting the title. So fallback to using 'toString'.
            this.buildAndAssignCaptions( enumClass , T -> T.toString() );
        }

        // Helper method. (sub-routine)
        // Extracts all the instances of the enum, and uses them as options in our OptionGroup.
        // Also assigns each option a labeling using String returned by passed method to be called for each instance of enum.
        private void buildAndAssignCaptions ( final Class< T > enumClass , final Function<T , String> f ) {
            if ( enumClass.isEnum() ) {  // This check may be unnecessary with Generics code"<T extends Enum>" at top of this class.
                Collection< T > enumValues = Arrays.asList( enumClass.getEnumConstants() );
                this.addItems( enumValues );  // Add all the instances of the enum as items backing our OptionGroup.
                this.assignCaptions( enumValues , f );
            } else {
                // Else the passed class is not an enum.
                // This case should not be possible because of the Generics marked on this class"<T extends Enum>".
                logger.error("Passed a class that is not a subclass of Enum. Message # f2098672-ab47-47fe-b720-fd411411052e." );
                throw new IllegalArgumentException("Passed a class that is not a subclass of Enum." );
            }
        }

        // Helper method. (sub-routine)
        // Assigns each option a labeling using String returned by passed method to be called for each instance of enum
        private void assignCaptions ( Collection< T > enumValues , final Function<T , String> f ) {
            for ( T option : enumValues ) {
                // For each option in our OptionGroup, determine and set its title, the label displayed for the user next to each radio button or checkbox.
                // To determine the label (the second argument), we invoke the passed method which must return a String. Using Lambda syntax.
                this.setItemCaption( option , f.apply( option ) );
            }
        }

    }

    我希望您将使用像这样的枚举DogBreed。请注意,此枚举如何具有构造函数,在该构造函数中,我们将文本用作传递给用户的标签。我们添加了方法getTitle来检索此标题文本。

    好。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package com.example;

    /**
     * Bogus example Enum.
     */
    public enum DogBreed {

        AUSSIE("Australian Shepherd") ,
        BORDER_COLLIE("Border Collie"),
        BLACK_LAB("Labrador, Black"),
        MUTT("Mixed Breed");

        private String title = null;

        DogBreed ( final String titleArg) {
            this.title = titleArg;
        }

        public String getTitle() {
            return this.title;
        }

    }

    screen shot of radio buttons showing dog breeds using presentation labeling

    好。

    由于WillShackleford对我的Question的Lambda语法的传递和调用方法引用,我只能完成该类。

    好。

    要使用此EnumBackedGroupOption类,请传递其类和该标题呈现方法的方法参考。这需要Java 8中新的Lambda语法。但是,无需掌握您对Lambda的理解,只需遵循此处看到的模式即可。

    好。

    1
    OptionGroup optionGroup = new EnumBackedOptionGroup<DogBreed>("Choose Breed:" , DogBreed.class , DogBreed :: getTitle );

    对于快速创建原型,可以定义一个没有此类构造函数和getter的简单枚举。在这种情况下,只传递您的标题和枚举类。 EnumBackedOptionGroup类回退到使用内置的toString方法。我和Enum类文档都没有建议将此路由用于认真的工作,其中toString仅应用于调试。

    好。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package com.example;

    /**
     * Bogus example Enum.
     */
    public enum SaySo {

        YES, NO, MAYBE;
    }

    OptionGroup optionGroup = new EnumBackedOptionGroup<SaySo>("Says you:" , SaySo.class );

    有时,您可能不希望在OptionGroup中使用所有枚举的实例值。如果是这样,请使用本课题中说明的隐式方法values提取这些实例的Collection。删除不需要的。请注意,我们如何从Arrays.asList的输出实例化一个新的ArrayList以允许此修改。然后将该集合传递给EnumBackedOptionGroup的另一个构造函数。

    好。

    您可以将null作为最后一个参数传递,以使用toString作为演示标签回退。

    好。

    您也许可以使用EnumMapEnumSet代替.values,但是我对此没有经验。

    好。

    1
    2
    3
    Collection< T > enumValues = new ArrayList( Arrays.asList( SaySo.values() ) );
    enumValues.remove( SaySo.MAYBE );
    OptionGroup optionGroup = new EnumBackedOptionGroup<SaySo>("Says you:" , SaySo.class , null );

    自己动手

    想象一下CRITTER_FILTER枚举嵌套在SomeClass中。

    好。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    public enum CRITTER_FILTER
    {

        CANINE ("Dogs" ), // Pass the text to be displayed to user as the radio button’s Caption (label).
        FELINE ("Cats" ),
        COCKATIEL ("Cockatiel birds" );

        private String title;

        CRITTER_FILTER ( String t )
        {
            this.title = t;
        }

        // Add this method for the more flexible approach.
        // JavaBeans"getter" for use in BeanItemContainer.
        public String getTitle ()
        {
            return this.title;
        }

        // Add this method for the short simple approach.
        @Override
        public String toString ()
        {
            return this.title;
        }

    }

    添加构造函数使我们能够将所需的显示文本传递到每个枚举实例,然后将该文本存储在私有成员String变量中。

    好。

    简短的简单方法

    如果确定显示文本没有花哨的工作,只需重写toString方法以返回存储的显示文本。

    好。

    我不推荐这种方法。文档建议仅在要创建一个特殊值以在调试工作中显示给程序员时才建议覆盖toString。但是,我确实尝试过这种方法,但确实有效。

    好。

    公共字符串toString()

    好。

    …尽管通常不需要或不希望使用此方法,但该方法可能会被覆盖。当存在更"程序员友好"的字符串形式时,枚举类型应覆盖此方法。

    好。

    1
    2
    this.filterRadios = new OptionGroup("Filter:" , Arrays.asList( SomeClass.CRITTER_FILTER.values() ) );  // Convert plain array of the enum instances (the values) into a `Collection` object by calling utility method `Arrays.asList`.
    this.filterRadios.setMultiSelect( false ); // Radio buttons are single-select.

    toString方法的完整示例

    Person类,带有嵌套枚举。

    好。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    package com.example.vaadinradiobuttons;

    import java.util.ArrayList;
    import java.util.List;

    /**
     *
     * @author Basil Bourque
     */
    public class Person {

        // Members
        String name;
        Person.VITAL_STATUS vitalStatus;

        public enum VITAL_STATUS {

            LIVING("Alive and Kicking" ),
            DECEASED("Dead" ),
            UNKNOWN("DUNNO" );

            private String captionText;

            VITAL_STATUS ( String t ) {
                this.captionText = t;
            }

            @Override
            public String toString () {
                return this.captionText;
            }

        }

        // Constructor
        public Person ( String nameArg , VITAL_STATUS vitalStatusArg ) {
            this.name = nameArg;
            this.vitalStatus = vitalStatusArg;
        }

    }

    还有一个很小的Vaadin 7.4.3小应用程序,使用该嵌套枚举填充选项组。查找注释// Core of example.以查看重要的行。

    好。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    package com.example.vaadinradiobuttons;

    import javax.servlet.annotation.WebServlet;

    import com.vaadin.annotations.Theme;
    import com.vaadin.annotations.VaadinServletConfiguration;
    import com.vaadin.annotations.Widgetset;
    import com.vaadin.data.Property;
    import com.vaadin.server.VaadinRequest;
    import com.vaadin.server.VaadinServlet;
    import com.vaadin.ui.OptionGroup;
    import com.vaadin.ui.UI;
    import com.vaadin.ui.VerticalLayout;
    import java.util.Arrays;
    import java.util.Collection;

    /**
     *
     */
    @Theme ("mytheme" )
    @Widgetset ("com.example.vaadinradiobuttons.MyAppWidgetset" )
    public class MyUI extends UI {

        @Override
        protected void init ( VaadinRequest vaadinRequest ) {
            final VerticalLayout layout = new VerticalLayout();
            layout.setMargin( true );
            setContent( layout );

            // Core of example.
            Collection<Person.VITAL_STATUS> v = Arrays.asList( Person.VITAL_STATUS.values() );
            OptionGroup radios = new OptionGroup("Vital Status :" , v );
            radios.setImmediate( true );
            radios.addValueChangeListener( ( Property.ValueChangeEvent event ) -> {
                Person.VITAL_STATUS vitalStatus = ( Person.VITAL_STATUS ) event.getProperty().getValue();
                System.out.println("User selected a vital status name:" + vitalStatus.name() +", labeled:" + vitalStatus.toString() );
            } );
            layout.addComponent( radios );

        }

        @WebServlet ( urlPatterns ="/*" , name ="MyUIServlet" , asyncSupported = true )
        @VaadinServletConfiguration ( ui = MyUI.class , productionMode = false )
        public static class MyUIServlet extends VaadinServlet {
        }
    }

    更灵活的方法

    请注意,在上面的枚举中添加了getTitle方法。您可以使用任何所需的方法名称,但getNamename已在Java中被定义为枚举的一部分。

    好。

    创建一个BeanItemContainer,填充枚举的实例,并告诉Vaadin提供显示文本的"属性"(用于反射地找到匹配的getter方法)的名称。

    好。

    考虑到文档有关覆盖toString的警告,这种方法除了更灵活之外,可能更明智。

    好。

    1
    2
    3
    4
    5
    6
    BeanItemContainer<SomeClass.CRITTER_FILTER> radiosBic = new BeanItemContainer<SomeClass.CRITTER_FILTER>( SomeClass.CRITTER_FILTER.class );
    radiosBic.addAll( Arrays.asList( SomeClass.CRITTER_FILTER.values() ) );  // Convert array of values to a `Collection` object.
    this.filterRadios = new OptionGroup("Critter Filter:" , radiosBic );
    this.filterRadios.setMultiSelect( false ); // Radio buttons are single-select.
    this.filterRadios.setItemCaptionMode( AbstractSelect.ItemCaptionMode.PROPERTY );  
    this.filterRadios.setItemCaptionPropertyId("title" );  // Matches the getter method defined as part of the enum.

    这样可行。我希望它可以在Vaadin 6和7中使用。

    好。

    BeanItemContainer方法的完整示例

    让我们调整上一节中显示的示例Person和Vaadin应用。

    好。

    Person类中,将toString方法替换为JavaBeans属性getter getCaptionText。该方法的名称可以是任何名称,只要它与下面进一步在Vaadin应用程序中看到的对setItemCaptionPropertyId的调用匹配即可。

    好。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    package com.example.vaadinradiobuttons;

    import java.util.ArrayList;
    import java.util.List;

    /**
     *
     * @author Basil Bourque
     */
    public class Person {

        // Members
        String name;
        Person.VITAL_STATUS vitalStatus;

        public enum VITAL_STATUS {

            LIVING("Alive and Kicking" ),
            DECEASED("Dead" ),
            UNKNOWN("DUNNO" );

            private String captionText;
            static public String CAPTION_TEXT_PROPERTY_NAME ="captionText";  //

            VITAL_STATUS ( String t ) {
                this.captionText = t;
            }

            // JavaBeans Property getter.
            public String getCaptionText () {
                return this.captionText;
            }

        }

        // Constructor
        public Person ( String nameArg , VITAL_STATUS vitalStatusArg ) {
            this.name = nameArg;
            this.vitalStatus = vitalStatusArg;
        }

    }

    Vaadin应用程序已更改为使用BeanItemContainer。通过调用setItemCaptionPropertyId,您可以指定该容器中的哪个属性用作显示的文本。

    好。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    package com.example.vaadinradiobuttons;

    import javax.servlet.annotation.WebServlet;

    import com.vaadin.annotations.Theme;
    import com.vaadin.annotations.VaadinServletConfiguration;
    import com.vaadin.annotations.Widgetset;
    import com.vaadin.data.Property;
    import com.vaadin.data.util.BeanItemContainer;
    import com.vaadin.server.VaadinRequest;
    import com.vaadin.server.VaadinServlet;
    import com.vaadin.ui.OptionGroup;
    import com.vaadin.ui.UI;
    import com.vaadin.ui.VerticalLayout;
    import java.util.Arrays;
    import java.util.Collection;

    /**
     *
     */
    @Theme ("mytheme" )
    @Widgetset ("com.example.vaadinradiobuttons.MyAppWidgetset" )
    public class MyUI extends UI {

        @Override
        protected void init ( VaadinRequest vaadinRequest ) {
            final VerticalLayout layout = new VerticalLayout();
            layout.setMargin( true );
            setContent( layout );

            // Core of example.
            Collection<Person.VITAL_STATUS> v = Arrays.asList( Person.VITAL_STATUS.values() );
            BeanItemContainer<Person.VITAL_STATUS> bic = new BeanItemContainer<>( Person.VITAL_STATUS.class , v );
            OptionGroup radios = new OptionGroup("Vital Status :" , bic );
            radios.setItemCaptionPropertyId(Person.VITAL_STATUS.CAPTION_TEXT_PROPERTY_NAME );  // …or… ("captionText" );
            radios.setImmediate( true );
            radios.addValueChangeListener( ( Property.ValueChangeEvent event ) -> {
                Person.VITAL_STATUS vitalStatus = ( Person.VITAL_STATUS ) event.getProperty().getValue();
                System.out.println("User selected a vital status name:" + vitalStatus.name() +", labeled:" + vitalStatus.toString() );
            } );
            layout.addComponent( radios );

        }

        @WebServlet ( urlPatterns ="/*" , name ="MyUIServlet" , asyncSupported = true )
        @VaadinServletConfiguration ( ui = MyUI.class , productionMode = false )
        public static class MyUIServlet extends VaadinServlet {
        }
    }

    好。


    病毒素

    Viritin是Vaadin的附加组件,它具有一个非常方便的字段,称为EnumSelect。 它可以从已编辑的属性中自动检测可用属性。 您还可以通过一个策略来显示用户界面上的标题。

    基本用法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
        EnumSelect<AddressType> select = new EnumSelect<AddressType>()
                .withSelectType(OptionGroup.class);
        select.setStyleName(ValoTheme.OPTIONGROUP_HORIZONTAL);

        // The Enum type is detected when the edited property is bound to select
        // This typically happens via basic bean binding, but here done manually.
        ObjectProperty objectProperty = new ObjectProperty(AddressType.Home);
        select.setPropertyDataSource(objectProperty);

        // Alternatively, if not using databinding at all, you could just use
        // basic TypedSelect, or the method from it
        // select.setOptions(AddressType.values());

    请注意,当前发行版的打字方式受到限制。 我刚刚修复了该问题,显示的类型化api将在下一个版本中发布。