如何正确渲染Wicket中GridView组件第一列,解决类型转换报错
Wicket GridView二次渲染类型转换错误解决方案
问题现象
我使用GridView组件展示周餐日历,通过newRowItem()方法在第一列添加了标注「早餐、午餐、晚餐、夜宵」等餐段名称的Label组件。
首次点击Select链接打开日历页面时,组件渲染正常;点击Close链接关闭后再次点击Select打开,会抛出「Label cannot be cast to MarkupContainer」错误。
补充说明:移除第一列后,周餐日历组件运行符合预期,无报错
报错日志
Last cause: class org.apache.wicket.markup.html.basic.Label cannot be cast to class org.apache.wicket.MarkupContainer (org.apache.wicket.markup.html.basic.Label and org.apache.wicket.MarkupContainer are in unnamed module of loader org.apache.catalina.loader.ParallelWebappClassLoader @24fab44a) WicketMessage: Error attaching this container for rendering: [Form [Component id = form]]
相关截图


实现代码
SimpleMealCalendarFormPopup.java
public class SimpleMealCalendarFormPopup extends Panel { public SimpleMealCalendarFormPopup(String id, IModel<Meal> mealModel) { super(id, mealModel); setVisible(false); setOutputMarkupPlaceholderTag(true); var dataProvider = new MealWeekDayDataProvider(new MockMealDayList().getMealDayList()); var form = new Form<Void>("form"); form.add(new SimpleMealCalendarGridView("rows", dataProvider, mealModel)); form.add(new CloseAjaxLink("close")); form.add(new SummaryLink("summary")); add(form); } private class CloseAjaxLink extends LoadingAjaxLink { public CloseAjaxLink(String id) { super(id); } @Override public IModel<?> getBody() { return new ResourceModel("CLOSE").wrapOnAssignment(this); } @Override public void onClick(AjaxRequestTarget target) { if (target != null) { SimpleMealCalendarFormPopup.this.setVisible(false); target.add(this, SimpleMealCalendarFormPopup.this); } } } private static class SummaryLink extends Link<Void> {} }
SimpleMealCalendarFormPopup.html
<tr wicket:id="rows"> <td> <span wicket:id="meal_time"></span> </td> <td wicket:id="cols"> <label wicket:id="label"> <input class="has-tooltip-warning" type="checkbox" wicket:id="mealSelected"/> </label> </td> </tr>
SimpleMealCalendarGridView.java
public class SimpleMealCalendarGridView extends GridView<MealWeekDay> { private IModel<Meal> mealModel; public SimpleMealCalendarGridView(String id, IDataProvider<MealWeekDay> dataProvider, IModel<Meal> mealModel) { super(id, dataProvider); this.mealModel = mealModel; } @Override protected void populateEmptyItem(Item<MealWeekDay> item) {} @Override protected void populateItem(Item<MealWeekDay> item) { var label_container = new LabelContainer("label", item.getModel()); label_container.add(new MealCheckBox("mealSelected", item.getModel())); item.add(label_container); } @Override protected Item<?> newRowItem(String id, int index) { return new FirstItemInGridRow(id, index); } @Override public int getRows() { return 6; } @Override public int getColumns() { return 7; } @Override protected void onDetach() { super.onDetach(); if (mealModel != null) { mealModel.detach(); } } private class LabelContainer extends WebMarkupContainer { public LabelContainer(String id, IModel<MealWeekDay> mealWeekDayModel) { super(id, mealWeekDayModel); add(new AttributeAppender("class", "has-background-warning") { @Override public boolean isEnabled(Component component) { var meal_week_day = (MealWeekDay) getDefaultModelObject(); return meal_week_day.isMealSelected(); } }); } } private class MealCheckBox extends CheckBox { // 省略实现 } }
FirstItemInGridRow.java
public class FirstItemInGridRow extends Item<String> { public FirstItemInGridRow(String id, int index) { super(id, index); // index = 网格行号 ResourceModel translation = switch (index) { case 0 -> new ResourceModel("mealTimes.BREAKFAST"); case 1 -> new ResourceModel("mealTimes.LUNCH"); case 2 -> new ResourceModel("mealTimes.DINNER_I"); case 3 -> new ResourceModel("mealTimes.DINNER_II"); case 4 -> new ResourceModel("mealTimes.ELEVENSES"); case 5 -> new ResourceModel("mealTimes.SUPPER"); default -> throw new IllegalStateException("Unexpected value: " + index); }; add(new Label("meal_time", translation)); } }
问题原因
Wicket的GridView默认会开启组件复用,第一次渲染生成的组件实例会被缓存,第二次打开弹窗时会复用之前的组件实例。你自定义了行组件FirstItemInGridRow并在其中添加了Label组件,复用过程中出现了组件ID和类型不匹配的问题,Wicket误将之前生成的Label实例当作容器尝试添加子组件,因此抛出类型转换错误。
解决方案
- 临时修复:在
SimpleMealCalendarGridView的构造方法中添加setReuseItems(false),强制GridView每次渲染时重新创建所有组件实例,不再复用旧实例,即可解决报错。 - 结构优化:你当前在GridView的行模板中手动添加了额外的餐段列
<td>,不符合GridView的标准使用规范。建议将列数调整为8,第一列专门用于渲染餐段名称,在populateItem方法中判断列索引,第0列渲染餐段Label,其余列渲染复选框,从根源上避免DOM结构和组件树不匹配的问题。
内容的提问来源于stack exchange,提问作者Slawek
相关产品推荐
相关产品推荐

