Now that we have seen the basics of an Eclipse RCP application, let's expand the example and create an application to store contact information. To keep it simple and focus on learning how to use the Eclipse RCP features, we will develop a standalone application instead of a client application that connects to a server. The application, however, could be easily modified to store and fetch contact information from a server. The advantage of such a design over a web-based client lies in the possibility of implementing mechanisms that allow your application to work in situations where Internet connection is faulty or simply nonexistent.
The idea here is to develop a very simple application, but one that has functionality and explores more features than the one in the previous example. It will have a view on the left side of the application window, containing a list of the contact names. Whenever a contact name is selected in this view, a contact editor is opened in the middle of the window, allowing the user to view the contact information and update it. The application menu will contain entries to create a new contact, save the currently edited one, as well as a toolbar with a save button. We will also use this example to introduce the concept of content and label providers.
We will create a number of classes in this example. The following screenshot contains all of them in their respective packages so that you can refer to it whenever you need a big picture of the application:
Ok, so let's get our hands on it. As you have seen in the previous example, Eclipse templates are a good way of getting started; so let's use it in this example too. Navigate to File | New | Plug-in Project and create a new RCP project named ContactsList
using the HelloRCP
template. Change the application window title to something more appropriate, such as Contacts List
.
The project has a similar structure to the previous example, but with fewer classes. Let's run the application to make sure everything is okay. Open the Plug-in Manifest Editor, switch to the Overview tab, and click on the Launch an Eclipse Application link in the column to the right. You will see an empty window named Contacts List
, which is exactly what we expected by now. Let's close it and proceed to developing the application.
We will start by coding the view that contains a list of the contacts. To create this list, we will use the SWT's TableViewer
element. Even though this is a very basic view, we will use label and content providers for the sake of demonstration. A label provider is a special class that is responsible for mapping models to their corresponding entries in a viewer. In our TableViewer
case, it will provide a label that represents contact. We will use the contact's name for that. The content provider feeds the viewer with the elements it should contain. We will use a simple SWT implementation named ArrayContentProvider
, which supplies elements via an array.
Let's begin with the contact model. Create a class named Contact
that contains three strings along with their getters and setters: name
, address
, and phoneNumber
. Also create a constructor with all the strings.
The next class we will work on is the label provider. Create a class named ContactLabelProvider
and make it implement the ILabelProvider
interface. Use the Eclipse code generation features presented in Chapter 2, Java Development, to create default implementations for the interface's methods. The only method we will modify is getText
. This is where we will determine that the contact will be represented by its name in the contact list. We need to get the element
parameter, check if it is an instance of Contact
, and return the contact's name. The following code does that:
@Override public String getText(Object element) { if(element instanceof Contact){ return ((Contact)element).getName(); } return null; }
Now that we have everything we need, let's start working on the ContactList
view itself. Create a class named ContactListView
with the following code:
public class ContactListView extends ViewPart { private TableViewer contactsViewer; private List<Contact> contacts; @Override public void createPartControl(Composite parent) { contactsViewer = new TableViewer(parent); contactsViewer.setContentProvider(new ArrayContentProvider()); contactsViewer.setLabelProvider(new ContactLabelProvider()); contacts = new ArrayList<Contact>(); contactsViewer.setInput(contacts); } @Override public void setFocus() { } public void addEntry(Contact contact){ contacts.add(contact); contactsViewer.refresh(true); } public void refreshEntries(){ contactsViewer.refresh(true); } }
The imports section was omitted. As we have seen in the previous chapter, an easy way of creating a view is by extending the ViewPart
class. This requires us to implement the createPartControl
and setFocus
methods. In the createPartControl
method, we are supposed to instantiate all of the SWT elements contained in the view. The setFocus
method is executed when focus is assigned to the view. This method typically assigns focus to one of the SWT elements in the view. We will ignore setFocus
for now and use an empty implementation. In the createPartControl
method, we initialize the TableViewer
interface and associate it with the ContactLabelProvider
class we just created. We also create a new ArrayContentProvider
method and supply it with an empty ArrayList
instance. This list will be modified as we create new contact entries. We have also created two methods (addEntry
and refreshEntries
) to simplify and encapsulate the task of adding new contacts to the list.
Now that we have the ContactList
class, let's modify the plugin.xml
file to register it in the workbench. We will use the Plug-in Manifest Editor window for that, just like we did in the previous chapter. Double-click on the plugin.xml
file to open it, switch to the Extensions tab, and add a new org.eclipse.ui.views
entry. Add a new view element to the extension. The following screenshot contains the properties of this element:
We must also add our view to the default perspective of our application. Let's use perspectiveExtension
for that, just like we did in the previous example. Create an extension to org.eclipse.ui.perspectiveExtensions
, add a perspectiveExtension
with *
as targetID
, and a view element under it with the properties as shown in the following screenshot:
The relative field determines which part will be the reference for the positioning of our view window. In this case, we're using the editor space (org.eclipse.ui.editors
), which represents the currently active editor in the workbench. Clicking on the Browse button allows searching for parts IDs. The relationship field determines where it will be placed relative to the part selected in the relative field.
In order to create new contact entries and edit the existing ones, we will create a contact editor. Before we work on the ContactEditor
class itself, let's create an editor input for it. An editor input represents the entry that is being editing. An editor input for a file, for example, could contain the path for the file being edited. Because our application's contents will be stored in-memory only, the editor input must contain the Contact
object itself. Create a class named ContactEditorInput
that implements the IEditorInput
interface, with the following code:
public class ContactEditorInput implements IEditorInput { private Contact contact; public ContactEditorInput(Contact contact) { super(); this.contact = contact; } public Contact getContact(){ return contact; } @Override public String getToolTipText() { return contact.getName(); } @Override public String getName() { return contact.getName(); } @Override public boolean equals(Object obj) { if(obj instanceof ContactEditorInput){ if(((ContactEditorInput)obj).getContact().equals(this.contact)){ return true; } } return false; } //Auto-generated method stub }
We have created a constructor that receives a Contact
object and also implemented a getter for it. We have also implemented the getToolTipText
and getName
methods to return the contact name. The equals
method must also be implemented in order to make sure that only one editor is open for one contact. We have used the auto-generated method stub
for the other methods. These methods were omitted in the code.
Now let's work on the contact editor. The following code is the implementation of the ContactEditor
class:
public class ContactEditor extends EditorPart { private boolean dirty = false; private Text addressText; private Text nameText; private Text phoneText; @Override public void init(IEditorSite site, IEditorInput input) throws PartInitException { setSite(site); setInput(input); } @Override public void createPartControl(Composite parent) { parent.setLayout(new GridLayout(2, false)); Label nameLabel = new Label(parent, SWT.NONE); nameLabel.setText("Name"); nameLabel.setLayoutData(new GridData()); nameText = new Text(parent, SWT.BORDER | SWT.FILL); nameText.setLayoutData(new GridData()); Label addressLabel = new Label(parent, SWT.NONE); addressLabel.setText("Address"); addressLabel.setLayoutData(new GridData()); addressText = new Text(parent, SWT.BORDER); addressText.setLayoutData(new GridData()); Label phoneLabel = new Label(parent, SWT.NONE); phoneLabel.setText("Phone number"); phoneLabel.setLayoutData(new GridData()); phoneText = new Text(parent, SWT.BORDER ); phoneText.setLayoutData(new GridData()); ContactEditorInput editorInput = (ContactEditorInput)getEditorInput(); if(editorInput != null){ Contact contact = editorInput.getContact(); nameText.setText(contact.getName()); this.setPartName(contact.getName()); addressText.setText(contact.getAddress()); phoneText.setText(contact.getPhoneNumber()); } nameText.addModifyListener(new ContactEditorModifyListener()); addressText.addModifyListener(new ContactEditorModifyListener()); phoneText.addModifyListener(new ContactEditorModifyListener()); } @Override public void doSave(IProgressMonitor monitor) { dirty = false; firePropertyChange(IWorkbenchPartConstants.PROP_DIRTY); ContactEditorInput editorInput = (ContactEditorInput)getEditorInput(); if(editorInput != null){ Contact contact = editorInput.getContact(); contact.setName(nameText.getText()); contact.setAddress(addressText.getText()); contact.setPhoneNumber(phoneText.getText()); this.setPartName(contact.getName()); } } @Override public boolean isDirty() { return dirty; } @Override public void doSaveAs() { } @Override public boolean isSaveAsAllowed() { return false; } @Override public void setFocus() { } private class ContactEditorModifyListener implements ModifyListener { @Override public void modifyText(ModifyEvent e) { dirty = true; firePropertyChange(IWorkbenchPartConstants.PROP_DIRTY); } } }
Notice that ContactEditor
extends EditorPart
instead of ViewPart
, just like ContactListView
did. It also contains a createPartControl
method that must be implemented. Our implementation creates SWT elements that compose a basic form for the contact's fields. It also fills these fields if the current editor input is not null, which will happen when we edit a contact instead of creating a new one. There's also an inline class named ContactEditorModifyListener
, which, as the name implies, gets notified whenever a text element is modified. We use this class to change the dirty
status to true
. The dirty
status means that the editor's input contains unsaved changes. This state is represented by an asterisk character (*
) in front of the editor's name. This state will also allow us to enable or disable the save button accordingly. Notice how we change the dirty
status in the doSave
method.
Now, let's register the ContactEditor
class in the plugin.xml
file. Add a new extension for the org.eclipse.ui.editors
extension point and add an editor element with the properties shown in the following screenshot:
Editors are not added to perspectives as regular views are. Editors are opened programmatically by calling the openEditor
method of the currently active WorkbenchPage
, as we will see later in this chapter. They can be opened in any perspective, and they are placed in the editor space, which lies in the middle of the window.
The next step is to implement commands, handlers, and menu entries that enable users to create new contacts and to save their work. Let's start with the handler classes. This is the code for the save
command handler:
public class SaveContactHandler extends AbstractHandler { @Override public Object execute(ExecutionEvent event) throws ExecutionException { PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActiveEditor().doSave(new NullProgressMonitor()); return null; } }
Note that there are two AbstractHandler
classes, one in the org.eclipse.core.commands
package and the other in org.eclipse.ui.commands
. We'll be using the former because the latter is deprecated.
This is the new contact handler:
public class NewContactHandler extends AbstractHandler { @Override public Object execute(ExecutionEvent event) throws ExecutionException { Contact contact = new Contact("New Contact", "", ""); IViewPart view = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().findView("ContactsList.ContactsListView"); ((ContactListView) view).addEntry(contact); return null; } }
The new contact handler simply creates a new Contact
object and calls the addEntry
method in the ContactsListView
view.
Let's proceed with the necessary modifications in the plugin.xml
file, which is similar to what was demonstrated in the previous chapter. Add an extension to the org.eclipse.ui.commands
extension point and add command elements for the Save
and New
commands. In the example, we use contactsList.save
and contactsList.new
as IDs and Save
and New
as names. Now add the org.eclipse.ui.handlers
extension, and create two handler elements for the commands, linking them to the handler classes we just created.
The next step is to add the menu entries for these commands. Let's add a File entry in the main menu with the New
and Save
entries and a Save button in the toolbar. Add the elements shown in the following screenshot to create them:
The New
and Save
commands point to the contactsList.new
and contactsList.save
command IDs respectively.
If you run the application at this point, you will realize that the Save button in the toolbar is not there. In fact, there's no toolbar in our application! That's because the implementation of the ApplicationWorkbenchWindowAdvisor
extension provided by the template disables the toolbar. Open this class, and either comment out the configurer.setShowCoolBar(false)
call inside the preWindowOpen
method or change the parameter to true
to display the toolbar.
Now that we have the two views and the menu entries, the next step is to connect the two views by opening an editor when the user double-clicks on an entry in the list. We will do that by adding a double-click listener to the ContactList
view. Let's start by coding the listener. Create a class named ContactListDoubleClickListener
with the following code:
public class ContactListDoubleClickListener implements IDoubleClickListener { @Override public void doubleClick(DoubleClickEvent event) { TableViewer viewer = (TableViewer) event.getSource(); StructuredSelection selection = (StructuredSelection) viewer.getSelection(); Contact contact = (Contact) selection.getFirstElement(); try { PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().openEditor(new ContactEditorInput(contact), "ContactsList.ContactEditor"); } catch (PartInitException e) { e.printStackTrace(); } } }
When a IDoubleClickListener
implementation is registered to a SWT widget, its doubleClick
method is executed when the widget is double-clicked. Our doubleClick
implementation gets the TableViewer
widget and discovers which element was selected. This is another advantage of using content providers; they allow you to access the object to which the TableViewer
entry refers easily.
Now that we have a double-click listener, add it to the contact's TableViewer
widget by modifying the createControl
method of ContactListView
by adding the following line:
contactsViewer.addDoubleClickListener(new ContactListDoubleClickListener());
To further integrate the contact's view window and the editor, we must forcibly refresh the contact's entries every time the editor is saved, in order to reflect a possible change in the contact's name. To do this, the ContactsList
view will listen to a specific event that the editor will fire whenever it is saved. Let's start by adding a private IViewPart
field named contactListView
to the ContactEditor
class that will contain the instance of the contacts view. Add the following line to the createPartControl()
method of ContactEditor
to get the instance:
contactListView = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().findView("ContactsList.ContactsListView");
Now add the following code to the end of the doSave()
method to notify the contact list's instance that the editor was saved:
if(contactListView != null){ if (contactListView instanceof IPropertyChangeListener){ PropertyChangeEvent changeDirty = new PropertyChangeEvent(this, "isDirty", true, false); ((IPropertyChangeListener)contactListView).propertyChange(changeDirty); } }
Now all we have to do is change the ContactListView
class to implement the IPropertyChangeListener
interface and add a propertyChange()
method that acts accordingly when the editor is saved. Implement the propertyChange
method with the following code:
@Override public void propertyChange(PropertyChangeEvent event) { refreshEntries(); }
We now have everything we proposed for this simple application. Let's see what it's looking like by running it. You should get a window like the following screenshot:
Navigate to File | New in the application's main menu to create a new contact. The editor will open. The following screenshot shows the editor: