【发布时间】:2015-04-01 23:30:06
【问题描述】:
我有一个带有 getter 的 Java 枚举,用于显示所需的文本。如何使用它来填充 Vaadin 7 中的 OptionGroup?
【问题讨论】:
我有一个带有 getter 的 Java 枚举,用于显示所需的文本。如何使用它来填充 Vaadin 7 中的 OptionGroup?
【问题讨论】:
以下是在 Vaadin 7 中执行此操作的三种方法:
EnumBackedOptionGroup。 Vaadin 7 中 OptionGroup 的子类。OptionGroup 的子类
这是我写的OptionGroup的一个新子类的源代码。
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 来检索这个标题文本。
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;
}
}
感谢this Answer WillShackleford 在我的问题Lambda syntax to pass and invoke a method reference 上的this Answer,我才能完成这门课。
要使用此EnumBackedGroupOption 类,请传递其类和该标题渲染方法的方法引用。这需要 Java 8 中的新 Lambda syntax。但您无需掌握 Lambda 的理解,只需按照您在此处看到的模式即可。
OptionGroup optionGroup = new EnumBackedOptionGroup<DogBreed>( "Choose Breed:" , DogBreed.class , DogBreed :: getTitle );
对于快速而简单的原型设计,您可以定义一个没有此类构造函数和 getter 的简单枚举。在这种情况下,只传递您的标题和枚举类。 EnumBackedOptionGroup 类回退到使用内置的 toString 方法。我和Enum 类文档都不推荐这条路线用于严肃的工作,toString 应该只用于调试。
package com.example;
/**
* Bogus example Enum.
*/
public enum SaySo {
YES, NO, MAYBE;
}
OptionGroup optionGroup = new EnumBackedOptionGroup<SaySo>( "Says you:" , SaySo.class );
有时您可能不想在您的 OptionGroup 中使用所有枚举的实例值。如果是这样,请使用this Question 中解释的隐式方法values 提取这些实例的集合。删除不需要的。请注意我们如何从Arrays.asList 的输出中实例化一个新的ArrayList 以允许此修改。然后将该集合传递给EnumBackedOptionGroup 的另一个构造函数。
您可以将 null 作为最后一个参数传递,以使用 toString 作为演示标签。
您可以使用EnumMap 或EnumSet 代替.values,但我没有这方面的经验。
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 中。
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 方法以返回存储的显示文本。
我不推荐这种方法。 documentation 建议仅当您想创建一个特殊值以在调试工作中显示给程序员时才覆盖 toString。不过,我确实尝试过这种方法,它确实有效。
公共字符串 toString()
... 这个方法可能会被覆盖,尽管它通常不是必需的或不可取的。当存在更“程序员友好”的字符串形式时,枚举类型应覆盖此方法。
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 类,带有嵌套枚举。
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. 以查看重要行。
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 方法。你可以使用任何你想要的方法名,除了 getName 或 name 已经在 Java 中定义为枚举的一部分。
创建一个 BeanItemContainer,填充我们的枚举实例,并告诉 Vaadin 提供显示文本的“属性”(用于反射性地查找匹配的 getter 方法)的名称。
除了更灵活之外,考虑到文档对覆盖toString 的警告,这种方法可能更明智。
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 的调用匹配即可。
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,您可以指定该容器中的哪些属性应该用作要显示的文本。
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 {
}
}
【讨论】:
toString并让它返回标题,因为标题的默认值是toString
toString。不过,我在我的回答中添加了一个关于您建议的方法的部分。
OptionGroup 的子类来处理所有工作。这个子类简洁、简单、独立且类型安全。在此答案的顶部发布了源代码。只需传递您的枚举类。
Viritin 是 Vaadin 的 add-on,有一个非常方便的字段 EnumSelect。它可以从编辑的属性中自动检测可用的属性。您也可以只传递在 UI 上显示为标题的策略。
基本用法
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 将在下一个版本中发布。
【讨论】: