0

使用 GWT 2.4...

我正在构建一个复杂的复合双视图/编辑模式实现,该实现由 GWT 的 DataGrid 和 MultiSelectionModel 提供支持。我的目标是让用户能够单击他们想要发布更新的每一行中的复选框。

这是半功能界面的屏幕截图:

能源供应输入屏幕示例

注意选定的(突出显示的)行。

现在的问题是,当我在任何单元格中输入内容时(例如,$/Mw 1 复合单元格标题下的第一行 $ 单元格),然后单击该行的复选框(或任何其他行的复选框)以选择或取消选择,第一次请求屏幕数据时,该值将重置为原始值。任何时候都不想要的行为!

让我们看一下我对网格的自定义实现。(请原谅长度)。

public abstract class ToggleableGrid<T extends Identifiable<?>> extends Composite {

private static final int CHKBOX_COLUMN_WIDTH = App.INSTANCE.checkboxColumnWidth();
private static final DisplayMode DEFAULT_MODE = DisplayMode.VIEW;


private ProvidesKey<T> keyProvider;

private DataGrid<T> grid;

private MultiSelectionModel<T> selectionModel;

private ListDataProvider<T> dataProvider;
private int tabIndex = 0;


public ToggleableGrid() {
    final DataGridConfiguration config = new DefaultDataGridConfiguration();
    initGrid(config);
}

public ToggleableGrid(DataGridConfiguration config) {
    initGrid(config);
}

private void initGrid(DataGridConfiguration config) {
    keyProvider = new ProvidesKey<T>() {
        @Override
        public Object getKey(T item) {
            return item == null ? null : item.getId();
        }
    };
    grid = new DataGrid<T>(config.getPageSize(), config.getResources(), keyProvider);
    // Set the message to display when the table is empty.
    grid.setEmptyTableWidget(new Label(UiMessages.INSTANCE.no_results()));
    initWidget(grid);
    setVisible(true);
}

public void setInput(List<T> content) {
    setInput(content, DEFAULT_MODE);
}

public void setInput(List<T> content, DisplayMode mode) {
    resetTableColumns();
    if (isInEditMode(mode)) {
        // Add a selection model so we can select cells
        selectionModel = new MultiSelectionModel<T>(keyProvider);
        grid.setSelectionModel(selectionModel, DefaultSelectionEventManager.<T> createCheckboxManager(0));
        addRowSelector();
    }
    dataProvider = new ListDataProvider<T>(content);
    final ListHandler<T> sortHandler = new ListHandler<T>(dataProvider.getList());
    grid.addColumnSortHandler(sortHandler);
    initializeStructure(constructMetadata(), sortHandler, mode);
    dataProvider.addDataDisplay(grid);
}

// see https://stackoverflow.com/questions/3772480/remove-all-columns-from-a-celltable
// concrete classes are forced to maintain a handle on all columns added
private void resetTableColumns() {
    for (final Column<T, ?> column: allColumns()) {
        grid.removeColumn(column);
    }
    allColumns().clear();
}

protected boolean isInEditMode(DisplayMode currentDisplayMode) {
    boolean result = false;
    if (currentDisplayMode.equals(DisplayMode.EDIT)) {
        result = true;
    }
    return result;
}

protected abstract Set<Column<T, ?>> allColumns();

protected abstract TableMetadata constructMetadata();

protected abstract void initializeStructure(TableMetadata metadata, ListHandler<T> sortHandler, DisplayMode mode);

protected void setColumnHorizontalAlignment(Column<T, ?> column, HorizontalAlignmentConstant alignment) {
    column.setHorizontalAlignment(alignment);
}

// TODO figure out how to add a checkbox to column header that provides select/de-select all capability

// see https://stackoverflow.com/questions/6174689/gwt-celltable-programmatically-select-checkboxcell
protected void addRowSelector() {
    final Column<T, Boolean> rowSelectColumn = new Column<T, Boolean>(new CheckboxCell(true, false)) {

        @Override
        public Boolean getValue(T value) {
            Boolean result;
            // check for null value and return null;
            if(value == null || value.getId() == null) {
                result = null;
            } else {  // get value from the selection model
                result = selectionModel.isSelected(value);
            }
            return result;
        }

    };

    addColumn(rowSelectColumn, UiMessages.INSTANCE.select());
    setColumnWidth(rowSelectColumn, CHKBOX_COLUMN_WIDTH, Unit.PX);
    setColumnHorizontalAlignment(rowSelectColumn, HasHorizontalAlignment.ALIGN_CENTER);
}

protected void setColumnWidth(Column<T, ?> column, int width, Unit unit) {
    grid.setColumnWidth(column, width, unit);
}

protected void addColumn(Column<T, ?> column, String columnHeaderName) {
    addColumn(column, columnHeaderName, HasHorizontalAlignment.ALIGN_RIGHT);
}

protected void addColumn(Column<T, ?> column, String columnHeaderName, HorizontalAlignmentConstant alignment) {
    final SafeHtmlBuilder sb = new SafeHtmlBuilder();
    final String divStart = "<div align=\""+ alignment.getTextAlignString() + "\" class=\"" +UiResources.INSTANCE.style().word_wrap() + "\">";
    sb.appendHtmlConstant(divStart).appendEscaped(columnHeaderName).appendHtmlConstant("</div>");
    final SafeHtml header = sb.toSafeHtml();
    grid.addColumn(column, header);
    allColumns().add(column);
}

protected CompositeCell<T> generateCompositeCell(final List<HasCell<T, ?>> hasCells) {
    final CompositeCell<T> compositeCell = new CompositeCell<T>(hasCells) {

        @Override
        public void render(Context context, T value, SafeHtmlBuilder sb) {
            sb.appendHtmlConstant("<table><tbody><tr>");
            super.render(context, value, sb);
            sb.appendHtmlConstant("</tr></tbody></table>");
        }

        @Override
        protected Element getContainerElement(Element parent) {
            // Return the first TR element in the table.
            return parent.getFirstChildElement().getFirstChildElement().getFirstChildElement();
        }

        @Override
        protected <X> void render(Context context, T value,
                SafeHtmlBuilder sb, HasCell<T, X> hasCell) {
            final Cell<X> cell = hasCell.getCell();
            sb.appendHtmlConstant("<td>");
            cell.render(context, hasCell.getValue(value), sb);
            sb.appendHtmlConstant("</td>");
        }
    };
    return compositeCell;
}

// FIXME not working quite the way we'd expect, index incremented within column for each row, not each row by column
protected int nextTabIndex() {
    tabIndex++;
    return tabIndex;
}

protected AbstractCellTable<T> getGrid() {
    return grid;
}

/**
 * Gets the selected (row(s) of) data from grid (used in edit mode)
 * @return the selected data (as per selection model)
 */
public List<T> getSelectedData() {
    final List<T> data = new ArrayList<T>();
    data.addAll(selectionModel.getSelectedSet());
    return data;
}

/**
 * Gets all (row(s) of) data in grid (used in edit mode)
 * @return all data as list
 */
public List<T> getAllData() {
    return dataProvider.getList();
}

/**
 * Clears the currently selected (row(s) of) data (used in edit mode)
 */
public void clearSelectedData() {
    selectionModel.clear();
    grid.redraw();
}

}

所以,盯着上面的有趣方法(我认为)是setInputgenerateCompositeCelladdRowSelector

我们用 List 数据初始化网格,并在 setInput 中设置显示模式。在这里也初始化了选择模型。它使用 GWT 的 DefaultSelectionEventManager createCheckboxManager()。

我一直在尝试理解事件模型,但它让我望而却步。我已经在线访问了以下资源,但在解决此问题的途径上还不够。

-- https://groups.google.com/forum/?fromgroups#!topic/google-web-toolkit/k5sfURxDaVg AbstractInputCell 的 getConsumedEventsImpl 增加了焦点、模糊和 keydown,所以这(我相信)不是我需要探索的轨道

-- GWT CellTable 以编程方式选择 CheckBoxCell 可以实例化 CheckBoxCell 的各种方法让我感到好奇,我尝试了许多构造函数参数排列,但我选择的一种(真,假)是(我相信)正确的一种

在这里和现在(在被谴责之前)同意我的实施中可能存在一些不必要的复杂性,但我仍在寻找指导。谢谢!

更新

如果它有帮助,这里是前面提到的 ToggleableGrid 的一个实现。如果有的话,它会为您提供有关每个 CompositeCell 内容的更多详细信息。有关 AbstractValidatableColumn 和 ValidatableInputCell 的详细信息,请参阅:寻找 GWT 验证示例...你在哪里?.

public class EnergyOfferGrid extends ToggleableGrid<EnergyOfferDTO> {

public EnergyOfferGrid() {
    super();
}

public EnergyOfferGrid(DataGridConfiguration config) {
    super(config);
}

private static final int MAX_NUMBER_OF_MW_PRICE_POINTS = App.INSTANCE.maxNoOfMwPricePoints();

private Set<Column<EnergyOfferDTO, ?>> columns = new HashSet<Column<EnergyOfferDTO, ?>>();

@Override
protected Set<Column<EnergyOfferDTO, ?>> allColumns() {
    return columns;
}

@Override
protected TableMetadata constructMetadata() {
    final TableMetadata metadata = new TableMetadata();

    // TODO Consider a predefined set of ReferenceData to be held in a common package

    // Use Slope
    metadata.addColumnMetadata(UiMessages.INSTANCE.use_slope(), new String[] {UiMessages.INSTANCE.yes(), UiMessages.INSTANCE.no()}, new String[] {"true", "false"});

    return metadata;
}

@Override
protected void initializeStructure(TableMetadata metadata, ListHandler<EnergyOfferDTO> sortHandler, DisplayMode currentDisplayMode) {
    addHourColumn(sortHandler);
    addUseSlopeColumn(metadata, sortHandler, currentDisplayMode);
    for (int i = 0; i < MAX_NUMBER_OF_MW_PRICE_POINTS; i++) {  // zero-based indexing
        addPriceMwColumn(i, currentDisplayMode);
    }
}

protected void addHourColumn(ListHandler<EnergyOfferDTO> sortHandler) {
    final Column<EnergyOfferDTO, String> hourColumn = new Column<EnergyOfferDTO, String>(new TextCell()) {

        @Override
        public String getValue(EnergyOfferDTO energyOffer) {
            String result = "";
            if (energyOffer.getId() != null) {
                final String isoDateTime = energyOffer.getId().getOperatingHour();
                if (isoDateTime != null && !isoDateTime.isEmpty()) {
                    final Date dateTime = CSTimeUtil.isoToDate(isoDateTime);
                    if (dateTime != null) {
                        result = CSTimeUtil.dateToHour(dateTime);
                    }
                }
            }
            return result;
        }

    };
    hourColumn.setSortable(true);
    sortHandler.setComparator(hourColumn, new Comparator<EnergyOfferDTO>() {
        @Override
        public int compare(EnergyOfferDTO eo1, EnergyOfferDTO eo2) {
            final String date1 = eo1.getId() != null ? eo1.getId().getOperatingHour() : "";
            final String date2 = eo2.getId() != null ? eo2.getId().getOperatingHour() : "";
            return date1.compareTo(date2);
        }
    });

    // We know that the data is sorted by hour by default.
    getGrid(). getColumnSortList().push(hourColumn);

    addColumn(hourColumn, UiMessages.INSTANCE.hour());
    setColumnWidth(hourColumn, 45, Unit.PX);
    setColumnHorizontalAlignment(hourColumn, HasHorizontalAlignment.ALIGN_RIGHT);
}


protected void addUseSlopeColumn(TableMetadata metadata, ListHandler<EnergyOfferDTO> sortHandler, DisplayMode currentDisplayMode) {
    final ReferenceData refData = metadata.allColumnMetadata().get(UiMessages.INSTANCE.use_slope());
    Column<EnergyOfferDTO, String> useSlopeColumn;
    Cell<String> cell;
    if (isInEditMode(currentDisplayMode)) {
        cell = new ReferenceDataBackedSelectionCell(refData);
    } else {
        cell = new TextCell();
    }
    useSlopeColumn = new Column<EnergyOfferDTO, String>(cell) {

        @Override
        public String getValue(EnergyOfferDTO energyOffer) {
            return refData.getDisplayValueForSubmitValue(Boolean.toString(energyOffer.isSlopeUsed()));
        }

    };

    useSlopeColumn.setSortable(true);
    sortHandler.setComparator(useSlopeColumn, new Comparator<EnergyOfferDTO>() {
        @Override
        public int compare(EnergyOfferDTO eo1, EnergyOfferDTO eo2) {
            final String slopeUsed1 = String.valueOf(eo1.isSlopeUsed());
            final String slopeUsed2 = String.valueOf(eo1.isSlopeUsed());
            return slopeUsed1.compareTo(slopeUsed2);
        }
    });

    addColumn(useSlopeColumn, UiMessages.INSTANCE.use_slope());
    setColumnWidth(useSlopeColumn, 75, Unit.PX);
    setColumnHorizontalAlignment(useSlopeColumn, HasHorizontalAlignment.ALIGN_RIGHT);
}

protected void addPriceMwColumn(final int colIndex, DisplayMode currentDisplayMode) {

    // Construct a composite cell for energy offers that includes a pair of text inputs
    final List<HasCell<EnergyOfferDTO, ?>> columns = new ArrayList<
            HasCell<EnergyOfferDTO, ?>>();

    // this DTO is passed along so that price and mw values for new entries are kept together
    final OfferPriceMwPair newOfferPriceMwPair = new OfferPriceMwPair();

    // Price
    final Column<EnergyOfferDTO, String> priceColumn = generatePriceColumn(colIndex, newOfferPriceMwPair, currentDisplayMode);
    columns.add(priceColumn);

    // MW
    final Column<EnergyOfferDTO, String> mwColumn = generateMwColumn(colIndex, newOfferPriceMwPair, currentDisplayMode);
    columns.add(mwColumn);

    // Composite
    final CompositeCell<EnergyOfferDTO> priceMwColumnInnards = generateCompositeCell(columns);

    final IdentityColumn<EnergyOfferDTO> priceMwColumn = new IdentityColumn<EnergyOfferDTO>(priceMwColumnInnards);

    final StringBuilder colHeader = new StringBuilder();
    colHeader.append(UiMessages.INSTANCE.price_mw_header()).append(" ").append(String.valueOf(colIndex + 1));

    addColumn(priceMwColumn, colHeader.toString());
    setColumnWidth(priceMwColumn, 7, Unit.EM);
    setColumnHorizontalAlignment(priceMwColumn, HasHorizontalAlignment.ALIGN_RIGHT);
}

protected Column<EnergyOfferDTO, String> generatePriceColumn(final int colIndex, final OfferPriceMwPair newOfferPriceMwPair, DisplayMode currentDisplayMode) {
    Column<EnergyOfferDTO, String> priceColumn;

    if (isInEditMode(currentDisplayMode)) {
        priceColumn = new BigDecimalValidatableColumn<EnergyOfferDTO, OfferPriceMwPair>(nextTabIndex(), getGrid()) {

            @Override
            public String getValue(EnergyOfferDTO energyOffer) {
                return obtainPriceValue(colIndex, energyOffer, false);
            }

            @Override
            public void doUpdate(int index, EnergyOfferDTO energyOffer, String value) {
                if (value != null && !value.isEmpty()) {
                    // number format exceptions should be caught and handled by event bus's handle method
                    final double valueAsDouble = NumberFormat.getDecimalFormat().parse(value);

                    final BigDecimal price = BigDecimal.valueOf(valueAsDouble);
                    final List<OfferPriceMwPair> offerPriceCurve = energyOffer.getCurve();
                    final OfferPriceMwPair offerPriceMwPair = offerPriceCurve.get(colIndex);
                    if (offerPriceMwPair == null) {  // we have a new price value
                        newOfferPriceMwPair.setPrice(price);
                        offerPriceCurve.add(newOfferPriceMwPair);
                    } else {
                        offerPriceMwPair.setPrice(price);
                    }

                }
            }

            @Override
            protected String getPropertyName() {
                return "price";
            }

            @Override
            protected Class<OfferPriceMwPair> getPropertyOwner() {
                return OfferPriceMwPair.class;
            }

        };
    } else {
        priceColumn = new Column<EnergyOfferDTO, String>(new TextCell()) {

            @Override
            public String getValue(EnergyOfferDTO energyOffer) {
                final String result = obtainPriceValue(colIndex, energyOffer, true);
                return result;
            }
        };
    }
    return priceColumn;
}

private String obtainPriceValue(final int colIndex, EnergyOfferDTO energyOffer, boolean withCurrency) {
    String result = "";
    if (energyOffer != null) {
        final List<OfferPriceMwPair> offerPriceCurve = energyOffer.getCurve();
        final int numberOfPairs = offerPriceCurve.size();
        if (colIndex < numberOfPairs) {
            final OfferPriceMwPair offerPriceMwPair = offerPriceCurve.get(colIndex);
            if (offerPriceMwPair != null) {
                final BigDecimal price = offerPriceMwPair.getPrice();
                if (price != null) {
                    final double value = price.doubleValue();
                    if (withCurrency) {
                        result = NumberFormat.getCurrencyFormat().format(value);
                    } else {
                        result = NumberFormat.getDecimalFormat().format(value);
                    }
                }

            }
        }
    }
    return result;
}

protected Column<EnergyOfferDTO, String> generateMwColumn(final int colIndex, final OfferPriceMwPair newOfferPriceMwPair, DisplayMode currentDisplayMode) {
    Column<EnergyOfferDTO, String> mwColumn;

    if (isInEditMode(currentDisplayMode)) {
        mwColumn = new BigDecimalValidatableColumn<EnergyOfferDTO, PriceMwPair>(nextTabIndex(), getGrid()) {

            @Override
            public String getValue(EnergyOfferDTO energyOffer) {
                return obtainMwValue(colIndex, energyOffer);
            }

            @Override
            public void doUpdate(int index, EnergyOfferDTO energyOffer, String value) {
                if (value != null && !value.isEmpty()) {
                    // number format exceptions should be caught and handled by event bus's handle method
                    final double valueAsDouble = NumberFormat.getDecimalFormat().parse(value);

                    final BigDecimal mw = BigDecimal.valueOf(valueAsDouble);
                    final List<OfferPriceMwPair> offerPriceCurve = energyOffer.getCurve();
                    final OfferPriceMwPair offerPriceMwPair = offerPriceCurve.get(colIndex);
                    if (offerPriceMwPair == null) {  // we have a new price value
                        newOfferPriceMwPair.setMw(mw);
                        offerPriceCurve.add(newOfferPriceMwPair);
                    } else {
                        offerPriceMwPair.setMw(mw);
                    }

                }
            }

            @Override
            protected String getPropertyName() {
                return "mw";
            }

            @Override
            protected Class<PriceMwPair> getPropertyOwner() {
                return PriceMwPair.class;
            }

        };
    } else {
        mwColumn = new Column<EnergyOfferDTO, String>(new TextCell()) {

            @Override
            public String getValue(EnergyOfferDTO energyOffer) {
                final String result = obtainMwValue(colIndex, energyOffer);
                return result;
            }
        };
    }
    return mwColumn;
}

private String obtainMwValue(final int colIndex, EnergyOfferDTO energyOffer) {
    String result = "";
    if (energyOffer != null) {
        final List<OfferPriceMwPair> offerPriceCurve = energyOffer.getCurve();
        final int numberOfPairs = offerPriceCurve.size();
        if (colIndex < numberOfPairs) {
            final PriceMwPair offerPriceMwPair = offerPriceCurve.get(colIndex);
            if (offerPriceMwPair != null) {
                final BigDecimal mw = offerPriceMwPair.getMw();
                if (mw != null) {
                    result = NumberFormat.getDecimalFormat().format(mw);
                }
            }
        }
    }
    return result;
}

} 
4

1 回答 1

1

所有这些定制工作都是不必要的 WrapperCellCompositeValidatableColumn

事实证明,有一种方法你不应该构造CompositeCells。请参阅http://code.google.com/p/google-web-toolkit/issues/detail?id=5714。我CompositeCell的 s 没有收到事件。所以,我改变了构建它们的方式ToggleableGrid

protected CompositeCell<T> generateCompositeCell(final List<HasCell<T, String>> hasCells) {
    final CompositeCell<T> compositeCell = new CompositeCell<T>(hasCells) {

        // to not run afoul of http://code.google.com/p/google-web-toolkit/issues/detail?id=5714
        @Override
        public void render(Context context, T value, SafeHtmlBuilder sb) {
            sb.appendHtmlConstant("<div style=\"display: inline\">");
            super.render(context, value, sb);
            sb.appendHtmlConstant("</div>");
        }

        @Override
        protected Element getContainerElement(Element parent) {
            // Return the first element in the DIV.
            return parent.getFirstChildElement();
        }

    };

    return compositeCell;
}

在那次更改并合并了我的其他面向验证的类之后:ValidatableFieldUpdater, AbstractValidatableColumn(和派生类)ValidatableInputFieldConversionResult,生活再美好不过了!

于 2012-06-01T20:51:26.473 回答