我已经为 GXT 3.X 版本实现了具有几乎所有功能的 MultiSelectComboBox。
这是我的代码:
import com.google.gwt.cell.client.AbstractCell;
import com.google.gwt.cell.client.Cell;
import com.google.gwt.cell.client.ValueUpdater;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasKeyDownHandlers;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.safecss.shared.SafeStyles;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.sencha.gxt.core.client.IdentityValueProvider;
import com.sencha.gxt.core.client.Style;
import com.sencha.gxt.core.client.dom.XElement;
import com.sencha.gxt.data.shared.LabelProvider;
import com.sencha.gxt.data.shared.ListStore;
import com.sencha.gxt.data.shared.ModelKeyProvider;
import com.sencha.gxt.widget.core.client.event.RowClickEvent;
import com.sencha.gxt.widget.core.client.event.RowMouseDownEvent;
import com.sencha.gxt.widget.core.client.form.TextField;
import com.sencha.gxt.widget.core.client.grid.CheckBoxSelectionModel;
import com.sencha.gxt.widget.core.client.grid.ColumnConfig;
import com.sencha.gxt.widget.core.client.grid.ColumnModel;
import com.sencha.gxt.widget.core.client.grid.Grid;
import com.sencha.gxt.widget.core.client.menu.Menu;
import com.sencha.gxt.widget.core.client.selection.SelectionChangedEvent;
import com.sencha.gxt.widget.core.client.tips.QuickTip;
import java.util.ArrayList;
import java.util.List;
public class MultiSelectComboBox<T> extends HorizontalPanel implements HasKeyDownHandlers {
private static final Icons ICONS = GWT.create(Icons.class);
private static final int SINGLE_ENTRY_HEIGHT = 22;
private static final int IMAGE_WIDTH = 17;
private TextField textField;
private Menu menu;
private Grid<SimpleValue> grid;
private ModelKeyProvider<T> keyProvider;
private LabelProvider<T> labelProvider;
private List<T> values;
private ListStore<SimpleValue> store;
private GridClickHandler clickHandler;
private CheckBoxSelectionModel<SimpleValue> sm;
private String noSelectionLabel;
private int listWidth;
private int width;
private MultiSelectComboBox(final ListStore<SimpleValue> store, ModelKeyProvider<T> keyProvider, LabelProvider<T> labelProvider, List<T> values, String noSelectionLabel, int width, int listWidth) {
this.store = store;
this.keyProvider = keyProvider;
this.labelProvider = labelProvider;
this.values = values;
this.noSelectionLabel = noSelectionLabel;
this.width = width;
this.listWidth = listWidth;
init();
}
public List<T> getValues() {
List<T> selectedItems = new ArrayList<>();
for (SimpleValue data : sm.getSelectedItems()) {
selectedItems.add(findRealValueInternal(data.getKey()));
}
return selectedItems;
}
public void setValue(final T value) {
setValues(new ArrayList<T>() {{add(value);}});
}
public void setValues(List<T> values) {
for (T value : values) {
boolean alreadyExists = findRealValueInternal(keyProvider.getKey(value)) != null;
if (!alreadyExists) {
values.add(value);
grid.getStore().add(SimpleValue.create(value, keyProvider, labelProvider));
}
}
for (T value : values) {
SimpleValue simpleValue = findSimpleValueInternal(keyProvider.getKey(value));
if (!sm.isSelected(simpleValue)) {
sm.select(simpleValue, true);
}
}
}
public void addAll(List<T> list) {
for (T v : list) {
add(v);
}
grid.setHeight(values.size() * SINGLE_ENTRY_HEIGHT);
}
public void add(T value) {
boolean alreadyExists = findRealValueInternal(keyProvider.getKey(value)) != null;
if (!alreadyExists) {
values.add(value);
grid.getStore().add(SimpleValue.create(value, keyProvider, labelProvider));
}
}
public void clearStore() {
values.clear();
grid.getStore().clear();
}
private SimpleValue findSimpleValueInternal(String key) {
SimpleValue value = null;
for (SimpleValue v : grid.getStore().getAll()) {
if (v.getKey().equals(key)) {
value = v;
break;
}
}
return value;
}
public boolean isValid() {
return textField.isValid();
}
@SuppressWarnings("GWTStyleCheck")
private void init() {
menu = new Menu();
initGrid();
menu.add(grid);
textField = new TextField();
textField.setReadOnly(true);
textField.setValue(noSelectionLabel);
textField.setWidth(width);
textField.addStyleName("multiComboTextFieldStyle");
textField.addKeyDownHandler(new KeyDownHandler() {
@Override
public void onKeyDown(KeyDownEvent event) {
if (event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER) {
textField.finishEditing();
textField.sinkEvents(KeyCodes.KEY_ENTER);
}
}
});
Image image = new Image(ICONS.arrowDown());
image.addStyleName("multiComboButtonStyle");
image.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent clickEvent) {
menu.showAt(textField.getAbsoluteLeft(), textField.getAbsoluteTop() + textField.getOffsetHeight());
}
});
add(textField);
add(image);
}
@SuppressWarnings({"unchecked", "GWTStyleCheck"})
private void initGrid() {
IdentityValueProvider<SimpleValue> identity = new IdentityValueProvider<>();
sm = new CheckBoxSelectionModel<SimpleValue>(identity) {
@Override
protected void onRowClick(RowClickEvent event) {}
@Override
protected void onRowMouseDown(RowMouseDownEvent event) {
boolean left = event.getEvent().getButton() == Event.BUTTON_LEFT;
XElement target = event.getEvent().getEventTarget().cast();
if (left && this.getAppearance().isRowChecker(target)) {
controlSelection(listStore.get(event.getRowIndex()));
}
}
};
sm.setSelectionMode(Style.SelectionMode.MULTI);
sm.addSelectionChangedHandler(new SelectionChangedEvent.SelectionChangedHandler<SimpleValue>() {
@Override
public void onSelectionChanged(SelectionChangedEvent<SimpleValue> simpleValueSelectionChangedEvent) {
setValuesToTextField();
}
});
ColumnModel columnModel = new ColumnModel(getConfigs());
grid = new Grid<>(store, columnModel, new ZGridView<SimpleValue>());
grid.setColumnReordering(true);
grid.getView().setColumnLines(true);
grid.setSelectionModel(sm);
grid.setWidth(listWidth);
grid.setHeight(values.size() * SINGLE_ENTRY_HEIGHT);
grid.setHideHeaders(true);
grid.getView().setColumnLines(false);
grid.addStyleName("z-grid-style");
new QuickTip(grid);
clickHandler = new GridClickHandler() {
@Override
public void onClick(Cell.Context context, Object value) {
controlSelection((SimpleValue)((Object[])value)[0]);
}
};
}
@SuppressWarnings("unchecked")
private List<ColumnConfig> getConfigs() {
List<ColumnConfig> configs = new ArrayList<>();
ColumnConfig<SimpleValue, String> columnConfig = new ColumnConfig(new ZEmptyValueProvider(), listWidth - sm.getColumn().getWidth());
columnConfig.setCell(new AbstractCell<String>("click") {
@Override
public void render(Context context, String value, SafeHtmlBuilder sb) {
String htmlValue = store.get(context.getIndex()).getLabel();
sb.append(SafeHtmlUtils.fromTrustedString(htmlValue != null ? "<div style=\"font-size: 12px;\">" + htmlValue + "</div>": ""));
}
@Override
public void onBrowserEvent(Context context, Element parent, String value, NativeEvent event, ValueUpdater<String> valueUpdater) {
super.onBrowserEvent(context, parent, value, event, valueUpdater);
if (("click").equals(event.getType())) {
if (clickHandler != null) {
Object[] objects = new Object[1];
objects[0] = store.get(context.getIndex());
clickHandler.onClick(context, objects);
}
}
}
});
columnConfig.setColumnStyle(new SafeStyles() {
@Override
public String asString() {
return "cursor: pointer;";
}
});
columnConfig.setColumnStyle(new SafeStyles() {
@Override
public String asString() {
return "border: none;";
}
});
configs.add(sm.getColumn());
configs.add(columnConfig);
return configs;
}
private void controlSelection(SimpleValue model) {
if (model != null) {
if (sm.isSelected(model)) {
sm.deselect(model);
} else {
sm.select(model, true);
}
}
}
private void setValuesToTextField() {
if (sm.getSelectedItems().size() > 0) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < sm.getSelectedItems().size(); i++) {
sb.append(sm.getSelectedItems().get(i).getLabel());
if (i != sm.getSelectedItems().size() - 1) {
sb.append(", ");
}
}
textField.setValue(sb.toString());
textField.setToolTip(sb.toString());
} else {
textField.setValue(noSelectionLabel);
textField.removeToolTip();
}
}
private T findRealValueInternal(String key) {
T value = null;
for (T v : values) {
if (key.equals(keyProvider.getKey(v))) {
value = v;
break;
}
}
return value;
}
@Override
public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) {
return addDomHandler(handler, KeyDownEvent.getType());
}
public void setEnabled(boolean enabled) {
textField.setEnabled(enabled);
}
public void setToolTip(String tooltip) {
textField.setToolTip(tooltip);
}
public static class Builder<T> {
private ModelKeyProvider<T> keyProvider;
private LabelProvider<T> labelProvider;
private String noSelectionLabel;
private List<T> values = new ArrayList<>();
private int listWidth;
private int width = 130;
public Builder<T> keyProvider(ModelKeyProvider<T> keyProvider) {
this.keyProvider = keyProvider;
return this;
}
public Builder labelProvider(LabelProvider<T> labelProvider) {
this.labelProvider = labelProvider;
return this;
}
public Builder noSelectionLabel(String noSelectionLabel) {
this.noSelectionLabel = noSelectionLabel;
return this;
}
public Builder values(List<T> values) {
this.values = values;
return this;
}
public Builder listWidth(int listWidth) {
this.listWidth = listWidth;
return this;
}
public Builder width(int width) {
this.width = width;
return this;
}
@SuppressWarnings("unchecked")
public MultiSelectComboBox build() {
if (keyProvider == null) {
throw new IllegalStateException("KeyProvider is required");
}
if (labelProvider == null) {
throw new IllegalStateException("LabelProvider is required");
}
if (noSelectionLabel == null) {
noSelectionLabel = "";
}
ListStore<SimpleValue> store = new ListStore<>(new ModelKeyProvider<SimpleValue>() {
@Override
public String getKey(SimpleValue item) {
return item.getKey();
}
});
if (values.size() > 0) {
for (T obj : values) {
store.add(SimpleValue.create(obj, keyProvider, labelProvider));
}
}
return new MultiSelectComboBox(store, keyProvider, labelProvider, values, noSelectionLabel, width - IMAGE_WIDTH, listWidth == 0 ? width : listWidth - IMAGE_WIDTH);
}
}
public static class SimpleValue {
private String key;
private String label;
public SimpleValue() {}
public static <T> SimpleValue create(T obj, ModelKeyProvider<T> keyProvider, LabelProvider<T> labelProvider) {
SimpleValue v = new SimpleValue();
v.setKey(keyProvider.getKey(obj));
v.setLabel(labelProvider.getLabel(obj));
return v;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
}
}
用法:
MultiSelectComboBox<String> combo = new MultiSelectComboBox.Builder<String>()
.values(Arrays.asList(Enum.values()))
.noSelectionLabel("All Values")
.width(150)
.listWidth(180)
.keyProvider(new ModelKeyProvider<String>() {
@Override
public String getKey(String s) {
return s;
}
})
.labelProvider(new LabelProvider<String>() {
@Override
public String getLabel(String s) {
return s;
}
})
.build();