站内搜索: 请输入搜索关键词
当前页面: 图书首页 > Eclipse Rich Client Platform: Designing, Coding, and Packaging Java Applications

Section 5.4.  Filling in the Contacts View - Eclipse Rich Client Platform: Designing, Coding, and Packaging Java Applications

Previous Page
Next Page

5.4. Filling in the Contacts View

Now that you have a model, you have something to put in the Contacts view. In the model, contacts are organized into groups of contact entries. A UI for this should be some sort of tree design that allows users to organize their contacts and groups. To help create that UI, this section shows you how to:

  • Add a tree viewer to the ContactsView class.

  • Initialize a Session object with test data.

  • Add a content provider to populate the tree based on the contents of the contacts list.

  • Add a label provider to display the labels and images in the ContactsView.

Note

For the rest of the UI discussion, it is useful to have some knowledge of SWT and JFace. We do not go into great detail about their usage in Hyperbola since we want to focus on RCP-specific concepts. See Section 5.7, "Pointers," for more pointers to information on SWT and JFace.


5.4.1. The ContactsView

When the Workbench creates the Contacts view, it calls createPart Control(Composite) on the view so it can create its controls. The code below does two things: it spoofs up a fake model and it adds a treeViewer to the view. Add the code to the ContactsView class and then let's take a look at it.

The fake model is created in initializeSession() out of convenience. Once a real chat model is integrated into Hyperbola in Chapter 10, "Messaging Support," this code will be removed. In any event, the model is built and is set as the input to the treeViewer. The treeViewer is also set up as a selection provider so that actions can determine the selection in this view.

org.eclipsercp.hyperbola/ContactsView
public class ContactsView extends ViewPart {
  public static final String ID =
      "org.eclipsercp.hyperbola.views.contacts";
  private TreeViewer treeViewer;
  private Session session;

  public ContactsView() {
    super();
  }

  public void createPartControl(Composite parent) {
    initializeSession(); // temporary tweak to build a fake model
    treeViewer = new TreeViewer(parent,
         SWT.BORDER | SWT.MULTI | SWT.V_SCROLL);
    getSite().setSelectionProvider(treeViewer);
    treeViewer.setLabelProvider(new WorkbenchLabelProvider());
    treeViewer.setContentProvider(new BaseWorkbenchContentProvider());
    treeViewer.setInput(session.getRoot());
    session.getRoot().addContactsListener(new IContactsListener() {
      public void contactsChanged(ContactsGroup contacts,
           ContactsEntry entry) {
         treeViewer.refresh();
      }
    });
  }

  private void initializeSession() {
    session = new Session();
    ContactsGroup root = session.getRoot();
    ContactsGroup friendsGroup = new ContactsGroup(root, "Friends");
    root.addEntry(friendsGroup);
    friendsGroup.addEntry(new ContactsEntry(friendsGroup,
        "Alize", "aliz", "localhost"));
    friendsGroup.addEntry(new ContactsEntry(friendsGroup,
        "Sydney", "syd", "localhost"));
    ContactsGroup otherGroup = new ContactsGroup(root, "Other");
    root.addEntry(otherGroup);
    otherGroup.addEntry(new ContactsEntry(otherGroup,
        "Nadine", "nad", "localhost"));
  }

  public void setFocus() {
    treeViewer.getControl().setFocus();
  }
}

TreeViewers do just what their name impliesdisplay tree structures. This is done using two providers: the content provider and the label provider. Content providers supply the tree nodes (e.g., parents and children) and the label provider produces human-readable names and representative images for the nodes. treeViewer.setInput(Object) tells a treeViewer to build the tree using the supplied object. It is up to the configured content providers to interpret the object and make it look like a tree.

Note

Do not confuse views with JFace viewers. It's unfortunate that their names are so similar, but they are two separate concepts. A viewer is a model-based adapter for an SWT widget. A viewer can be used to show content within a view. Moreover, a view can contain multiple viewers, and even editors can contain viewers.


5.4.2. Content Providers Overview

TreeViewers require content providers that implement ITreeContentProvider, as shown below. This allows the viewer to query the structure of its input object using methods such as getChildren(Object) and getParent(Object).

org.eclipse.jface/ITreeContentProvider
public interface ITreeContentProvider extends IStructuredContentProvider {
  public Object[] getChildren(Object parentElement);
  public Object getParent(Object element);
  public boolean hasChildren(Object element);
}

The arguments to these methods are the elements being shown in the viewer. As shown in Figure 5-9, treeViewer.setInput(Object) is called when ContactsView is created. This in turn uses the configured ITreeContentProvider. getChildren(Object) method to find the first level of elements to display. Notice that this means the root input object for the TReeViewer is never displayedit provides the starting point from which the visible tree is built. The collaboration between the viewer and the content provider continues as more elements in the tree are expanded.

Figure 5-9. Content providers


Figure 5-9 shows how viewers and providers work together, but not how providers and input model objects interact. For example, how does the provider discover the children of a model object? There are several possible techniques:

  • Make the model objects implement ITreeContentProvider.

  • Wrap the model objects with another object that an existing ITreeContentProvider understands.

  • Supply a customized content provider.

  • Extend the objects with the required provider function.

Adding the provider methods, for example, getChildren(Object) and getParent(Object), to ContactsEntry and ContactsGroup is simple and straightforward. The methods simply expose the inherent structure of the underlying model objects. This approach has the drawback that it pollutes the chat model with UI concerns. It is better to keep the model and UI layers decoupled from one another.

Wrapping chat objects in provider-friendly objects gets around this limitation but introduces the overhead of having two objects for every model object. It also makes identity maintenance, for example, equality checks, difficult, as there can be more than one objectthe model and the wrapperrepresenting the same entity.

TreeViewers are customizable and allow you to supply your own content providers. We could simply implement providers that directly access model objects and navigate their object structure. This approach requires control over the providers and is not extensible as all object types handled must be known in advance.

The final approach is to use the Eclipse adapter mechanism to extend the behavior of the model objects. The Workbench defines a standard content provider called BaseWorkbenchContentProvider that knows how to navigate the IWorkbenchAdapter type shown below.

org.eclipse.ui.workbench/IWorkbenchAdapter
public interface IWorkbenchAdapter {
  public Object[] getChildren(Object o);
  public ImageDescriptor getImageDescriptor(Object object);
  public String getLabel(Object o);
  public Object getParent(Object o);
}

If an object can adapt to IWorkbenchAdapter, then it can be shown in a tree viewer using the standard content provider. In fact, objects that adapt to IWorkbenchAdapter can be shown in a wide range of viewers. All you have to do to enable this is have the model object implement IAdaptable and supply an adaptor factory that produces IWorkbenchAdapters for the model objects.

5.4.2.1. Adding the IWorkbenchAdapters

Let's look at this in more detail. There are four things you have to do to use the adapter technique:

  1. Configure the treeViewer to use an instance of BaseWorkbenchContentProvider as its content provider.

  2. Implement IWorkbenchAdapters for the chat model elements that need to be displayed.

  3. Implement an adapter factory that returns an IWorkbenchAdapter for each model element.

  4. Register the Hyperbola adapter factory with Eclipse.

The first point was covered in Section 5.4.1, "The ContactsView," when the TReeViewer was created. The HyperbolaAdapterFactory class below covers the next two requirements. It contains inner class implementations of IWorkbenchAdapter for ContactsGroup and ContactsEntry. The adapters do not maintain any state, so the same instances are used for all relevant model objects. When getAdapter(Object, Class) is called, the factory picks an adapter to return.

org.eclipsercp.hyperbola/HyperbolaAdapterFactory
public class AdapterFactory implements IAdapterFactory {

  private IWorkbenchAdapter groupAdapter = new IWorkbenchAdapter() {
    public Object getParent(Object o) {
      return ((ContactsGroup)o).getParent();
    }
    public String getLabel(Object o) {
      // to be filled in soon!
      return ((ContactsGroup)o).getName();
    }
    public ImageDescriptor getImageDescriptor(Object object) {
      // to be filled in soon!
      return null;
    }
    public Object[] getChildren(Object o) {
      return ((ContactsGroup)o).getEntries();
    }
  };

  private IWorkbenchAdapter entryAdapter = new IWorkbenchAdapter() {
    public Object getParent(Object o) {
      return ((ContactsEntry)o).getParent();
    }
    public String getLabel(Object o) {
      ContactsEntry entry = ((ContactsEntry)o);
      return entry.getName() + '-' + entry.getServer();
    }
    public ImageDescriptor getImageDescriptor(Object object) {
      // to be filled in soon!
      return null;
    }

    public Object[] getChildren(Object o) {
      return new Object[0];
    }
  };

  public Object getAdapter(Object adaptableObject, Class adapterType) {
    if(adapterType == IWorkbenchAdapter.class &&
         adaptableObject instanceof ContactsGroup)
      return groupAdapter;
    if(adapterType == IWorkbenchAdapter.class &&
         adaptableObject instanceof ContactsEntry)
      return entryAdapter;
    return null;
    }
    public Class[] getAdapterList() {
      return new Class[] {IWorkbenchAdapter.class};
    }
}

The last step is to register the adapter factory with Eclipse when the ContactsView is created and unregister it when the view is closed, as shown below:

org.eclipsercp.hyperbola/ContactsView
private IAdapterFactory adapterFactory = new HyperbolaAdapterFactory();

public void createPartControl(Composite parent) {
  treeViewer = new TreeViewer(parent, SWT.BORDER | SWT.MULTI
  Platform.getAdapterManager().
       registerAdapters(adapterFactory, Contact.class);
  ...
}
public void dispose() {
  Platform.getAdapterManager().unregisterAdapters(adapterFactory);
  super.dispose();
}

5.4.3. The Label Provider

The content provider gives you the tree structure to display, but not the labels and icons needed to paint elements on the screen. This is the role of the label provider. When the ContactsView was created in Section 5.4.1, a default WorkbenchLabelProvider was configured as the TreeViewer's label provider. Like BaseWorkbenchContentProvider, it used IWorkbenchAdapters to determine the label and image to show in the tree.

Minimally, you should update the adapter created in the adapter factory to return the name of the group or contact it adapts. To make things a little more interesting, the code below defines the label for group names to include the number of logged-in contacts in the group. Update the entry adapter as well, perhaps to show contacts with a nickname followed by their real name and server. Decorating the entries with images is covered in the next section.

org.eclipsercp.hyperbola/HyperbolaAdapterFactory
private IWorkbenchAdapter groupAdapter = new IWorkbenchAdapter() {
...
public String getLabel(Object o) {
  ContactsGroup group = ((ContactsGroup) o);
  int available = 0;
  Contact[] entries = group.getEntries();
  for (int i = 0; i < entries.length; i++) {
    Contact contact = entries[i];
    if (contact instanceof ContactsEntry) {
      if (((ContactsEntry) contact).getPresence()
          != Presence.INVISIBLE)
         available++;
    }
  }
  return group.getName() +
      " (" + available + "/" + entries.length + ")";
}

Now Hyperbola is starting to look a bit more interesting, as shown in Figure 5-10.

Figure 5-10. Hyperbola showing a mock-up Contacts view



Previous Page
Next Page