SWT TableViewer

By | 2018年4月7日

SWT Table控件,本身没有提供编辑功能,JFace TableViewer及相关组件,不仅提供了编辑功能,并且可以自定义如何显示数据。

  • TableViewer是对Table的封装。
  • TableViewerColumn是对TableColumn的封装。

1. 使用TableViewer

TableViewer通过调用setInput(Object)方法,来设置TableViewer的数据。数据需要以下处理,才能显示:

  1. input需要被解析成Element Array。如果input是数组或Collection,可以使用ArrayContentProvider。如果不是,我们就要定义自己的IStructuredContentProvider来转换input为Element Array。
  2. 定义LabelProvider,如何根据Element来显示Cell的Text, Image。

1.1. 定义Content Provider

通常不需要定义,使用ArrayContentProvider便可。如果不是Collection或Array就自己定义如何从Input转换成Element数组。

public class StudentTableContentProvider implements IStructuredContentProvider {

    @Override
    public void dispose() {
        // Dispose some resource.
    }

    @Override
    public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
        // Do something when input is changed.
    }

    @Override
    public Object[] getElements(Object inputElement) {
        if (inputElement instanceof Object[]) {
            return (Object[]) inputElement;
        }
        if (inputElement instanceof Collection) {
            return ((Collection) inputElement).toArray();
        }
        return new Object[0];
    }
}

1.2. 定义Label Provider

1.2.1. 整个TableViewer添加

可以调用TableViewer的setLabelProvider(IBaseLabelProvider)给所有列添加LabelProvider。这个Label provider需要实现ITableLabelProvider,为了减少不必要的override方法,又继承了ColumnLabelProvider。

private static class StudentLabelProvider extends ColumnLabelProvider implements ITableLabelProvider {
    @Override
    public Image getColumnImage(Object element, int columnIndex) {
        return null;
    }

    @Override
    public String getColumnText(Object element, int columnIndex) {
        Student student = (Student) element;
        switch(columnIndex) {
            case 0:
                return student.getName();
            case 1:
                return student.getSex().getName();
            case 2:
                return student.getCountry();
            case 3:
                return String.valueOf(student.getHeight());
            case 4:
                return student.isMarried() ? "YES" : "NO";
            case 5:
                return "";
            default:
            return null;
        }
    }
}

tableViewer.setLabelProvider(new StudentLabelProvider());

1.2.2. TableViewerColumn单独加

给整个TableViewer加Label Provider有个缺陷,就是不能动态返回Cell的foreground color, background color, font和Tooltip等,但是ColumnLabelProvider提供了这些接口可以做到。

TableViewer设置了Label Provider后,我们依然可以再给TableViewerColum单独设置Label Provider,它们没有冲突,但是会互相覆盖,哪个后设置,哪个起作用。通常是先给整根table设置Label Provider后,再对特别需求的Column设置Label Provider。

tableViewer.setLabelProvider(new StudentLabelProvider());

colorViewerColumn.setLabelProvider(new ColumnLabelProvider() {
    @Override
    public String getText(Object element) {
        return "Color";
    }
    
    @Override
    public Color getForeground(Object element) {
        Student student = (Student) element;
        RGB rgb = student.getFavoriteColor();
        
        if (!colorMap.containsKey(rgb)) {
            colorMap.put(rgb, new Color(Display.getDefault(), rgb));
        }
        return colorMap.get(rgb);
    }
});

image

2. 提供编辑功能

2.1. TableViewer添加CellEditor

2.1.1. 定义每列的CellEditor

private CellEditor[] createEditors(Table table) {
    CellEditor[] cellEditors = new CellEditor[6];
    cellEditors[0] = new TextCellEditor(table);
    
    cellEditors[1] = new ComboBoxCellEditor(table, 
            new String[]{Sex.MALE.getName(), Sex.FEMALE.getName()});
    
    cellEditors[2] = new TextCellEditor(table);
    
    cellEditors[3] = new TextCellEditor(table);
    cellEditors[3].setValidator(new ICellEditorValidator() {
        @Override
        public String isValid(Object value) {
            try {
                Double.parseDouble((String) value); 
                return null;
            } catch (Exception e) {
                return value.toString();
            }
        }
    });
    
    cellEditors[4] = new CheckboxCellEditor(table);
    
    cellEditors[5] = new ColorCellEditor(table);
    return cellEditors;
}

2.1.2. 添加列的Property

就是给每列起个别名,Modifier里通过这个别名,知道自己在修改哪列。没有setColumnProperties,后面添加的Modifier会出错。

tableViewer.setColumnProperties(new String[] {"0", "1", "2", "3", "4", "5"});

2.1.3. 定义ICellModifier

每个方法通过property确定修改的是哪列,getValue返回的值,要跟CellEditor匹配。

  • TextCellEditor:value是String类型。数值类型,也要转换成String。
  • ComboBoxCellEditor:value是其在ComboBox里的下标,int类型。
  • CheckboxCellEditor:value是布尔类型。
  • ColorCellEditor:value是SWT RGB。
private static class StudentModifier implements ICellModifier {
    private TableViewer tableViewer;
    
    private StudentModifier(TableViewer tableViewer) {
        this.tableViewer = tableViewer;
    }
    
    @Override
    public boolean canModify(Object element, String property) {
        return true;
    }

    @Override
    public Object getValue(Object element, String property) {
        Student student = (Student) element;
        switch(property) {
            case "0":
                return student.getName();
            case "1":
                return student.getSex().ordinal();
            case "2":
                return student.getCountry();
            case "3":
                return String.valueOf(student.getHeight());
            case "4":
                return student.isMarried();
            case "5":
                return student.getFavoriteColor();
            default: 
                return null;
        }
    }

    @Override
    public void modify(Object element, String property, Object value) {
        TableItem item = (TableItem) element;
        Student student = (Student) item.getData();
        switch(property) {
            case "0":
                student.setName((String) value);
                break;
            case "1":
                student.setSex(Sex.fromOrdinal((Integer) value));
                break;
            case "2":
                student.setCountry(String.valueOf(value));
                break;
            case "3":
                student.setHeight(Double.valueOf((String) value));
                break;
            case "4":
                student.setMarried((boolean) value);
                break;
            case "5":
                student.setFavoriteColor((RGB) value);
                break;
            default: 
                break;
                // Do nothing.
        }            
        
        tableViewer.refresh(student);
    }
}

把Modifier设置给TableViewer。

Table table = tableViewer.getTable();
tableViewer.setCellEditors(createEditors(table));

tableViewer.setColumnProperties(new String[] {"0", "1", "2", "3", "4", "5"});
tableViewer.setCellModifier(new StudentModifier(tableViewer));

2.2. 给TableViewerColumn添加EditingSupport

2.2.1. 直接new EditingSupport

2.1的方法个人感觉有点繁琐,而且相互之间太离散了。我们可以给每个TableViewerColumn单独添加EditingSupport。下面的Example给nameViewerColumn添加EditingSupport。

nameViewerColumn.setEditingSupport(new EditingSupport(tableViewer) {
    @Override
    protected CellEditor getCellEditor(Object element) {
        return new TextCellEditor(table);
    }

    @Override
    protected boolean canEdit(Object element) {
        return element != null;
    }

    @Override
    protected Object getValue(Object element) {
        if (element instanceof Student) {
            return ((Student) element).getName();
        }
        return null;
    }

    @Override
    protected void setValue(Object element, Object value) {
        if (element instanceof Student) {
            ((Student) element).setName((String) value);
            tableViewer.refresh(element);
        }
    }
});

2.2.2 用Column index聚合成一个EdintingSupport

给每个TableViewerColumn都new 一个EditingSupport,作为匿名内部类的话太长,每个都抽成单独的类感觉类太多了。参照ITableLabelProvider我们可以只定义一个EditingSupport.

private static class StudentColumnEditingSupport extends EditingSupport {
    private final int index;
    private final Table table;
    
    public StudentColumnEditingSupport(TableViewerColumn viewerColumn) {
        super(viewerColumn.getViewer());
        
        table = ((TableViewer) viewerColumn.getViewer()).getTable();
        index = table.indexOf(viewerColumn.getColumn());
    }

    @Override
    protected CellEditor getCellEditor(Object element) {
        switch(index) {
        case 0:
            return new TextCellEditor(table);
        case 1:
            return new ComboBoxCellEditor(table, 
                    new String[]{Sex.MALE.getName(), Sex.FEMALE.getName()});
        case 2:
            return new TextCellEditor(table);
        case 3:
            TextCellEditor heightEditor = new TextCellEditor(table);
            heightEditor.setValidator(new ICellEditorValidator() {
                @Override
                public String isValid(Object value) {
                    try {
                        Double.parseDouble((String) value); 
                        return null;
                    } catch (Exception e) {
                        return value.toString();
                    }
                }
            });
            return heightEditor;
        case 4:
            return new CheckboxCellEditor(table);
        case 5:
            return new ColorCellEditor(table);
        default: 
            return null;
        }
    }

    @Override
    protected boolean canEdit(Object element) {
        return element != null;
    }

    @Override
    protected Object getValue(Object element) {
        Student student = (Student) element;
        switch(index) {
        case 0:
            return student.getName();
        case 1:
            return student.getSex().ordinal();
        case 2:
            return student.getCountry();
        case 3:
            return String.valueOf(student.getHeight());
        case 4:
            return student.isMarried();
        case 5:
            return student.getFavoriteColor();
        default: 
            return null;
        }
    }

    @Override
    protected void setValue(Object element, Object value) {
        Student student = (Student) element;
        
        switch(index) {
        case 0:
            student.setName((String) value);
            break;
        case 1:
            student.setSex(Sex.fromOrdinal((Integer) value));
            break;
        case 2:
            student.setCountry(String.valueOf(value));
            break;
        case 3:
            student.setHeight(Double.valueOf((String) value));
            break;
        case 4:
            student.setMarried((boolean) value);
            break;
        case 5:
            student.setFavoriteColor((RGB) value);
            break;
        default: 
            break;
            // Do nothing.
        }            

        ((TableViewer) getViewer()).refresh(student);
    }
    
}

总结

从代码行数来看,原始的给TableViewer添加CellEditor和分开给每个Column添加EditingSupport,都基本上一样。只是分开来设置,会更容易理解,并且可定制性更强。

代码

TableViewerUsage1,展示使用ITableLabelProvider和ColumnLabelProvider来显示data。
https://github.com/tadckle/rcp/blob/master/rcp3/rcp3.study/src/rcp3/study/viewers/TableViewerUsage1.java

TableViewerUsage2,直接给TableViewer添加CellEditor。
https://github.com/tadckle/rcp/blob/master/rcp3/rcp3.study/src/rcp3/study/viewers/TableViewerUsage2.java

TableViewerUsage3,使用EditingSupport为Column添加编辑。
https://github.com/tadckle/rcp/blob/master/rcp3/rcp3.study/src/rcp3/study/viewers/TableViewerUsage3.java