前言
上节说到, 创建和渲染表格需要如下几个步骤:
- 接收源数据数组(也可以是单个对象或者其他集合类型):
TableViewer.setInput(Object)
- 渲染接收的数据
- 渲染表头:
TableViewer.setLabelProvider(IBaseLabelProvider)
- 渲染内容:
TableViewer.setContentProvider(IContentProvider)
- 渲染表头:
在实际应用中我们往往需要编辑表格并实现双向绑定, 本节内容主要集中讲如何对表格单元格添加编辑支持.
需求
当双击单元格时进入编辑模式
步骤
添加编辑支持
Jface
提供了EditingSupport
抽象类, 可以很方便的实现, 不过需要配合TableViewerColumn
使用, 上节我们根据表头数组创建了列:
String[] titles = {"ID", "姓名", "性别", "年龄"};
// 创建列头信息, 并最终绑定到table
Arrays.stream(titles).forEach(title -> TableColumnFactory.newTableColumn(SWT.NONE).width(80).text(title).create(table));
如需添加编辑支持可以直接根据TableColumn
创建TableViewerColumn
并将每一列对应的EditingSupport
实现赋值给TableViewerColumn
即可.
实现EditingSupport
需要覆写的方法简介:
CellEditor getCellEditor(Object)
: 当前列对应的编辑器类型, 主要有:TextCellEditor
: 文字编辑器, 非常通用CheckboxCellEditor
: 复选编辑器ComboBoxCellEditor
: 下拉列表编辑器ColorCellEditor
: 颜色编辑器DialogCellEditor
: 对话编辑器, 这是高级用法, 可以实现个性定制
boolean canEdit(Object)
: 当前列是否支持编辑Object getValue(Object)
: 编辑初始状态显示的值void setValue(Object oldValue, Object newValue)
: 编辑结束时需要赋值的逻辑, 第一个参数为编辑前对应的值, 第二个为编辑后对应的新值, 值类型取决于编辑器, 比如TextCellEditor
对应的就是String
类型.
需要注意的是, 当我们接受新值后, 要刷新下当前表格, 否则界面展示依然是之前的值, 也就是说我们在
setValue
方法的最后需要加上一行tableViewer.update(o, null);
此时我们丰富一下创建表头的逻辑, 这里列出空实现:
// 创建列头信息, 并最终绑定到table
Arrays.stream(titles).forEach(title -> {
TableColumn tableColumn = TableColumnFactory.newTableColumn(SWT.NONE).width(80).text(title).create(table);
// 创建TableViewerColumn关联到当前列并添加编辑支持
new TableViewerColumn(tableViewer, tableColumn).setEditingSupport(new EditingSupport(tableViewer) {
@Override
protected CellEditor getCellEditor(Object o) {
return null;
}
@Override
protected boolean canEdit(Object o) {
return false;
}
@Override
protected Object getValue(Object o) {
return null;
}
@Override
protected void setValue(Object o, Object o1) {
// 赋值逻辑...
tableViewer.update(o, null);
}
});
});
添加触发条件
仅仅添加编辑支持是不够的, 因为系统不知道什么时候切换为编辑状态, 比如我们只希望在双击当前单元格时开启编辑状态, Jface
提供了ColumnViewerEditorActivationStrategy
来控制策略:
ColumnViewerEditorActivationStrategy activationStrategy = new ColumnViewerEditorActivationStrategy(tableViewer) {
@Override
protected boolean isEditorActivationEvent(ColumnViewerEditorActivationEvent event) {
// 只有双击事件才激活编辑器
return event.eventType == ColumnViewerEditorActivationEvent.MOUSE_DOUBLE_CLICK_SELECTION;
}
};
此时打开界面发现单击表格也进入了编辑状态, 并且一次性就高亮显示整行, 这和需求不符, 我们需要借助TableViewerEditor
来强制激活此策略, 并集成TableViewerFocusCellManager
来高亮显示本单元格而不是整行:
TableViewerFocusCellManager focusCellManager = new TableViewerFocusCellManager(tableViewer, new FocusCellOwnerDrawHighlighter(tableViewer));
TableViewerEditor.create(tableViewer, focusCellManager, activationStrategy, ColumnViewerEditor.DEFAULT);
此时我们只需要将之前的EditingSupport
根据实际业务完善下基本就OK了, 整体源码见下一小节, 先看下效果.
数据校验
实际业务中可能对某些数据有特殊要求, 此时就要对输入的数据进行校验, 并给出提示, 我们可以借助MessageBox
来实现提示, 将校验逻辑放在EdittingSupport.setValue
方法中.
比如对年龄的校验可以这样写:
@Override
protected void setValue(Object o, Object o1) {
String newValue = String.valueOf(o1);
if (o instanceof People people) {
switch (title) {
case "年龄" -> {
try {
people.setAge(Integer.parseInt(newValue));
} catch (Exception e) {
MessageBox messageBox = new MessageBox(shell);
messageBox.setText("输入不合法");
messageBox.setMessage("必须是数字");
messageBox.open();
}
}
// 其他逻辑
}
}
tableViewer.update(o, null);
}
看下效果:
源码
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.ColumnViewerEditor;
import org.eclipse.jface.viewers.ColumnViewerEditorActivationEvent;
import org.eclipse.jface.viewers.ColumnViewerEditorActivationStrategy;
import org.eclipse.jface.viewers.EditingSupport;
import org.eclipse.jface.viewers.FocusCellOwnerDrawHighlighter;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.TableViewerEditor;
import org.eclipse.jface.viewers.TableViewerFocusCellManager;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.jface.widgets.TableColumnFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
final Display display = Display.getDefault();
final Shell shell = new Shell();
shell.setLayout(new FillLayout());
shell.setSize(500, 375);
shell.setText("SWT Application");
//注意这里,SWT.MULTI代表可以选择多行,SWT.FULL_SELECTION代表可以整行选择,SWT.BORDER边框,SWT.V_SCROLL ,SWT.H_SCROLL滚动条
TableViewer tableViewer = new TableViewer(shell,
SWT.MULTI | SWT.FULL_SELECTION | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
Table table = tableViewer.getTable();
// 表格边框线是否可见
table.setLinesVisible(true);
// 表头是否可见
table.setHeaderVisible(true);
// 设置表格大小
table.setBounds(97, 79, 373, 154);
String[] titles = {"ID", "姓名", "性别", "年龄"};
// 创建列头信息, 并最终绑定到table
Arrays.stream(titles).forEach(title -> {
TableColumn tableColumn = TableColumnFactory.newTableColumn(SWT.NONE).width(80).text(title).create(table);
new TableViewerColumn(tableViewer, tableColumn).setEditingSupport(new EditingSupport(tableViewer) {
@Override
protected CellEditor getCellEditor(Object o) {
return new TextCellEditor(tableViewer.getTable());
}
@Override
protected boolean canEdit(Object o) {
return !"ID".equalsIgnoreCase(title);
}
@Override
protected Object getValue(Object o) {
if (o instanceof People people) {
return switch (title) {
case "ID" -> String.valueOf(people.getId());
case "姓名" -> people.getName();
case "性别" -> people.getSex();
case "年龄" -> String.valueOf(people.getAge());
default -> "";
};
}
return "";
}
@Override
protected void setValue(Object o, Object o1) {
String newValue = String.valueOf(o1);
if (o instanceof People people) {
switch (title) {
case "年龄" -> {
try {
people.setAge(Integer.parseInt(newValue));
} catch (Exception e) {
MessageBox messageBox = new MessageBox(shell);
messageBox.setText("输入不合法");
messageBox.setMessage("必须是数字");
messageBox.open();
}
}
case "姓名" -> people.setName(newValue);
case "性别" -> people.setSex(newValue);
}
}
tableViewer.update(o, null);
}
});
});
ColumnViewerEditorActivationStrategy activationStrategy = new ColumnViewerEditorActivationStrategy(
tableViewer) {
@Override
protected boolean isEditorActivationEvent(ColumnViewerEditorActivationEvent event) {
// 只有双击事件才激活编辑器
return event.eventType == ColumnViewerEditorActivationEvent.TRAVERSAL
|| event.eventType == ColumnViewerEditorActivationEvent.MOUSE_DOUBLE_CLICK_SELECTION
|| event.eventType == ColumnViewerEditorActivationEvent.PROGRAMMATIC;
}
};
table.setHeaderBackground(display.getSystemColor(SWT.COLOR_TITLE_BACKGROUND));
table.setHeaderForeground(display.getSystemColor(SWT.COLOR_TITLE_FOREGROUND));
TableViewerFocusCellManager focusCellManager = new TableViewerFocusCellManager(tableViewer,
new FocusCellOwnerDrawHighlighter(tableViewer));
int feature = ColumnViewerEditor.TABBING_HORIZONTAL | ColumnViewerEditor.TABBING_MOVE_TO_ROW_NEIGHBOR
| ColumnViewerEditor.TABBING_VERTICAL | ColumnViewerEditor.KEYBOARD_ACTIVATION;
TableViewerEditor.create(tableViewer, focusCellManager, activationStrategy, feature);
tableViewer.setContentProvider(ArrayContentProvider.getInstance());
tableViewer.setLabelProvider(PeopleLabelProvider.getInstance());
People people = new People();
people.setId(1);
people.setName("张三");
people.setSex("男");
people.setAge(10);
tableViewer.setInput(new People[] {people});
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
}
}
其他方案
实现编辑支持还有其他方式, 比如实现ICellModifier
, 不过这种方式需要额外指定properties
用来指定和列名的对应关系, 个人不是很喜欢这种, 有兴趣可以参考: Swt/Jface tableViewer入门教程三(加入在表格上直接编辑数据)