| <html> |
| |
| <head> |
| <meta http-equiv="Content-Type" content="text/html; charset=windows-1252"> |
| <meta name="GENERATOR" content="Microsoft FrontPage 4.0"> |
| <meta name="ProgId" content="FrontPage.Editor.Document"> |
| <title>Building and delivering a table editor with SWT/JFace</title> |
| <link rel="stylesheet" href="../default_style.css"> |
| </head> |
| |
| <body link="#0000ff" vlink="#800080" bgcolor="#FFFFFF"> |
| |
| <div align="right"> |
| <font face="Times New Roman, Times, serif" size="2">Copyright © 2003 |
| Mirasol Op'nWorks inc.</font> |
| <table border="0" cellspacing="0" cellpadding="2" width="100%"> |
| <tr> |
| <td align="LEFT" valign="TOP" colspan="2" bgcolor="#0080C0"><b><font face="Arial,Helvetica"><font color="#FFFFFF"> Eclipse |
| Corner Article</font></font></b></td> |
| </tr> |
| </table> |
| </div> |
| <div align="left"> |
| <h1><img src="images/Idea.jpg" align="CENTER" width="120" height="86"></h1> |
| </div> |
| <p> </p> |
| <h1 align="CENTER">Building and delivering a table editor with SWT/JFace</h1> |
| <blockquote> |
| <b>Summary</b><br> |
| The JFace API provides several classes that can be used to build editable table |
| views. In this article, we present a fairly extensive example that exercises |
| the JFace and SWT classes needed to implement a table with cell editors for |
| check-boxes, free text and combo-boxes. We also show how to package and deliver |
| the classes into a stand-alone (non-Eclipse) Java application. |
| <p><b>By Laurent Gauthier, Mirasol Op'nWorks, <a href="mailto:lgauthier@opnworks.com">lgauthier@opnworks.com<br> |
| </a></b>July 3, 2003</b></p> |
| </blockquote> |
| <hr width="100%"> |
| <h2>Introduction</h2> |
| <p>Users of business software take many things for granted from modern GUI |
| applications. Their reference consists of office automation software such as |
| word processing and spreadsheet applications and they rightly expect other |
| programs to exhibit similar look and feel. They certainly do not want to hear |
| that because the application was written using this or that technology, they |
| should not expect the same behavior from the application as the one they are |
| used to. They do not understand, do not want to understand and should not even |
| have to be confronted with such an issue. The Eclipse team and more |
| specifically, the folks who worked on the SWT and JFace APIs fully understand |
| this and have done a tremendous job in providing Java developers with a toolset |
| that can be used to quickly build robust native applications. In this article, |
| we examine a small part of the SWT/JFace API, namely the constructs that allow |
| us to program a table with editable cells and sortable columns.</p> |
| <p>The example we use for this article is a task list editor. It is by no means |
| a truly useful application and the GUI design was driven essentially by the need |
| to provide examples of the use of cell editors. The code presented here can be a |
| useful starting point for projects that need the functionality of a table view |
| with editable cells.</p> |
| <h2>Installation</h2> |
| <p>The project and code described herein was designed and implemented as a standalone |
| SWT/JFace application. In other words, it was meant to run outside the Eclipse |
| environment per say although it has dependencies on some core Eclipse APIs. |
| I have wrapped the main class in a plug-in so that it can be conveniently installed |
| into Eclipse. This also shows how one can build an application that is meant |
| to run both as a standalone Java application and as an Eclipse extension. But |
| that could be the subject of another article.</p> |
| <p><img src="images/tryit.gif" width="61" height="13">The reader can install the |
| executable and source code by extracting the contents of the <a href="TableViewerExamplePlugin.zip">TableViewerExamplePlugin.zip</a> |
| file into the <i>plugins</i> directory of an Eclipse 2.1 installation and start |
| (or restart) Eclipse.</p> |
| <h2>Running the example</h2> |
| <p><img src="images/win_only.gif" width="49" height="13">To run the example, |
| simply open the sample view through the following menu item: Window -> Show |
| View -> Other... -> Examples -> TableViewerExample view. You may also |
| want to import the Java source code into an Eclipse project. To run the example |
| as a standalone Java application inside the Eclipse workbench, simply run the <code>TableViewerExample</code> |
| class.</p> |
| <h2>The Task List Editor</h2> |
| <p>A Task List editor window is shown below. The first column is used to |
| indicate whether the task is completed or not. The other columns and buttons are |
| self explanatory. The user can toggle the "completed" cell, edit in |
| place the "Description" and "% Complete" cells, change the |
| "Owner" cell through the use of a combo box, change the sort order by |
| clicking on a column header and add and delete tasks by clicking on the |
| corresponding button.</p> |
| <p><img src="images/tableViewer.gif" width="630" height="292"></p> |
| <h2>The ExampleTask and ExampleTaskList classes</h2> |
| <p>The example uses two simple classes that play the role of the model in our |
| MVC design. The <code>ExampleTask</code> is a simple business object |
| representing a task with getters and setters for the following <code></code>properties:</p> |
| <pre> <font color="#4444cc">private boolean completed = false; |
| private String description = ""; |
| private String owner = "?"; |
| private int percentComplete = 0;</font></pre> |
| <p>The <code>ExampleTaskList</code> is used to hold, as the name implies, a |
| collection of <code>ExampleTask</code> instances. It also knows about the list |
| of possible task owners.</p> |
| <h2>Building the TableViewerExample class</h2> |
| <p>Here we get into the substance of our subject matter: how do we go about |
| building and implementing the view and behavior described above? We do this with |
| a combination of SWT and JFace constructs. SWT provides an interface to the |
| native platform widgetry while JFace provides a high-level abstraction to build |
| rich GUIs. In other words, we could implement the example by using only SWT |
| objects but that would represent much more work and more complicated code.</p> |
| <p>So lets start by examining our <code>TableViewerExample</code> class. It |
| inherits from <code>Object</code> and the <code>main()</code> method simply |
| creates a new instance, calls the <code>open()</code> method on the window and |
| runs until the user tells the window to close itself.</p> |
| <pre> <font color="#4444cc">/** |
| * Main method to launch the window |
| */ |
| public static void main(String[] args) { |
| <img src="images/tag_1.gif" width="24" height="13"> Shell shell = new Shell(); |
| shell.setText("Task List - TableViewer Example"); |
| |
| // Set layout for shell |
| GridLayout layout = new GridLayout(); |
| shell.setLayout(layout); |
| |
| // Create a composite to hold the children |
| <img src="images/tag_2.gif" width="24" height="13"> Composite composite = new Composite(shell, SWT.NONE); |
| <img src="images/tag_3.gif" align="baseline" width="24" height="13"> TableViewerExample tableViewerExample = new TableViewerExample(composite); |
| |
| // Open the shell and run until a close event is detected |
| shell.open(); |
| tableViewerExample.run(shell); |
| }</font></pre> |
| <p>We first <font color="#4444cc"><img src="images/tag_1.gif" width="24" height="13"></font> |
| create and configure a SWT <code>Shell</code> object. Then, <font color="#4444cc"><img src="images/tag_2.gif" width="24" height="13"></font> |
| we create a <code>Composite</code> to hold the widgets (table and buttons) and <font color="#4444cc"><img src="images/tag_3.gif" align="baseline" width="24" height="13"></font> |
| instantiate our class passing the composite to the constructor. We could have |
| done things differently but passing the composite to the constructor makes it |
| easy to wrap our class in an Eclipse view for example.</p> |
| <p>Adding widgets to the composite is done in the <code>addChildControls()</code> |
| method. The more interesting part of this method is in the following code fragment:</p> |
| <pre> <font color="#4444cc">// Create the table |
| <img src="images/tag_1.gif" width="24" height="13"> createTable(composite); |
| |
| // Create and setup the TableViewer |
| <img src="images/tag_2.gif" width="24" height="13"> createTableViewer(); |
| <img src="images/tag_3.gif" align="baseline" width="24" height="13"> tableViewer.setContentProvider(new ExampleContentProvider()); |
| tableViewer.setLabelProvider(new ExampleLabelProvider()); |
| // The input for the table viewer is the instance of ExampleTaskList |
| tableViewer.setInput(taskList);</font></pre> |
| <p>We first <font color="#4444cc"><img src="images/tag_1.gif" width="24" height="13"></font> |
| create a <code>org.eclipse.swt.widgets.Table</code> and then <font color="#4444cc"><img src="images/tag_2.gif" width="24" height="13"></font> |
| create a <code>org.eclipse.jface.viewers.TableViewer</code> on that table. We |
| then <font color="#4444cc"><img src="images/tag_3.gif" align="baseline" width="24" height="13"></font> |
| set the content provider, label provider and input for the <code>TableViewer</code>. |
| We will come back to these later. Let's first look at the <code>createTable()</code> |
| method. It starts by instantiating a <code>Table</code> object and sets its |
| parent, style and layout attributes. It then creates each of the four columns in |
| turn. As an example, the second column is created with the following fragment:</p> |
| <pre> <font color="#4444cc">// 2nd column with task Description |
| <img src="images/tag_1.gif" width="24" height="13"> column = new TableColumn(table, SWT.LEFT, 1); |
| column.setText("Description"); |
| column.setWidth(400); |
| // Add listener to column so tasks are sorted by description when clicked |
| <img src="images/tag_2.gif" width="24" height="13"> column.addSelectionListener(new SelectionAdapter() { |
| |
| public void widgetSelected(SelectionEvent e) { |
| tableViewer.setSorter( |
| new ExampleTaskSorter(ExampleTaskSorter.DESCRIPTION)); |
| } |
| });</font></pre> |
| <p>Here, we <img src="images/tag_1.gif" align="CENTER" width="24" height="13"> |
| create a <code>TableColumn</code> object for the <code>Table</code>, set some of |
| its attributes and <img src="images/tag_2.gif" align="CENTER" width="24" height="13"> |
| add a selection listener that sets the sorter object for the <code>TableViewer</code> |
| object when the column header is clicked. We will look at column sorting later.</p> |
| <p>The rest of the method is concerned with setting up the other columns using |
| the same approach. The creation of the <code>TableViewer</code> is also of |
| particular interest to us. The method is outlined below (less important code was |
| omitted):</p> |
| <pre><font color="#4444cc"> /** |
| * Create the TableViewer |
| */ |
| private void createTableViewer() { |
| <img src="images/tag_1.gif" align="baseline" width="24" height="13"> tableViewer = new TableViewer(table); |
| tableViewer.setUseHashlookup(true); |
| <img src="images/tag_2.gif" align="baseline" width="24" height="13"> tableViewer.setColumnProperties(columnNames); |
| |
| // Create the cell editors |
| <img src="images/tag_3.gif" align="baseline" width="24" height="13"> CellEditor[] editors = new CellEditor[columnNames.length]; |
| |
| // Column 1 : Completed (Checkbox) |
| <img src="images/tag_4.gif" align="baseline" width="24" height="13"> editors[0] = new CheckboxCellEditor(table); |
| |
| // Column 2 : Description (Free text) |
| <img src="images/tag_4.gif" align="baseline" width="24" height="13"> TextCellEditor textEditor = new TextCellEditor(table); |
| ((Text) textEditor.getControl()).setTextLimit(60); |
| editors[1] = textEditor; |
| |
| // Column 3 : Owner (Combo Box) |
| <img src="images/tag_4.gif" align="baseline" width="24" height="13"> editors[2] = new ComboBoxCellEditor(table, taskList.getOwners(), |
| SWT.READ_ONLY); |
| |
| // Column 4 : Percent complete (Text with digits only) |
| textEditor = new TextCellEditor(table); |
| ((Text) textEditor.getControl()).addVerifyListener( |
| |
| <img src="images/tag_5.gif" align="baseline" width="24" height="13"> new VerifyListener() { |
| public void verifyText(VerifyEvent e) { |
| e.doit = "0123456789".indexOf(e.text) >= 0; |
| } |
| }); |
| editors[3] = textEditor; |
| |
| // Assign the cell editors to the viewer |
| <img src="images/tag_6.gif" align="baseline" width="24" height="13"> tableViewer.setCellEditors(editors); |
| // Set the cell modifier for the viewer |
| tableViewer.setCellModifier(new ExampleCellModifier(this)); |
| // Set the default sorter for the viewer |
| <img src="images/tag_7.gif" align="baseline" width="24" height="13"> tableViewer.setSorter( |
| new ExampleTaskSorter(ExampleTaskSorter.DESCRIPTION)); |
| }</font> |
| </pre> |
| <p><br> |
| We <img src="images/tag_1.gif" align="CENTER" width="24" height="13"> first |
| construct the <code>TableViewer</code> object, passing along our <code>Table</code> |
| instance since a <code>TableViewer</code> always operates on a table. We then |
| <font color="#4444cc"><img src="images/tag_2.gif" align="baseline" width="24" height="13"></font> |
| set the column properties that will be used in callbacks to recognize the column |
| on which we will want to operate. We <font color="#4444cc"><img src="images/tag_3.gif" align="baseline" width="24" height="13"></font> |
| initialize an array of <code>CellEditor</code> objects and we <font color="#4444cc"><img src="images/tag_4.gif" align="baseline" width="24" height="13"></font> |
| build editors for each column. For the checkbox editor, we do not need to do |
| anything special. For column 2, we set the text length limit of the free text |
| editor while for column 3, we specify the items used in the combo box editor. |
| We obtain these from our <code>ExampleTaskList</code> object. In the case of |
| column 4, we <font color="#4444cc"><img src="images/tag_5.gif" align="baseline" width="24" height="13"></font> |
| add a validator (a <code>VerifyListener</code>) to makes sure we accept only |
| digits.</p> |
| <p>We finish setting up our <code>TableViewer</code> by <font color="#4444cc"><img src="images/tag_6.gif" align="baseline" width="24" height="13"></font> |
| setting it's cellEditors and cellModifier properties and by <font color="#4444cc"><img src="images/tag_7.gif" align="baseline" width="24" height="13"></font> |
| specifying the default sorter. The <code>ExampleCellModifier</code> is described |
| in detail below.</p> |
| <h2>Specifying Content and Label Providers</h2> |
| <p>Coming back to the <code>TableViewerExample.open()</code> method, we now have |
| a <code>TableViewer</code> instance for which we can set the content and label |
| providers. The content provider is implemented as an inner class of the <code>TableViewerExample</code> |
| class. This allows us to implement both the <code>IStructuredContentProvider</code> |
| and the <code>ITaskListViewer</code> without "polluting", so to speak, |
| our model with JFace dependencies. This class does several things. It handles |
| the <code><font color="#4444cc"><img src="images/tag_1.gif" align="baseline" width="24" height="13"></font> |
| inputChanged() </code>and <codee><font color="#4444cc"><img src="images/tag_2.gif" align="baseline" width="24" height="13"></font> |
| dispose()</codee> calls by registering (or deregistering) itself as a change |
| listener for the model. It <font color="#4444cc"><img src="images/tag_3.gif" align="baseline" width="24" height="13"></font> |
| relays the <code>getElements()</code> request to our model (a <code>TaskList</code> |
| object) and packages the result into an array of <code>Object</code> instances. |
| Finally, it implements the <code><font color="#4444cc"><img src="images/tag_4.gif" align="baseline" width="24" height="13"></font> |
| addTask()</code>, <code><font color="#4444cc"><img src="images/tag_5.gif" align="baseline" width="24" height="13"></font> |
| removeTask()</code> and <code><font color="#4444cc"><img src="images/tag_6.gif" align="baseline" width="24" height="13"></font> |
| updateTasks()</code> callbacks specified by <code>ITaskListViewer</code>. The |
| general principle here is that one should not assume that the model |
| "feeds" a single view. Hence, views should register and deregister |
| their interest in model changes (in our case, task additions, removals and |
| changes).</p> |
| <pre><font color="#4444cc"> /** |
| * InnerClass that acts as a proxy for the ExampleTaskList |
| * providing content for the Table. It implements the ITaskListViewer |
| * interface since it must register changeListeners with the |
| * ExampleTaskList |
| */ |
| class ExampleContentProvider |
| implements IStructuredContentProvider, ITaskListViewer { |
| <img src="images/tag_1.gif" align="baseline" width="24" height="13"> public void inputChanged(Viewer v, Object oldInput, Object newInput) { |
| if (newInput != null) |
| ((ExampleTaskList) newInput).addChangeListener(this); |
| if (oldInput != null) |
| ((ExampleTaskList) oldInput).removeChangeListener(this); |
| } |
| |
| <img src="images/tag_2.gif" align="baseline" width="24" height="13"> public void dispose() { |
| taskList.removeChangeListener(this); |
| } |
| |
| // Return the tasks as an array of Objects |
| <img src="images/tag_3.gif" align="baseline" width="24" height="13"> public Object[] getElements(Object parent) { |
| return taskList.getTasks().toArray(); |
| } |
| |
| /* (non-Javadoc) |
| * @see ITaskListViewer#addTask(ExampleTask) |
| */ |
| <img src="images/tag_4.gif" align="baseline" width="24" height="13"> public void addTask(ExampleTask task) { |
| tableViewer.add(task); |
| } |
| |
| /* (non-Javadoc) |
| * @see ITaskListViewer#removeTask(ExampleTask) |
| */ |
| <img src="images/tag_5.gif" align="baseline" width="24" height="13"> public void removeTask(ExampleTask task) { |
| tableViewer.remove(task); |
| } |
| |
| /* (non-Javadoc) |
| * @see ITaskListViewer#updateTask(ExampleTask) |
| */ |
| <img src="images/tag_6.gif" align="baseline" width="24" height="13"> public void updateTask(ExampleTask task) { |
| tableViewer.update(task, null); |
| } |
| }</font></pre> |
| <p>For the label provider, we chose to use a top-level class (the <code>ExampleLabelProvider</code> |
| class). An <code>org.eclipse.jface.viewers.ITableLabelProvider</code> must |
| implement two methods: <code><font color="#4444cc"><img src="images/tag_1.gif" align="baseline" width="24" height="13"></font> |
| getColumnText()</code> and <font color="#4444cc"><img src="images/tag_2.gif" align="baseline" width="24" height="13"></font><code> |
| getColumnImage()</code>. As the names imply, these methods must return the label |
| text and image for a given column of a given element and are implemented using |
| the following code:</p> |
| <pre><font color="#4444cc"><code><font color="#4444cc"><img src="images/tag_1.gif" align="baseline" width="24" height="13"></font></code> public String getColumnText(Object element, int columnIndex) { |
| String result = ""; |
| ExampleTask task = (ExampleTask) element; |
| switch (columnIndex) { |
| case 0: // COMPLETED_COLUMN |
| break; |
| case 1 : |
| result = task.getDescription(); |
| break; |
| case 2 : |
| result = task.getOwner(); |
| break; |
| ...code omitted... |
| } |
| return result; |
| } |
| |
| <code><font color="#4444cc"><img src="images/tag_2.gif" align="baseline" width="24" height="13"></font></code> public Image getColumnImage(Object element, int columnIndex) { |
| return (columnIndex == 0) ? // COMPLETED_COLUMN? |
| getImage(((ExampleTask) element).isCompleted()) : |
| null; |
| }</font></pre> |
| <p>These methods are called by the <code>TableViewer</code> whenever a table element |
| needs to be refreshed. Our <code>ITableLabelProvider</code> extends <code>org.eclipse.jface.viewers.LabelProvider</code>. |
| This way, we benefit from the behavior defined in the superclass. Internally, |
| our class manages an image registry (for the checkboxes) so we had to put in |
| place the mechanics to load and manage instances of <code>org.eclipse.jface.resource.ImageDescriptor</code> |
| and <code>org.eclipse.jface.resource.ImageRegistry</code>. The details of this |
| can be assessed by looking at the source code for the class. In SWT, platform |
| resources such as images and fonts must be disposed of when the application |
| exits. However, an <code>ImageRegistry</code> owns all of the image objects |
| registered with it and automatically disposes of them when the SWT Display is |
| disposed so we do not need to explicitly do so.</p> |
| <h2>Supporting cell editors with an ICellModifier</h2> |
| <p>We now have in place all the parts to display our task list in a table. What |
| we need to add is the componentry to support cell editing. For this, we |
| implement an <code>org.eclipse.jface.viewers.ICellModifier</code>. The later |
| must define the <code>canModify()</code>, <code>getValue()</code> and <code>modify()</code> |
| methods. The first one returns a <code>boolean</code> given an element (a table |
| row) and a column name. The second method returns a cell value (given an element |
| and a column name). Part of its implementation is shown below:</p> |
| <pre><font color="#4444cc"> public Object getValue(Object element, String property) { |
| |
| // Find the index of the column |
| <code><font color="#4444cc"><img src="images/tag_1.gif" align="baseline" width="24" height="13"></font></code> int columnIndex = tableViewer.getColumnNames().indexOf(property); |
| |
| Object result = null; |
| <code><font color="#4444cc"><img src="images/tag_2.gif" align="baseline" width="24" height="13"></font></code> ExampleTask task = (ExampleTask) element; |
| |
| <code><font color="#4444cc"><img src="images/tag_3.gif" align="baseline" width="24" height="13"></font></code> switch (columnIndex) { |
| case 0 : // COMPLETED_COLUMN |
| result = new Boolean(task.isCompleted()); |
| break; |
| case 1 : // DESCRIPTION_COLUMN |
| result = task.getDescription(); |
| break; |
| <code><font color="#4444cc"><img src="images/tag_4.gif" align="baseline" width="24" height="13"></font></code> case 2 : // OWNER_COLUMN |
| String stringValue = task.getOwner(); |
| String[] choices = tableViewer.getChoices(property); |
| int i = choices.length - 1; |
| while (!stringValue.equals(choices[i]) && i > 0) |
| --i; |
| result = new Integer(i); |
| break; |
| ...code omitted... |
| } |
| return result; |
| }</font></pre> |
| <p><code></code>First, <code><font color="#4444cc"><img src="images/tag_1.gif" align="baseline" width="24" height="13"></font></code> |
| we obtain the column index for the property. <code></code>Second, <code><font color="#4444cc"><img src="images/tag_2.gif" align="baseline" width="24" height="13"></font></code> |
| we cast the contents of <code>element</code> into a <code>ExampleTask</code> |
| object so we can directly address its interface. Finally, <code><font color="#4444cc"><img src="images/tag_3.gif" align="baseline" width="24" height="13"></font></code> |
| we <code></code>switch to the proper block of code based on the column index. |
| Cases 0 and 1 are self explanatory. In <code><font color="#4444cc"><img src="images/tag_4.gif" align="baseline" width="24" height="13"></font></code> |
| case 2, we must return the index for the combo box. Hence, we match the <code>stringValue</code> |
| to one of the choices and return an <code>Integer</code> representing the |
| current selection.</p> |
| <p>The <code>modify()</code> method is somewhat symmetrical to the previous one. |
| It is where the actual modification of the <code>ExampleTask</code> takes place.</p> |
| <pre><font color="#4444cc"> public void modify(Object element, String property, Object value) { |
| |
| // Find the index of the column |
| ...code omitted... |
| |
| switch (columnIndex) { |
| <code><font color="#4444cc"><img src="images/tag_1.gif" align="baseline" width="24" height="13"></font></code> case 0 : // COMPLETED_COLUMN |
| task.setCompleted(((Boolean) value).booleanValue()); |
| break; |
| case 1 : // DESCRIPTION_COLUMN |
| task.setDescription(((String) value).trim()); |
| break; |
| ...code omitted... |
| } |
| <code><font color="#4444cc"><img src="images/tag_2.gif" align="baseline" width="24" height="13"></font></code> tableViewerExample.getTaskList().taskChanged(task); |
| }</font></pre> |
| <p>As an example, for the <code><font color="#4444cc"><img src="images/tag_1.gif" align="baseline" width="24" height="13"></font></code> |
| "completed" column, we <code></code>convert the passed value into a <code>boolean</code> |
| and use the resulting value as an argument to the <code>ExampleTask.setCompleted()</code> |
| method. We implement an equivalent behavior for each column. The <code><font color="#4444cc"><img src="images/tag_2.gif" align="baseline" width="24" height="13"></font></code> |
| final line asks the <code>TableViewerExample </code>for its model (an <code>ExampleTaskList</code>) |
| and notifies the model that a given task was changed. The model in turn notifies |
| this change to each of its registered views.</p> |
| <h2>Adding and Deleting Table Items</h2> |
| <p>Editing existing tasks is useful but we also want to add and remove tasks |
| using the GUI. The following code attaches the proper behavior to the |
| "Add" button (very similar code is used for the "Delete" |
| button):</p> |
| <pre><font color="#4444cc"> </font><font color="#4444cc">// Create and configure the "Add" button |
| <code><font color="#4444cc"><img src="images/tag_1.gif" align="baseline" width="24" height="13"></font></code> Button add = new Button(parent, SWT.PUSH | SWT.CENTER); |
| add.setText("Add"); |
| |
| GridData gridData = new GridData (GridData.HORIZONTAL_ALIGN_BEGINNING); |
| gridData.widthHint = 80; |
| add.setLayoutData(gridData); |
| <code><font color="#4444cc"><img src="images/tag_2.gif" align="baseline" width="24" height="13"></font></code> add.addSelectionListener(new SelectionAdapter() { |
| |
| // Add a task to the ExampleTaskList and refresh the view |
| public void widgetSelected(SelectionEvent e) { |
| taskList.addTask(); |
| } |
| });</font> |
| </pre> |
| <p>In the above code fragment, we <code><font color="#4444cc"><img src="images/tag_1.gif" align="baseline" width="24" height="13"></font></code> |
| layout the button and <code><font color="#4444cc"><img src="images/tag_2.gif" align="baseline" width="24" height="13"></font></code> |
| specify a selection listener that invokes the model's <code>addTask()</code> |
| method. This later method notifies all its active views that a task was added |
| using the following implementation:</p> |
| <pre><font color="#4444cc"> public void addTask() { |
| ExampleTask task = new ExampleTask("New task"); |
| tasks.add(tasks.size(), task); |
| Iterator iterator = changeListeners.iterator(); |
| while (iterator.hasNext()) |
| ((ITaskListViewer) iterator.next()).addTask(task); |
| }</font> |
| </pre> |
| <p>This pretty much closes the loop for cell and table editing. Through |
| relatively simple implementations of <code>ICellModifier</code>, <code>ITableLabelProvider</code> |
| and <code>IStructuredContentProvider</code>, we have a sophisticated and robust |
| GUI supporting in-place edition of table cells and a viewer that will always |
| reflect the current state of the model.<br> |
| </p> |
| <h2>Supporting column-wise sorting</h2> |
| <p>As hinted above, adding column-wise sorting is almost trivial. To achieve |
| this, we extend the <code>org.eclipse.jface.viewers.ViewerSorter</code> class |
| and override the <code>compare()</code> public method with one that knows how to |
| compare tasks based on its preset criteria. We leave it up to the reader to look |
| up the class implementation. Specifying a sorter for our table viewer is done |
| using the following pattern:</p> |
| <pre><font color="#4444cc"> tableViewer.setSorter( |
| new ExampleTaskSorter(ExampleTaskSorter.PERCENT_COMPLETE))</font></pre> |
| <p>We construct a sorter using one of its exposed criteria and use the result as |
| an argument to the <code>TableViewer.setSorter()</code> method. On selection of |
| a column header, we tell the <code>TableViewer</code> to use a different sorter |
| and table lines get resorted "automagically"!</p> |
| <h2>Packaging the table viewer as a stand-alone application</h2> |
| <p>This table viewer/editor is all very nice but how and where can I make use of |
| it in applications? One answer is, obviously, within an Eclipse plugin. But more |
| than that, as others have described, SWT and JFace can be used to build |
| applications that live outside the Eclipse framework. This is actually very |
| simple: One simply has to put the following files in the same folder: <b>TableViewerExample.jar</b> |
| (contains the application class and image files), <b>swt.jar</b>, <b>jface.jar</b>,<b> |
| runtime.jar</b> and <b>boot.jar</b>. In addition, in a Win32 environment, you |
| will need the <b>swt-win32-2133.dll</b> file which contains the "glue" |
| allowing Java objects to communicate with Windows native widgets and APIs. On |
| other platforms, the Java to native code will be found in a different file so |
| you would need to somehow make sure your Java VM can find it.</p> |
| <p><img src="images/win_only.gif" width="49" height="13"> On Windows, to launch |
| the TableViewerExample application, you need to execute the following command:</p> |
| <pre><font color="#4444cc">java -classpath TableViewerExample.jar;swt.jar;jface.jar;runtime.jar;boot.jar |
| -Djava.library.path=. com.opnworks.tableviewer.example.TableViewerExample</font></pre> |
| <p>You would typically but such a command inside a .bat file. You can also |
| achieve a clickable icon result on Windows and on other platforms by using a |
| Java aware packager/installer application, some of which are free to use even |
| for commercial purposes. With such a packaging, users will never know they are |
| using Java technology! Not that there is anything to be shy about, it is just |
| that most users really don't care and why should they?</p> |
| <h2>Wrap-up</h2> |
| <p>With the popularity of the web, it seems people and programmers have been |
| shying away from building GUI applications. In addition, Java programmers and |
| software development managers are often convinced that Java does not cut it on |
| the client and that it cannot be used to build "native" applications. |
| You can convince them of the contrary by rapidly prototyping and packaging a |
| native application using the SWT and JFace APIs.<br> |
| </p> |
| |
| </body> |
| |
| </html> |