【问题标题】:CheckboxCell, MultiSelectionModel unwantonly reset DataGrid's dataCheckboxCell、MultiSelectionModel 不小心重置了 DataGrid 的数据
【发布时间】:2012-06-05 05:47:39
【问题描述】:

使用 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 添加了 focus、blur 和 keydown,所以(我相信)这不是我需要探索的轨道

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

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

更新

如果它有帮助,这里是前面提到的 ToggleableGrid 的一个 impl。如果有的话,它会为您提供有关每个 CompositeCell 内容的更多详细信息。有关 AbstractValidatableColumn 和 ValidatableInputCell 的详细信息,请参阅:In search of a GWT validation example... where art thou?

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;
}

} 

【问题讨论】:

  • 您在CompositeCell 中使用哪种Cell 来编辑值?选择一行时,表格会被重绘,因此单元格应在其视图数据中/从其保存/恢复其当前值。
  • 我的 CompositeCell 用例中有两个自定义 Cell 实现。它们基于 AbstractInputCell。我在我的更新中间接引用了 impl (ValidatableInputCell)(按照上面的“In Search Of...”链接)。我观察到的是,单击复选框时,仅针对 CompositeCell 而不是基于 ValidatableInputCell 的单个 Cell 重置值。此外,CompositeCell 被包装在 IdentityColumn 中,并作为列添加到 DataGrid。请参阅 EnergyOfferGrid 实现。这足以继续吗?
  • 如果我是你,我会在ValidatableInputCellrender() 方法中设置断点(可能是条件断点,取决于CellContext 的列索引)和/或@987654332 @首先了解发生了什么(viewData中有什么,是否正确使用等)
  • 所以我在这里错过了一些基本的东西。 AbstractValidatableColumn 仅适用于它当前负责实例化的 ValidatableInputCell 的单个实例!试图围绕同一个 AbstractValidatableColumn 如何接受一个 CompositeCell,其中它的内部是一个或多个 ValidatableInputCell。 AbstractValidatableColumn 中的 FieldUpdater 完成了大部分的验证魔术,但也负责通过 ValidatableInputCell 处理 ViewData。我的大脑被这个设计弄得晕头转向。
  • 我想我想做的是在包含 CompositeCell 的 IdentityColumn 上设置一个备用 FieldUpdater。该更新程序会以某种方式委托给 CompositeCell 中单元格的适当字段更新程序。不过不知道该怎么做。

标签: gwt datagrid checkbox selectionmodel


【解决方案1】:

所有自定义工作w.r.t。 WrapperCellCompositeValidatableColumn 是不必要的。

事实证明,有一种方法您不应该构造CompositeCells。见http://code.google.com/p/google-web-toolkit/issues/detail?id=5714。我的CompositeCells 没有收到事件。所以,我改变了我在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;
}

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

【讨论】:

    猜你喜欢
    • 2013-05-22
    • 1970-01-01
    • 2013-07-28
    • 2022-11-13
    • 2023-04-09
    • 2021-09-19
    • 2013-10-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多