Expanding the example – creating a contact list application

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:

Expanding the example – creating a contact list 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.

The contact list view

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:

The contact list view

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 contact list view

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.

The contact editor

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:

The contact editor

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 Save and New commands and menu entries

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 Save and New commands and menu entries

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.

Tying up the two views

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();
  }

Running the application

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:

Running the application

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:

Running the application
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset