如何为SWT Java虚拟表格列头添加复选框并修复全选异常
SWT Virtual Table 全选复选框实现与滚动异常修复
一、列头带"Select All"标签的复选框实现
放弃图片模拟状态的方案,直接通过控件嵌入+状态变量维护的方式实现,避免状态不同步问题:
1. 嵌入列头复选框
直接在第一列列头区域添加复选框控件,绑定点击事件:
private boolean isSelectAll = false; // 用集合维护选中行的索引(虚拟表不能依赖TableItem本身) private Set<Integer> selectedRows = new HashSet<>(); private Table table; // 初始化列头复选框 private void initSelectAllHeader() { TableColumn firstCol = table.getColumn(0); Button selectAllBtn = new Button(table, SWT.CHECK); selectAllBtn.setText("Select All"); // 定位按钮到列头位置 Point btnSize = selectAllBtn.computeSize(SWT.DEFAULT, SWT.DEFAULT); Rectangle headerRect = firstCol.getHeaderBounds(); selectAllBtn.setBounds(headerRect.x + 5, headerRect.y + 2, btnSize.x, btnSize.y); // 全选/取消全选逻辑 selectAllBtn.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { isSelectAll = selectAllBtn.getSelection(); selectedRows.clear(); if (isSelectAll) { // 大数据量下不要遍历所有行,用标志位替代(后续优化见第二部分) for (int i = 0; i < table.getItemCount(); i++) { selectedRows.add(i); } } table.redraw(); } }); }
2. 行复选框绘制与状态同步
在虚拟表的PaintItem事件中绘制行复选框,基于维护的选中集合判断状态:
table.addListener(SWT.PaintItem, event -> { if (event.index != 0) return; // 只处理第一列 TableItem item = (TableItem) event.item; Integer rowIndex = (Integer) item.getData("rowIndex"); if (rowIndex == null) return; // 绘制复选框边框 Rectangle cellRect = event.getBounds(); int checkboxSize = 16; int x = cellRect.x + (cellRect.width - checkboxSize) / 2; int y = cellRect.y + (cellRect.height - checkboxSize) / 2; event.gc.drawRectangle(x, y, checkboxSize - 1, checkboxSize - 1); // 绘制选中状态 if (selectedRows.contains(rowIndex)) { event.gc.fillRectangle(x + 2, y + 2, checkboxSize - 4, checkboxSize - 4); } });
3. 手动选中行时同步列头状态
监听鼠标点击第一列的事件,更新选中集合并同步列头复选框状态:
table.addListener(SWT.MouseDown, event -> { TableItem item = table.getItem(new Point(event.x, event.y)); if (item == null) return; Rectangle firstColRect = item.getBounds(0); if (!firstColRect.contains(event.x, event.y)) return; Integer rowIndex = (Integer) item.getData("rowIndex"); if (selectedRows.contains(rowIndex)) { selectedRows.remove(rowIndex); } else { selectedRows.add(rowIndex); } updateSelectAllBtnState(); table.redraw(); }); // 更新列头复选框状态(支持全选/未选/部分选中) private void updateSelectAllBtnState() { Button selectAllBtn = (Button) table.getChildren()[0]; int totalRows = table.getItemCount(); int selectedCount = selectedRows.size(); if (selectedCount == 0) { selectAllBtn.setSelection(false); isSelectAll = false; } else if (selectedCount == totalRows) { selectAllBtn.setSelection(true); isSelectAll = true; } else { // 部分选中状态:可自定义绘制灰色横线,或切换图片 selectAllBtn.setSelection(false); isSelectAll = false; // 示例:用GC绘制中间状态 // GC gc = new GC(selectAllBtn); // gc.setForeground(Display.getCurrent().getSystemColor(SWT.COLOR_GRAY)); // gc.drawLine(x+2, y+8, x+14, y+8); // gc.dispose(); } }
二、大数据量滚动时行数据丢失但复选框选中的问题修复
该问题源于虚拟表的TableItem复用机制,首次加载时行索引未正确绑定,导致选中集合与显示行不匹配:
1. 强制绑定行索引到TableItem
在虚拟表的SetData事件中,给每个复用的TableItem绑定当前行的真实索引(核心修复点):
table.addListener(SWT.SetData, event -> { TableItem item = (TableItem) event.item; int rowIndex = event.index; // 虚拟表当前行对应的数据源索引 item.setData("rowIndex", rowIndex); // 填充当前行的业务数据 YourDataModel data = yourDataSource.get(rowIndex); item.setText(1, data.getDocName()); item.setText(2, data.getCreateTime()); // ...其他列数据 });
2. 大数据量全选优化(避免遍历所有行)
全选时不用遍历5000+行,改用isSelectAll标志位+未选中集合的方式,减少内存占用和初始化时间:
// 替换原selectedRows为unselectedRows private Set<Integer> unselectedRows = new HashSet<>(); // 全选按钮点击事件修改 selectAllBtn.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { isSelectAll = selectAllBtn.getSelection(); unselectedRows.clear(); if (!isSelectAll) { // 取消全选时,将所有行加入未选中集合 for (int i = 0; i < table.getItemCount(); i++) { unselectedRows.add(i); } } table.redraw(); } }); // 行复选框选中判断修改 if ((isSelectAll && !unselectedRows.contains(rowIndex)) || (!isSelectAll && !unselectedRows.contains(rowIndex))) { event.gc.fillRectangle(x + 2, y + 2, checkboxSize - 4, checkboxSize - 4); }
3. 首次加载后强制刷新
数据加载完成后,手动触发一次表格刷新和列头状态同步:
// 数据源加载完成后调用 private void afterDataLoaded() { updateSelectAllBtnState(); table.redraw(); }
核心注意事项
- 虚拟表的选中状态必须基于数据源索引,而非
TableItem对象或顺序,因为TableItem会被复用。 - 避免使用
TableItem.setSelection()方法维护选中状态,该方法仅适用于非虚拟表。 - 列头部分选中状态建议用自定义GC绘制,比图片切换更稳定,不会出现资源加载延迟问题。
内容的提问来源于stack exchange,提问作者Sanjay
相关产品推荐
相关产品推荐

