You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 15:47:04