JFace EditingSupport for Custom Tables
Stan Lee in Spider Man taught us that, "With great powerthere must also come -- great responsibility!" I think about thatquote when working on Eclipse plug-ins, only it changes in my head toread, "With great power there must also come -- great complexity!" Eclipse provides the building blocks to do wonderfully powerful things,but to wade into plug-in development is to wade into all sorts ofcomplexity.My most recent responsibility/complexity was tomake a service properties chooser/editor in the BUG Application codegeneration wizard. This functionality (still in development at thetime of this writing), will allow BUG Application developers tightercontrol of the filters used by the Application's Service Tracker.
Serviceproperty values are stored as strings, but they can represent booleans,numbers, or Strings. I wanted to create a table where each row is aservice property key/value pair. The developer can then choose theproperties they want to include in the filter and modify the values forthat property. Moreover, I wanted the property value to be editablelike a text field for things like numbers, but use a combo box forthings like boolean values. Here's what the table looks like:
http://communityasset2.buglabs.net/photos/0000/0629/jface-2_large.png?1248200738
Itlooks straight forward enough, but the implementation turned out to berather challenging. The custom behavior of the cells pushed me towardusing JFace CellEditorS and EditingSupport. I found some tutorials andcode-snippets on-line, but nothing suited my specific needs, which wereto have checkbox support and selection events on the rows, plusdifferent CellEditorS for each of the second column's cells, dependingon the type of value. The most helpful tutorial I found is here: http://www.vogella.de/articles/EclipseJFaceTable/article.html. It didn't solve all my problems, but it certainly gave me a start.
Aftersome time-consuming web-slinging, pining over the above tutorial, andsome old-fashion trial and error, I was able to finally make the thingwork. Here, I share my solution in the hope that it will help whereother resources fall short:
First, let's start out with the class. This particular JFace component was added to a WizardPage:
public class CodeGenerationPage extends WizardPage { Next, define our main TableViewer as an instance variable:
// Main JFace componentprivate CheckboxTableViewer servicePropertiesViewer; All of the wizard page drawing is kicked off from the createControlmethod, which you must override. Inside there, we create the JFacecomponents -- a CheckboxTableViewer which is the surrounding table, andTableColumnViewerS for the columns -- and put it all together:
// table with list of properties to choose from// compServices is a Group that we're puttingall of this stuff infinal Table propertiesTable = new Table( compServices, SWT.CHECK | SWT.BORDER | SWT.V_SCROLL | SWT.FULL_SELECTION);propertiesTable.setHeaderVisible(true);propertiesTable.setLinesVisible(true);// layout of columns in tableTableLayout propTableLayout = new TableLayout();propTableLayout.addColumnData(new ColumnWeightData(90));propTableLayout.addColumnData(new ColumnWeightData(120));propertiesTable.setLayout(propTableLayout);// layout of table on the pageGridData pViewerData = new GridData(GridData.FILL_BOTH);pViewerData.horizontalSpan = layout.numColumns;pViewerData.heightHint = SERVICE_PROPERTIES_HEIGHT_HINT;propertiesTable.setLayoutData(pViewerData);// viewer for services listservicePropertiesViewer = new CheckboxTableViewer(propertiesTable);servicePropertiesViewer.setContentProvider(new ServicePropsContentProvider());// Add a listener to do something when a checkbox on a row is selectedservicePropertiesViewer.addCheckStateListener(new ICheckStateListener() {public void checkStateChanged(CheckStateChangedEvent event) { // You can do something when a row is selected here }});// Column 0 - checkbox and property name// col0 is taken care of by checkboxtableviewerTableViewerColumn col0viewer = new TableViewerColumn(servicePropertiesViewer, SWT.FULL_SELECTION, 0);// TableViewerColumn needs a label providercol0viewer.setLabelProvider(new ColumnLabelProvider() { @Override public String getText(Object element) { return ((ServicePropertyHelper) element).getKey(); }});col0viewer.getColumn().setText(KEY_LABEL);// Column 1 - property value w/ celleditors// col1 has custom cell editors defined in EditingSupport belowTableViewerColumn col1viewer = new TableViewerColumn(servicePropertiesViewer, SWT.FULL_SELECTION, 1);col1viewer.setLabelProvider(new ColumnLabelProvider() { @Override public String getText(Object element) { return ((ServicePropertyHelper) element).getSelectedValue(); }});col1viewer.getColumn().setText(VALUE_LABEL);// col1viewer has editing support// this is where the magic happens that sets a different celleditor dependingcol1viewer.setEditingSupport( new PropertyValueEditingSupport(col1viewer.getViewer())); The comments in the code should help explain things, but there are twoitems worthy of extra attention. First, by using aCheckboxTableViewer, we can get a selection event from the table andalso access selected elements viaservicePropertiesViewer.getCheckedElements(). For the second column'sCellEditorS, we need to decide, per-cell, which editor to use (whetherto use a TextCellEditor or a ComboBoxCellEditor) based on what thepotential values are. We do this by adding EditingSupport to thecolumn, i.e. col1viewer.setEditingSupport(). Here is most of theEditingSupport implementation (with all the helper functions strippedout for brevity):
public class PropertyValueEditingSupport extends EditingSupport { private final String[] truefalse = new String[] {"true", "false"}; private Composite parent; private TextCellEditor text_editor; private ComboBoxCellEditor combobox_editor; public PropertyValueEditingSupport(ColumnViewer viewer) { super(viewer); parent =((TableViewer) viewer).getTable(); text_editor = new TextCellEditor(parent); combobox_editor = new ComboBoxCellEditor(parent, new String); } @Override protected boolean canEdit(Object element) { return true; } @Override protected CellEditor getCellEditor(Object element) { ServicePropertyHelper propertyHelper = ((ServicePropertyHelper) element); if (usesTextEditor(propertyHelper.getValues())) { // Ints and blanks use text editor return text_editor; } else { // everything else uses a combobox if (hasBools(propertyHelper.getValues())) // boolean combos prefill w/ true and false combobox_editor.setItems(truefalse); else // other types, just do set the combobox to values combobox_editor.setItems(propertyHelper.getValuesAsArray()); return combobox_editor; } } @Override protected Object getValue(Object element) { ServicePropertyHelper propertyHelper = ((ServicePropertyHelper) element); if (usesTextEditor(propertyHelper.getValues())) { return propertyHelper.getSelectedValue(); } else { return Integer.valueOf(propertyHelper.getSelectedIndex()); } } @Override protected void setValue(Object element, Object value) { // Get the current service that's been selected ServicePropertyHelper propertyHelper = ((ServicePropertyHelper) element); if (usesTextEditor(propertyHelper.getValues())) { // if it's a text field, just set the value propertyHelper.setSelectedValue("" + value); } else if (hasBools(propertyHelper.getValues())) { // if it's a boolean, value is an index in truefalse array propertyHelper.setSelectedValue(truefalse); } else { // if it's something else, value is an index in the service property values set String val = propertyHelper.getValueAt(Integer.valueOf("" + value)); if (val != null) propertyHelper.setSelectedValue(val); } getViewer().update(element, null); }}In the constructor, we create our two CellEditorS. We then overridegetCellEditor(Object element) to return the proper cell editor for thepassed element. Element is an element in the array returned fromservicePropertiesViewer's ContentProvider.getElements() method (I mustpoint out that the relationship between a TableViewer, it'sContentProvider, it's TableViewerColumnS, and a TableViewerColumn'sEditingSupport is pretty confusing. The tutorial mentioned aboveshould help clear all that up if you're lost. Also, if you're still inthe dark about JFace viewers, label providers, content providers, andinputs, Eclipse: Building Commercial-Quality Plug-insis a must-read). We must also override a couple of otherEditingSupport methods: canEdit(), getValue(Object element), andsetValue(Object element, Object value). The important thing to note isthat the values (given and returned) for a TextCellEditor are Strings,and for a ComboBoxCellEditor, Integers. My model object (when I set upmy CheckBoxTableViewer, a list of model objects is set with thesetInput() method), is called ServicePropertyHelper. It keeps track ofthe possible values and the set/selected values for a property. Italso has some helper methods for setting these, which thePropertyValueEditingSupport methods call.
So, this is obviouslyrather complex stuff, but it's powerful as well. Using EditingSupportand CellEditorS gives very fine-grained control over JFace TableViewerSfor doing real custom Interfaces. Lastly, the full classes can befound in our svn tree at svn://svn.buglabs.net/dragonfly/trunk/com.buglabs.dragonfly.ui/src/com/buglabs/dragonfly/ui/wizards/bugProject and I'll be happy to answer any questions I can if you find yourself wrangling with similar problems.
页:
[1]