源文转自:An MVP-compatible EnumListBox for GWT
A frequent request on the GWT and gwt-presenter forums is for a ListBox that implements HasValue like a TextBox. I recently needed one myself, and thought it would be especially cool if I could use it with a Java enum type like this:
public static enum Frequency {DAILY, WEEKLY, MONTHLY};
private ConstantsWithLookup enumLabels = GWT.create(EnumLabels.class);
private EnumListBox<Frequency> freqBox;
freqBox = new EnumListBox<Frequency>(Frequency.class, enumLabels);
In keeping with MVP philosophy, the presenter’s display interface only needs the HasValue type to get and set the selected value as well as add a ValueChangeHandler to respond to a new selection. Here as some relevant excerpts from a presenter that uses the EnumListBox:
public interface Display extends WidgetDisplay
{
HasValue<Frequency> getFrequency();
}
...
protected void onFirstRequest()
{
...
display.getFrequency().addValueChangeHandler(new ValueChangeHandler<Frequency>()
{
@Override
public void onValueChange(ValueChangeEvent<Frequency> event)
{
// Do something with the newly selected event.getValue()
...
}
});
}
Here’s a straightforward implementation of an EnumListBox that implements HasValue. Thanks to the gwt-ent project for the original idea for this。
package com.roa.app.client.ui.widget;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.i18n.client.ConstantsWithLookup;
import com.google.gwt.user.client.ui.HasValue;
import com.google.gwt.user.client.ui.ListBox;
import com.roa.common.client.util.EnumTranslator;
public class EnumListBox<T extends Enum<T>> extends ListBox implements HasValue<T>
{
private final Class<T> clazzOfEnum;
private boolean valueChangeHandlerInitialized;
public EnumListBox(final Class<T> clazzOfEnum, final ConstantsWithLookup constants)
{
if (clazzOfEnum == null)
throw new IllegalArgumentException("Enum class cannot be null");
this.clazzOfEnum = clazzOfEnum;
EnumTranslator enumTranslator = new EnumTranslator(constants);
T[] values = clazzOfEnum.getEnumConstants();
for (T value : values)
{
// this.addItem(constant.toString(), constant.name());
this.addItem(enumTranslator.getText(value), value.name());
}
}
public T getSelectedValue()
{
if (getSelectedIndex() >= 0)
{
String name = getValue(getSelectedIndex());
T[] values = clazzOfEnum.getEnumConstants();
for (T value : values)
{
if (value.name().equals(name))
return value;
}
}
return null;
}
public void setSelectedValue(T value)
{
T[] values = clazzOfEnum.getEnumConstants();
for (int i = 0; i < values.length; i++)
{
if (values[i] == value)
{
this.setSelectedIndex(i);
return;
}
}
throw new IllegalArgumentException("No index found for value " + value.toString());
}
/*
* Methods to implement HasValue
*/
@Override
public T getValue()
{
return this.getSelectedValue();
}
@Override
public void setValue(T value)
{
this.setValue(value, false);
}
@Override
public void setValue(T value, boolean fireEvents)
{
T oldValue = getValue();
this.setSelectedValue(value);
if (fireEvents)
{
ValueChangeEvent.fireIfNotEqual(this, oldValue, value);
}
}
@Override
public HandlerRegistration addValueChangeHandler(ValueChangeHandler<T> handler)
{
// Initialization code
if (!valueChangeHandlerInitialized)
{
valueChangeHandlerInitialized = true;
super.addChangeHandler(new ChangeHandler()
{
public void onChange(ChangeEvent event)
{
ValueChangeEvent.fire(EnumListBox.this, getValue());
}
});
}
return addHandler(handler, ValueChangeEvent.getType());
}
}
There’s really not much to it, just a little weirdness that always comes with generics. Notice that the constructor uses an EnumTranslator to populate the labels in the ListBox. This allows you to use a standard GWT ConstantsWithLookup inteface to supply localized text for the enum values instead of the constant names. ConstantsWithLookup is just like Constants, but with the important ability to find a value dynamically without invoking a method corresponding to the property name. Unfortunately, you still have to define a method for each value of the enum in your ConstantsWithLookup class, even though it’s never used directly. Here’s a sample interface:
public interface EnumLabels extends ConstantsWithLookup {
// Enums
String com_mypackage_MyClass_Frequency_DAILY();
String com_mypackage_MyClass_Frequency_WEEKLY();
String com_mypackage_MyClass_Frequency_MONTHLY();
String com_mypackage_MyClass_Frequency_QUARTERLY();
String com_mypackage_MyClass_Frequency_YEARLY();
And the corresponding default properties file EnumLabels.properties:
com_mypackage_MyClass_Frequency_DAILY=daily
com_mypackage_MyClass_Frequency_WEEKLY=weekly
com_mypackage_MyClass_Frequency_MONTHLY=monthly
com_mypackage_MyClass_Frequency_QUARTERLY=quarterly
com_mypackage_MyClass_Frequency_YEARLY=yearly
And finally, here’s my EnumTranslator:
package com.roa.common.client.util;
import com.google.gwt.i18n.client.ConstantsWithLookup;
/**
* Does a properties file lookup to get text associated with an enum value
* Property keys use the full class name with all dots and dollars
* converted to underscores. Keys are case-sensitive and GWT requires a
* method in the interface that extends ConstantsWithLookup, even though
* the method is never called.
*
* Example:
* String my_package_class_Frequency_DAILY();
*
* In the corresponding properties file:
* my_package_class_Frequency_DAILY=daily
*
* @author David Chandler
*/
public class EnumTranslator
{
private ConstantsWithLookup constants;
public EnumTranslator(ConstantsWithLookup constants)
{
this.constants = constants;
}
public String getText(Enum e)
{
String lookupKey = e.getClass().getName() + "." + e.name();
lookupKey = lookupKey.replace(".", "_");
lookupKey = lookupKey.replace("$", "_");
return constants.getString(lookupKey);
}
}
This EnumListBox is a fairly hard-wired kind of ListBox. In the near future, I anticipate refactoring along these lines:
Add a constructor that takes any java.util.List, not just an Enum.
Create an interface HasSelectedValue that extends HasValue by adding a populateAllSelections() method. This would allow the available selections to come from the presenter through the Display interface and is thus even better for MVP. Versions of the new method could also take a java.util.List or Enum and would replace the constructor.
Ditto for a HasSelectedValues interface to deal with multiple-select type ListBoxes.
Stay tuned.