| <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN"> |
| <html> |
| <head> |
| <title>How to use the JFace Tree Viewer</title> |
| <link rel="stylesheet" href="default_style.css"> |
| </head> |
| <body LINK="#0000ff" VLINK="#800080"> |
| <div align="right"> <font face="Times New Roman, Times, serif" size="2">Copyright |
| © 2002 Chris Grindstaff.</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" height=86 width=120></h1> |
| </div> |
| <p> </p> |
| <h1 align="CENTER">How to use the JFace Tree Viewer</h1> |
| <blockquote> |
| <b>Summary</b> |
| <br> |
| The goal of this article is to teach you how to use TreeViewers in your Eclipse |
| plug-ins or stand-alone JFace/SWT applications. We’ll start with a simple |
| example and progressively add functionality. |
| <p><b> By Chris Grindstaff, Applied Reasoning</b> (<font face="arial,helvetica,geneva" size=-1>chrisg |
| at appliedReasoning.com)</font><br> |
| <font size="-1">May 5, 2002</font> </p> |
| </blockquote> |
| |
| <hr width="100%"> |
| <h2>Introduction</h2> |
| <p> |
| The TreeViewer is used throughout Eclipse. In fact, |
| trees are pervasive in most applications. |
| This article will explain the following information about tree viewers: |
| </p> |
| <ul> |
| <li>how the tree viewer fits into the JFace UI toolkit</li> |
| <li>how to populate a tree viewer</li> |
| <li>how to listen for events generated by the tree viewer</li> |
| <li>how to select items in the tree viewer</li> |
| <li>how to filter out items</li> |
| <li>how to control the order of items in the tree viewer.</li> |
| </ul> |
| <p> |
| The tree viewer can be used in Eclipse plug-ins or in a stand alone |
| JFace/SWT application. We’ve chosen to implement a plug-in that |
| demonstrates the tree viewer functionality. |
| </p> |
| <p> |
| <a name="mainView"></a> |
| Here’s an example of what the plugin provides: |
| </p> |
| <p> |
| <p align="center"><img alt="main.gif (4K)" src="images/main.gif" height="350" width="230" align="middle"></p> |
| <p></p> |
| <p> |
| As you work through the article you may notice the |
| occasional <img alt="tryit.gif (1K)" src="images/tryit.gif" height="13" width="61">. These |
| icons indicate points where you should start up Eclipse and try the example. |
| </p> |
| <h2> |
| Source Code |
| </h2> |
| <p> |
| To run the examples or view the source for this article, |
| unzip <a href="cbg.article.treeviewer.zip">cbg.article.treeviewer.zip</a> |
| into your <i>eclipse_root</i> directory and restart Eclipse. In Windows |
| the <i>eclipse_root</i> directory will look something like <i>d:\apps\eclipse\</i>. |
| Once Eclipse is restarted, select the Perspective | Show View | |
| Other… menu option. In the “Other” |
| category select “Moving Box.” |
| </p> |
| <p> |
| <p align="center"><img alt="perspectiveMenu.jpg (21K)" src="images/perspectiveMenu.jpg" height="272" width="383"></p> |
| <p></p> |
| <p> |
| <img alt="tryit.gif (1K)" src="images/tryit.gif" height="13" width="61"> |
| </p> |
| <p> |
| This article will be most effective if you are able to run the plugin we’ve |
| provided as you read it. |
| </p> |
| |
| <h2> |
| Big Picture |
| </h2> |
| <p> |
| To understand how to use the TreeViewer, it is important |
| to understand where the TreeViewer fits into JFace as a |
| whole, and how JFace fits into Eclipse. |
| </p> |
| <p> |
| JFace is a UI toolkit that helps solve |
| common UI programming tasks. JFace also acts as a bridge |
| between low-level SWT widgets and your domain objects. |
| SWT widgets interact with the host operating system and |
| as such have no knowledge of your domain objects. |
| </p> |
| <p> |
| One of the ways that JFace bridges the gap between SWT |
| widgets and domain models is through viewers. JFace |
| viewers consist of an SWT widget (e.g. Tree, Table, etc), plus |
| your domain objects. You provide the viewers with the |
| information they need in order to populate the underlying |
| SWT widget. The viewer is able to sort and filter your domain objects, |
| as well as update the widget when your domain objects change. |
| </p> |
| <p> |
| More information about JFace and SWT can be found in the |
| Javadoc for org.eclipse.jface.viewers, |
| org.eclipse.swt.widgets, org.eclipse.swt.custom and |
| <a href="http://www.eclipse.org/documentation/html/plugins/org.eclipse.platform.doc.isv/doc/guide/jface_viewers.htm">JFace viewers</a>. |
| </p> |
| <h2> |
| Setting the Stage |
| </h2> |
| <p> |
| Because the TreeViewer uses domain objects to populate its Tree, |
| we will create a simple model to be used throughout |
| this article. |
| </p> |
| <p> |
| Suppose you've bought a new house and it's time to pack up your |
| stuff for the move. To keep all of your books and board |
| games organized you decide to employ a TreeViewer to help |
| you maintain order. (Yes, you're a geek.) |
| </p> |
| <p> |
| Here are our domain objects: |
| </p> |
| <ul type="disc"> |
| <li> |
| MovingBox |
| </li> |
| </ul> |
| <p> |
| A moving box can contain other moving boxes, books, and |
| board games. A moving box may or may not have a name. |
| </p> |
| <ul type="disc"> |
| <li>Book</li> |
| <li>Board game</li> |
| </ul> |
| <p> |
| Each book and board game has both a title and an author. |
| </p> |
| <h2> |
| Build a Simple TreeViewer |
| </h2> |
| <p> |
| We’ll start with a simple TreeViewer example and |
| build on it throughout the article. |
| </p> |
| <p> |
| Here’s the code that creates this <a href="#mainView">view</a> depicted earlier. |
| </p> |
| <blockquote> |
| <a name="sampleCode"></a><pre>treeViewer = new TreeViewer(parent); |
| treeViewer.setContentProvider(new MovingBoxContentProvider()); |
| treeViewer.setLabelProvider(new MovingBoxLabelProvider()); |
| treeViewer.setInput(getInitalInput()); |
| treeViewer.expandAll(); |
| </pre></blockquote> |
| <h2> |
| TreeViewer Domain Model Interactions |
| </h2> |
| <p> |
| It is crucial to understand the model/view relationship as utilized by |
| JFace viewers. Conceptually, all viewers perform two primary tasks: |
| </p> |
| <ul> |
| <li>they help adapt your domain objects into viewable entities</li> |
| <li>they provide notifications when the viewable entities are selected or |
| changed through the UI</li> |
| </ul> |
| <p> |
| More specifically, when working with a tree viewer, you |
| use your domain objects as arguments in the API methods. |
| For example, you can |
| add a book to a moving box by calling the |
| TreeViewer.add(aMovingBox, aBook) method. You don’t |
| need to translate your domain objects into UI elements; |
| the tree viewer does that for you. You make your "root" domain object |
| avaliable to the tree viewer by invoking the TreeViewer’s <b>setInput</b> |
| method. Your domain object then becomes the tree viewer’s input. |
| </p> |
| <p> |
| Likewise, when you ask the tree viewer for the selected |
| objects, it will answer with the domain objects - not the underlying |
| UI resources. |
| </p> |
| <h2> |
| Content Provider |
| </h2> |
| <p> |
| When working with a tree viewer, you need to provide the |
| viewer with information on how to <i>transform</i> your |
| domain object into an item in the UI tree. That is the |
| purpose of an <code>ITreeContentProvider</code>. One of your domain |
| classes could implement this interface. Instead of "polluting" your |
| domain objects with user interface code, you may want to create |
| another object to fulfill the tree content provider requirements.<br><br> |
| Let’s take a look at some of this interface’s |
| methods. |
| </p> |
| <p> |
| <ul type="disc"> |
| <li> |
| <code>public Object[] getElements(Object inputElement)</code> |
| </li> |
| </ul> |
| This is the method invoked by calling the <b>setInput</b> method on |
| the tree viewer. In fact, the <b>getElements</b> method is called only in |
| response to the tree viewer's <b>setInput</b> method and should answer with |
| the appropriate domain objects of the inputElement. |
| |
| The <b>getElements</b> and <b>getChildren</b> methods operate in a similar way. |
| Depending on your domain objects, you may have the <b>getElements</b> simply |
| return the result of calling <b>getChildren</b>. The two methods are kept |
| distinct because it provides a clean way to differentiate between the root |
| domain object and all other domain objects. |
| <p></p> |
| <ul type="disc"> |
| <li> |
| <code>public Object[] getChildren(Object parent)</code> |
| </li> |
| </ul> |
| <p> |
| The tree viewer calls its content provider’s |
| getChildren method when it needs to create or display the |
| child elements of the domain object, <b>parent</b>. This method |
| should answer an array of domain objects that represent |
| the unfiltered children of <b>parent</b> (more on filtering later). |
| </p> |
| <ul type="disc"> |
| <li> |
| <code>public Object getParent(Object element)</code> |
| </li> |
| </ul> |
| <p> |
| The tree viewer calls its content provider’s |
| getParent method when it needs to reveal collapsed domain objects programmatically |
| and to set the expanded state of domain objects. This method should answer the parent of the |
| domain object element. |
| </p> |
| <p> |
| <ul type="disc"> |
| <li><code>public boolean hasChildren(Object element)</code></li> |
| </ul> |
| <p></p> |
| <p> |
| The tree viewer asks its content provider if the domain |
| object represented by <b>element</b> has any children. This |
| method is used by the tree viewer to determine whether or |
| not a plus or minus should appear on the tree widget. |
| </p> |
| <ul type="disc"> |
| <li> |
| <code>public void |
| inputChanged</code><code>(Viewer viewer, Object |
| oldInput, Object newInput)</code> |
| </li> |
| </ul> |
| <p> |
| This method really serves two slightly different purposes. |
| This method is invoked when you set the tree viewer's input. In other |
| words, anytime you change the tree viewer's input via the setInput |
| method, the inputChanged method will be called. This will often be |
| used to register the content provider as a recipient of domain changes |
| and to de-register itself from the old domain object.<br><br> |
| In big picture terms, input objects for the content provider are managed |
| through the viewer.<br><br> |
| |
| The method is also called when the tree viewer wants |
| to inform the content provider that it's no longer the tree viewer's |
| content provider. This happens when you set the tree viewer's content |
| provider. |
| </p> |
| <p> |
| For example, our moving box notifies its listeners |
| whenever a book, board game or box is added to or removed |
| from it. The content provider will register as a listener |
| for these domain model changes so it can update its UI |
| when the domain changes. Likewise, it will want to remove |
| itself as a listener from domain objects that are no |
| longer being viewed. |
| </p> |
| <p> |
| Enough discussion about the abstract APIs. Let’s |
| take a look at the actual code used in this example. In |
| the code provided <a href="#sampleCode">above</a> |
| the tree viewer’s content provider was set to an |
| instance of <code>MovingBoxContentProvider</code>. |
| Let’s take a look at that class in detail. |
| </p> |
| <p> |
| <blockquote> |
| <pre> |
| public Object[] getChildren(Object parentElement) { |
| if(parentElement instanceof MovingBox) { |
| MovingBox box = (MovingBox)parentElement; |
| return concat(box.getBoxes().toArray(), |
| box.getBooks().toArray(), box.getGames().toArray()); |
| } |
| return EMPTY_ARRAY; |
| }</pre></blockquote> |
| <p></p> |
| <p> |
| Only MovingBox domain objects can have children, so an |
| empty array is returned for any other object. A moving |
| box’s children are a concatenation of its moving |
| boxes, plus its books, plus its board games. |
| </p> |
| <p> |
| <blockquote> |
| <pre> |
| public Object[] getElements(Object inputElement) { |
| return getChildren(inputElement); |
| }</pre></blockquote> |
| <p></p> |
| <p> |
| The getElements method is used to obtain the root |
| elements for the tree viewer. In our case, |
| the tree viewer's root object is a moving box so we can simply call |
| the getChildren method since it handles MovingBoxes. If the root |
| domain object was <i>special</i> in some way, the getElement method |
| would likely not delegate to the getChildren method. |
| </p> |
| <p> |
| <blockquote> |
| <pre> |
| public Object getParent(Object element) { |
| if(element instanceof Model) { |
| return ((Model)element).getParent(); |
| } |
| return null; |
| }</pre></blockquote> |
| <p></p> |
| <p> |
| The getParent method is used to obtain the parent of the |
| given element. In our example, the Model class is the common superclass |
| of moving boxes, books and board games and defines a |
| child/parent relationship. |
| </p> |
| <p> |
| <blockquote> |
| <pre> |
| public boolean hasChildren(Object element) { |
| return getChildren(element).length > 0; |
| }</pre></blockquote> |
| <p></p> |
| <p> |
| The hasChildren method is invoked by the tree viewer when |
| it needs to know whether a given domain object has |
| children. |
| </p> |
| <a name="inputChanged"></a> |
| <p> |
| <blockquote> |
| <pre> |
| public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { |
| this.viewer = (TreeViewer)viewer; |
| if(oldInput != null) { |
| removeListenerFrom((MovingBox)oldInput); |
| } |
| if(newInput != null) { |
| addListenerTo((MovingBox)newInput); |
| } |
| }</pre></blockquote> |
| <p></p> |
| <p> |
| The <b>input changed</b> method caches the viewer argument for later |
| use when responding to events. Recall that the <b>inputChanged</b> |
| method will be called when the tree viewer's setInput method is |
| invoked. When the tree viewer's input is changed, we need to make |
| sure that we remove any listeners we have associated with the |
| old input so that we no longer receive updates from the stale input. |
| Likewise, we need to listen for changes in the new input. Typically |
| the content provider adds itself as a listener to domain object |
| changes. This way when the domain object changes the content provider |
| is notified and can in turn, notify the tree viewer. |
| |
| Here is the addListenerTo method. (The removeListenerFrom method is |
| almost exactly the same so we'll ignore it.) |
| </p> |
| <blockquote> |
| <pre> |
| /** Because the domain model does not have a richer |
| * listener model, recursively add this listener |
| * to each child box of the given box. */ |
| protected void addListenerTo(MovingBox box) { |
| box.addListener(this); |
| for (Iterator iterator = box.getBoxes().iterator(); iterator.hasNext();) { |
| MovingBox aBox = (MovingBox) iterator.next(); |
| addListenerTo(aBox); |
| } |
| } |
| </pre></blockquote> |
| <p> |
| This method simply finds all the moving boxes and adds the content viewer |
| as a listener; therefore, if any of the moving boxes change, the content |
| viewer will be notified. Later in the article we'll show how the content |
| viewer responds to these domain changes. |
| </p> |
| <h2> |
| Label Provider |
| </h2> |
| <p> |
| The label provider is responsible for providing an image |
| and text for each item contained in the tree viewer. As |
| with the content provider, the label provider accepts |
| domain objects as its arguments. It is important that |
| instances of label providers are not shared between tree viewers |
| because the label provider will be disposed when the |
| viewer is disposed. |
| </p> |
| <p> |
| The tree viewer makes use of the ILabelProvider interface |
| to provide these services. Let’s take a look |
| at the interface’s two methods. |
| </p> |
| <p> |
| <code>public Image getImage(Object element)</code> |
| </p> |
| <p> |
| This method answers an SWT <code>Image</code> to be used |
| when displaying the domain object, <b>element</b>. If the domain |
| object does not have a corresponding image, you can |
| answer null. Because images use OS resources you need to |
| be careful to dispose of them when you are no longer using them. |
| This is often accomplished by caching |
| the images in the label provider or the plug-in class and |
| disposing of them when the viewer is disposed. |
| </p> |
| |
| <p> Another important point to keep in mind is that the tree viewer will scale |
| your images if they are different sizes. The first image returned from this |
| method will be the "standard" tree viewer image size. All other images will |
| be scaled up or down to match the size of this first image. These plus and minus |
| images used in the tree viewer to show expanded and collapsed items are also |
| scaled to the "standard" size. <br> |
| <br> |
| A safe size to use for your images is 16x16. |
| </p> |
| <p> |
| <code>public String getText(Object element)</code> |
| </p> |
| <p> |
| The getText method answers a string that represents the |
| label for the domain object, element. If there is no |
| label for the element, answer null. |
| </p> |
| <p> |
| <code>public void dispose()</code> |
| </p> |
| <p> |
| The dispose method is called when the tree viewer that |
| contains the label provider is disposed. This method is often used |
| to dispose of the cached images managed by the |
| receiver. |
| </p> |
| <p> |
| In |
| the code listed <a href="#sampleCode">above</a>, |
| the tree viewer’s label provider was set to an |
| instance of <code>MovingBoxLabelProvider</code>. |
| Let’s take a look at this class in detail. |
| </p> |
| <p> |
| <blockquote> |
| <pre> |
| public Image getImage(Object element) { |
| ImageDescriptor descriptor = null; |
| if (element instanceof MovingBox) { |
| descriptor = TreeViewerPlugin.getImageDescriptor("movingBox.gif"); |
| } else if (element instanceof Book) { |
| descriptor = TreeViewerPlugin.getImageDescriptor("book.gif"); |
| } else if (element instanceof BoardGame) { |
| descriptor = TreeViewerPlugin.getImageDescriptor("gameboard.gif"); |
| } else { |
| throw unknownElement(element); |
| } |
| |
| //obtain the cached image corresponding to the descriptor |
| Image image = (Image)imageCache.get(descriptor); |
| if (image == null) { |
| image = descriptor.createImage(); |
| imageCache.put(descriptor, image); |
| } |
| return image; |
| } |
| </pre></blockquote> |
| <p></p> |
| <p> |
| This method answers the correct image for the given |
| domain object, <b>element</b>. An ImageDescriptor is used to |
| load the image for the corresponding domain |
| object. If an image has not yet been |
| loaded, the image descriptor is asked to create the image. The |
| <code>getImageDescriptor</code><code>(String)</code> |
| method is shown below. This method will often be |
| implemented as a static method on the plug-in class since |
| it is a generic utility method and makes use of the |
| plug-in’s installURL. |
| </p> |
| <p> |
| <blockquote> |
| <pre> |
| public static ImageDescriptor getImageDescriptor(String name) { |
| String iconPath = "icons/"; |
| try { |
| URL installURL = getDefault().getDescriptor().getInstallURL(); |
| URL url = new URL(installURL, iconPath + name); |
| return ImageDescriptor.createFromURL(url); |
| } catch (MalformedURLException e) { |
| // should not happen |
| return ImageDescriptor.getMissingImageDescriptor(); |
| } |
| }</pre></blockquote> |
| <p></p> |
| <p><blockquote> |
| <pre> |
| public void dispose() { |
| for (Iterator i = imageCache.values().iterator(); i.hasNext();) { |
| ((Image) i.next()).dispose(); |
| } |
| imageCache.clear(); |
| }</pre></blockquote> |
| <p></p> |
| <p> |
| This dispose method makes sure the label provider correctly |
| disposes of the images it has created. This is very |
| important to insure that operating system resources are |
| not “leaked.” |
| </p> |
| <h2> |
| Setting the Initial Input |
| </h2> |
| <p> |
| Now that we’ve discussed the content provider and the |
| label provider, we are nearly finished with the code |
| required to create the tree viewer. The one step missing from the example code |
| is setting the initial input for the tree viewer. As it stands now, our |
| tree viewer does not contain any domain objects. |
| Here’s the code used to set the initial input of |
| the tree viewer. |
| </p> |
| <p> |
| <code>treeViewer.setInput(getInitalInput());</code> |
| </p> |
| <p> |
| When you set the tree viewer’s input, the tree |
| viewer works in conjunction with the content and label |
| providers to display the tree. Also, remember our content |
| provider’s <code><a href= |
| "#inputChanged">inputChanged(…)</a></code> method |
| will be invoked whenever the tree viewer’s setInput |
| method is called. |
| </p> |
| <p> |
| The getInitalInput() method simply creates our sample set |
| of domain objects. |
| </p> |
| <p> |
| <blockquote> |
| <pre> |
| public MovingBox getInitalInput() { |
| MovingBox root = new MovingBox(); |
| MovingBox books = new MovingBox("Books"); |
| MovingBox games = new MovingBox("Games"); |
| MovingBox books2 = new MovingBox("More books"); |
| MovingBox games2 = new MovingBox("More games"); |
| |
| root.addBox(books); |
| root.addBox(games); |
| root.addBox(new MovingBox()); |
| |
| books.addBox(books2); |
| games.addBox(games2); |
| |
| books.addBook(new Book("The Lord of the Rings", "J.R.R.", "Tolkien")); |
| books.addBoardGame(new BoardGame("Taj Mahal", "Reiner", "Knizia")); |
| books.addBook(new Book("Cryptonomicon", "Neal", "Stephenson")); |
| books.addBook(new Book("Smalltalk, Objects, and Design", "Chamond", "Liu")); |
| books.addBook(new Book("A Game of Thrones", "George R. R.", " Martin")); |
| books.addBook(new Book("The Hacker Ethic", "Pekka", "Himanen")); |
| //books.addBox(new MovingBox()); |
| |
| books2.addBook(new Book("The Code Book", "Simon", "Singh")); |
| books2.addBook(new Book("The Chronicles of Narnia", "C. S.", "Lewis")); |
| books2.addBook(new Book("The Screwtape Letters", "C. S.", "Lewis")); |
| books2.addBook(new Book("Mere Christianity ", "C. S.", "Lewis")); |
| games.addBoardGame(new BoardGame("Tigris & Euphrates", "Reiner", "Knizia")); |
| games.addBoardGame(new BoardGame("La Citta", "Gerd", "Fenchel")); |
| games.addBoardGame(new BoardGame("El Grande", "Wolfgang", "Kramer")); |
| games.addBoardGame(new BoardGame("The Princes of Florence", "Richard", "Ulrich")); |
| games.addBoardGame(new BoardGame("The Traders of Genoa", "Rudiger", "Dorn")); |
| games2.addBoardGame(new BoardGame("Tikal", "M.", "Kiesling")); |
| games2.addBoardGame(new BoardGame("Modern Art", "Reiner", "Knizia")); |
| return root; |
| }</pre></blockquote> |
| |
| <p></p> |
| <h2> |
| Selection |
| </h2> |
| <p> |
| In most applications, the user selects an item from the tree viewer in |
| order to perform some specific action. You can be notified of these |
| selections by adding a selection change listener |
| to the tree viewer. When a selection occurs in the tree |
| viewer, it will notify each of its selection change |
| listeners, passing along a selection event describing |
| what has been selected. Note that these events flow in the opposite |
| direction of the model-generated events we discussed earlier.<br><br> |
| |
| Here’s an example of handling selection in the tree viewer. |
| </p> |
| |
| <p> <a name= |
| "selectionChanged"></a> </p> |
| <blockquote> |
| <pre> |
| treeViewer.addSelectionChangedListener(new ISelectionChangedListener() { |
| public void selectionChanged(SelectionChangedEvent event) { |
| // if the selection is empty clear the label |
| if(event.getSelection().isEmpty()) { |
| text.setText(""); |
| return; |
| } |
| if(event.getSelection() instanceof IStructuredSelection) { |
| IStructuredSelection selection = (IStructuredSelection)event.getSelection(); |
| StringBuffer toShow = new StringBuffer(); |
| for (Iterator iterator = selection.iterator(); iterator.hasNext();) { |
| Object domain = (Model) iterator.next(); |
| String value = labelProvider.getText(domain); |
| toShow.append(value); |
| toShow.append(", "); |
| } |
| // remove the trailing comma space pair |
| if(toShow.length() > 0) { |
| toShow.setLength(toShow.length() - 2); |
| } |
| text.setText(toShow.toString()); |
| } |
| } |
| }); |
| </pre> |
| </blockquote> |
| <p></p> |
| <p> |
| Here an anonymous class selection changed listener is |
| created. The selection changed method is implemented to |
| display the currently selected items in a label or to |
| clear the label if no items are selected. A few points to |
| note: |
| </p> |
| <ul type="disc"> |
| <li> |
| Because the selection changed listener method is |
| defined in a very generic way, some casting is required. |
| For example, a tree viewer’s selection will |
| always be an <code>IStructuredSelection</code> even |
| though the <code>SelectionChangedEvent</code>’s |
| getSelection method will answer the more generic |
| <code>ISelection</code>. |
| </li> |
| <li> |
| We are using the label provider’s getText() |
| method to assist us in converting the domain objects to |
| a textual representation to display in the label. |
| </li> |
| <li> |
| In most UI frameworks, the selection-changed listener |
| will be called by the application’s UI thread. |
| SWT is no exception; this means you should be careful |
| to return from listener methods in a timely fashion. |
| </li> |
| </ul> |
| <p> |
| <img alt="tryit.gif (1K)" src="images/tryit.gif" height="13" width="61"> |
| </p> |
| <p> |
| Open the “Moving Box” view and select one or |
| more of the items in the tree. You should see the status |
| label update with the names of the selected items like |
| this: |
| </p> |
| <p> |
| <p align="center"><img alt="selection.jpg (8K)" src="images/selection.jpg" height="120" width="214"></p> |
| <p></p> |
| <h2> |
| Responding to Change |
| </h2> |
| <p> |
| Applications aren’t very useful if they don’t |
| handle change. Conceptually, change can be described in a couple |
| of ways. When domain objects are changed, the UI |
| usually reflects these changes. Likewise, user actions in the UI may |
| require updates in the domain objects. |
| </p> |
| <h3> |
| Responding to domain model changes |
| </h3> |
| <p> |
| When a change occurs in the domain model, the UI needs |
| to reflect that change. For example, if a new |
| book is added to one of our moving boxes |
| programmatically, we want the UI to show the newly added book. |
| </p> |
| <p> |
| While we want the domain to notify the UI through some |
| means, we do not want to “pollute” the domain |
| objects with knowledge about the UI. If the model and the |
| view are too strongly coupled, each becomes brittle and |
| fragile to change. We employ an Observer or |
| Event-Notification style pattern to break this strong |
| coupling. |
| </p> |
| <p> |
| In our example, we achieve this by creating a listener |
| interface that our domain objects notify when an |
| interesting change occurs. Now we need to provide |
| an object to listen for the changes. |
| </p> |
| <p> |
| Typically that object will be the tree viewer’s |
| content provider. Remember the <a href= |
| "#inputChanged">inputChanged</a>() method? Our |
| inputChanged method will register itself as a listener to |
| the domain object changes so it can notify the tree |
| viewer of any changes. |
| </p> |
| <p> |
| Typically the tree viewer will be notified of domain |
| object changes by calling one of the update methods. |
| These are important methods, so let’s take a look at each |
| of them in more detail. |
| </p> |
| <h4> |
| <a name="refeshAndUpdateDifference"></a>What’s the |
| difference between refresh and update? |
| </h4> |
| <p> |
| The tree viewer provides both a refresh and an update |
| method. What is the difference between these two and when |
| should you use one or the other? |
| </p> |
| <h5> |
| Refresh method |
| </h5> |
| <p> |
| The refresh method refreshes the domain object and all of |
| the domain object’s children. The tree viewer |
| updates the label for the object passed to the refresh |
| method. The tree viewer also asks its content viewer for |
| the children of the object passed to the refresh method |
| and recursively updates each of them by collaborating |
| with the label and content providers. |
| </p> |
| <p> |
| In addition to this version of refresh, there is also a |
| version that allows you to specify whether or not the |
| labels of existing elements should be updated. |
| </p> |
| <h5> |
| Update method<a name="update"></a> |
| </h5> |
| <p> |
| The update method simply updates the given domain object’s |
| label. In other words, if the domain object passed to |
| the update method contained new children, those new |
| children would not appear in the tree viewer by invoking |
| the update method. Invoking update will only update the |
| domain object’s label or image. |
| </p> |
| <h6> |
| Update properties |
| </h6> |
| <p> |
| In addition to this behavior, the update method also |
| provides a means to specify which “parts” of |
| the domain object changed. If you choose to specify these |
| sub-parts, the tree viewer may be able to optimize the |
| update. If no sub-properties are specified, a full update |
| of the element is performed. |
| </p> |
| <p> |
| In particular, when you call the update method the |
| following decisions are made: |
| </p> |
| <ul type="disc"> |
| <li> |
| Does the updated domain object need a new label and |
| image? |
| </li> |
| </ul> |
| <p> |
| The tree viewer collaborates |
| with your label provider in order to answer this question. |
| By default, all label providers |
| answer that they should be updated when a given property |
| of the domain object changes. You can override the |
| <code>isLabelProperty(Object element, String |
| property)</code> method to specify otherwise. |
| </p> |
| <ul type="disc"> |
| <li> |
| If the tree viewer contains any filters, it asks each |
| filter if the updated domain object needs to be filtered |
| out of the tree. |
| </li> |
| </ul> |
| <p> |
| The same rules apply here. Each viewer filter can specify |
| whether or not it is affected by a change in the given |
| property. By default, viewer filters answer false but you |
| can override the <code>isFilterProperty(Object element, |
| String property)</code> method to specify otherwise. |
| </p> |
| <ul type="disc"> |
| <li> |
| If the tree viewer contains a sorter, it asks the |
| sorter if the updated domain object needs to be |
| re-sorted. |
| </li> |
| </ul> |
| <p> |
| The same rules apply again. The sorter can specify whether or |
| not it is affected by a change in the given property. By |
| default, viewer sorters answer false but you can override |
| the <code>isSorterProperty(Object element, String |
| property)</code> method to specify otherwise. |
| </p> |
| <h4>Example of Responding to Domain Object Additions and Removals</h4> |
| <p> |
| Let's take a look at how to respond to domain object changes. Let's |
| add a new book to one of the moving boxes. We will add this book in |
| response to a button push. It is |
| important to understand, however, that this domain change could come |
| from "anywhere" - a database trigger, a background thread or some other |
| program. We use a button press to keep it simple. |
| </p> |
| <img alt="tryit.gif (1K)" src="images/tryit.gif" height="13" width="61"> |
| <p> |
| When you press the "Add New Book" button on the view's toolbar, a |
| book will be added to the selected moving box. What happens when the |
| button is pressed? When the SWT button is presed the OS generates an event |
| that is forwarded to the button's selection listener. The selection listener |
| method adds a new book to the selected moving box. When a new book is |
| added, the moving box will notify its listeners that a domain change has |
| occurred. Since we added the content provider as a domain listener, it will |
| be notified of the change. The content provider then updates the tree viewer. |
| </p> |
| <img alt="addBook.jpg (5K)" src="images/addBook.jpg" height="55" width="415"> |
| <p> |
| If nothing is selected, the book will be added to the topmost moving |
| box. |
| </p> |
| <p>Let's take a look at the code. |
| </p> |
| <blockquote> |
| <pre> |
| protected void addNewBook() { |
| MovingBox receivingBox; |
| if (treeViewer.getSelection().isEmpty()) { |
| receivingBox = root; |
| } else { |
| IStructuredSelection selection = (IStructuredSelection) treeViewer.getSelection(); |
| Model selectedDomainObject = (Model) selection.getFirstElement(); |
| if (!(selectedDomainObject instanceof MovingBox)) { |
| receivingBox = selectedDomainObject.getParent(); |
| } else { |
| receivingBox = (MovingBox) selectedDomainObject; |
| } |
| } |
| receivingBox.add(Book.newBook()); |
| } |
| </pre></blockquote> |
| <p> |
| The addNewBook method is called when the "Add a New Book" toolbar button |
| is pressed. Notice that if multiple items are selected, we ignore all of them |
| except for the first one. Also notice that if the selected item is not a moving box, |
| we ask the selected item for its parent, which will be a moving box. Once |
| we have the moving box domain object, we tell it to add a new book. |
| </p> |
| <p> |
| The moving box's add method is not very interesting. It simply adds the |
| book and notifies its listeners that a book has been added. When this |
| notification occurs, the content viewer will be informed since it |
| <a href="#inputChanged">registered</a> itself as a listener for these |
| domain changes. Let's take a look at how the content provider services |
| the addition event notification. Here's the content provider's listener |
| method that is called on additions. |
| </p> |
| <blockquote> |
| <pre> |
| public void add(DeltaEvent event) { |
| Object movingBox = ((Model)event.receiver()).getParent(); |
| viewer.refresh(movingBox, false); |
| } |
| </pre></blockquote> |
| <p> |
| Pretty simple, right? The content provider asks the event for the receiver, |
| which is the newly added book. Next, the content provider asks the |
| tree viewer to refresh the moving box that contains the newly added |
| book. Remember that refresh will cause the tree viewer to consult with |
| its content provider to supply a list of children for the refresh |
| object. The false argument is passed along to the refresh method |
| to let it know that we do not need the labels of the other objects in |
| the tree refreshed. |
| </p> |
| <p>Removing something from the tree works in exactly the same way. We |
| remove the domain object from the moving box and ask the tree viewer |
| to acquire its model objects from the content provider again. |
| </p> |
| <h3> |
| Responding to UI changes |
| </h3> |
| <p> |
| We also need to respond to changes made in the UI. Often |
| these UI changes cause a domain object to change. For |
| example, if we edit the author of one of the books in the |
| tree viewer, we need to make sure this change is |
| correctly propagated to the book domain object. |
| </p> |
| <p> |
| Typically, you will create listeners and add them to the |
| tree viewer. Your listener methods will retrieve the necessary |
| domain objects from the tree viewer or passed event |
| object. |
| </p> |
| <p> |
| An example of this technique was shown in the |
| selectionChanged method. Here's part of that method again. |
| </p> |
| <p> |
| <blockquote> |
| <pre> |
| public void selectionChanged(SelectionChangedEvent event) { |
| // if the selection is empty clear the label |
| if(event.getSelection().isEmpty()) { |
| text.setText(""); |
| return; |
| } |
| if(event.getSelection() instanceof IStructuredSelection) { |
| IStructuredSelection selection = (IStructuredSelection)event.getSelection(); |
| StringBuffer toShow = new StringBuffer(); |
| for (Iterator iterator = selection.iterator(); iterator.hasNext();) { |
| <img SRC="images/tag_1.gif" BORDER=0 height=13 width=24>Object domain = (Model) iterator.next(); |
| String value = labelProvider.getText(domain); |
| toShow.append(value); |
| toShow.append(", "); |
| } |
| // remove the trailing comma space pair |
| if(toShow.length() > 0) { |
| toShow.setLength(toShow.length() - 2); |
| } |
| text.setText(toShow.toString()); |
| } |
| </pre></blockquote> |
| <p></p> |
| <p> |
| <img SRC="images/tag_1.gif" BORDER=0 height=13 width=24> In this example, we're retrieving the |
| domain object from the event object's selection. We could have just as easily asked the tree viewer |
| for the currently selected objects. |
| </p> |
| <p> |
| Here is a list of listeners you can add to a tree viewer |
| along with a description of each of them. |
| </p> |
| <table border="1" cellspacing="3" cellpadding="0" width="595"> |
| <tr> |
| <td valign="top"> |
| <p align="center"> |
| <b>Listener Type</b> |
| </p> |
| </td> |
| <td valign="top"> |
| <p align="center"> |
| <b>Description</b> |
| </p> |
| </td> |
| </tr> |
| <tr> |
| <td valign="top"> |
| <p> |
| <code>addHelpListener(HelpListener)</code> |
| </p> |
| </td> |
| <td valign="top"> |
| <p> |
| Responds to help requests |
| </p> |
| </td> |
| </tr> |
| <tr> |
| <td valign="top"> |
| <p> |
| <code>addSelectionChangedListener</code><code>(ISelectionChangedListener)</code> |
| </p> |
| </td> |
| <td valign="top"> |
| <p> |
| Responds to selection changes |
| </p> |
| </td> |
| </tr> |
| <tr> |
| <td valign="top"> |
| <p> |
| <code>addDoubleClickListener</code><code>(IdoubleClickListener)</code> |
| </p> |
| </td> |
| <td valign="top"> |
| <p> |
| Responds to double clicks |
| </p> |
| </td> |
| </tr> |
| <tr> |
| <td valign="top"> |
| <p> |
| <code>addDragSupport</code><code>(int, Transfer[], |
| DragSourceListener)</code> |
| </p> |
| </td> |
| <td valign="top"> |
| <p> |
| Sets up a listener to support dragging items out of |
| the tree viewer |
| </p> |
| </td> |
| </tr> |
| <tr> |
| <td valign="top"> |
| <p> |
| <code>addDropSupport</code><code>(int, Transfer[], |
| DropTargetListener)</code> |
| </p> |
| </td> |
| <td valign="top"> |
| <p> |
| Sets up a listener to support dropping objects in |
| tree viewer |
| </p> |
| </td> |
| </tr> |
| <tr> |
| <td valign="top"> |
| <p> |
| <code>addTreeListener</code><code>(ItreeViewerListener)</code> |
| </p> |
| </td> |
| <td valign="top"> |
| <p> |
| Responds to expand and collapse events |
| </p> |
| </td> |
| </tr> |
| </table> |
| <h2> |
| <a name="viewFilters"></a><a name= |
| "viewerFilters"></a>Viewer Filters |
| </h2> |
| <p> |
| Viewer filters are used by a tree viewer to extract a |
| subset of the domain objects provided by the content |
| provider. You add and remove viewer filters from the tree |
| viewer. |
| </p> |
| <p> |
| Viewer filters are additive, which means that the output of |
| one filter is passed in as the input of the next filter. |
| The final result has been filtered through each view |
| filter. |
| </p> |
| <p> |
| ViewerFilter is an abstract class. In order to implement |
| a viewer filter you subclass this class and override a |
| single method, <code>select(Viewer, Object, |
| Object)</code>. The method should answer true if |
| the domain object makes it through the filter. The select |
| method also passes the domain object’s |
| parent. |
| </p> |
| <p> |
| If you need more sophisticated filtering, you may override |
| other methods in ViewFilter to refine how the filtering |
| is accomplished. In particular the <code>isFilterProperty(Object |
| domain, String property)</code> method can be used to |
| answer whether or not the particular filter is interested in a |
| <b>property</b> change to the <b>domain</b> object. |
| </p> |
| <p> |
| The <code>isFilterProperty</code> method is used in |
| conjunction with the tree viewer’s update method. |
| More information about the update method can be found <a |
| href="#update">here</a>. |
| </p> |
| <p> |
| Let’s build a view filter that shows only the board |
| games contained in our moving boxes. |
| </p> |
| <p> |
| When building filters, it’s important to keep in |
| mind the content providers and the input element for the viewer. For |
| example, if we use the following filter, we’ll have |
| problems. |
| </p> |
| <p> |
| <blockquote> |
| <pre> |
| public boolean select(Viewer viewer, Object parentElement, Object element) { |
| return element instanceof BoardGame; |
| } |
| </pre></blockquote> |
| <p></p> |
| <p> |
| The problem with this example is that we will filter out our parent. |
| In other words, this filter rejects moving boxes, but |
| remember that moving boxes are what contain board games. So if |
| we filter out moving boxes, the content provider will not |
| have anything left to ask for content. |
| </p> |
| <p> |
| We can solve this by changing our filter to |
| this: |
| </p> |
| <p> |
| <blockquote> |
| <pre> |
| public boolean select(Viewer viewer, Object parentElement, Object element) { |
| return element instanceof BoardGame || element instanceof MovingBox; |
| } |
| </pre></blockquote> |
| <p></p> |
| <p> |
| This filter works as intended, leaving only board games |
| visible in the tree viewer. |
| </p> |
| <p> |
| Let’s build another filter that both augments and |
| works independently from the board game filter. The second |
| filter will show only the contents of moving boxes that |
| contain at least 3 items. Here’s the code for our |
| ThreeItemFilter: |
| </p> |
| <p> |
| <blockquote> |
| <pre> |
| public boolean select(Viewer viewer, Object parentElement, Object element) { |
| return parentElement instanceof MovingBox && ((MovingBox)parentElement).size() >= 3; |
| } |
| </pre></blockquote> |
| <p></p> |
| <p> |
| Coupled with the first filter, this filter can be used |
| to show all of the board games contained in moving boxes |
| with three or more items in them. |
| </p> |
| <p> |
| Another important point to remember with filters is that |
| they also apply to the root of the tree viewer. In other words, if the |
| root does not contain at least three |
| items, then you will never get around to filtering the |
| children. |
| </p> |
| <p> |
| <img alt="tryit.gif (1K)" src="images/tryit.gif" height="13" width="61"> |
| </p> |
| <p> |
| Open the “Moving Box” view and select the |
| triangle near the edge of the window. Then choose the |
| filters and verify that they are working. |
| </p> |
| <p> |
| <p align="center"><img alt="filters.jpg (13K)" src="images/filters.jpg" height="117" width="482"></p> |
| <p></p> |
| <h2> |
| <a name="viewerSorters"></a>Viewer Sorters |
| </h2> |
| <p> |
| The tree viewer collaborates with a viewer sorter to order |
| the domain objects provided by the content provider. |
| </p> |
| <p> |
| In contrast to our discussion of viewer filters, a tree viewer may |
| utilize only one viewer sorter at a given time. You can always set a different view |
| sorter whenever you like, but at any specific moment, there is either |
| exactly one viewer sorter or none at all. |
| </p> |
| <p> |
| ViewerSorter is an abstract class that defines a default |
| sorting behavior. Conceptually, the only method that needs |
| to be implemented is the <code>public int compare(Viewer |
| viewer, Object e1, Object e2)</code>method. The |
| ViewerSorter class provides a default implementation of |
| this method that defines a default sorting behavior. |
| </p> |
| <p> |
| This default sorting behavior consists of grouping |
| each domain object into categories [by |
| invoking the public int category(Object |
| element) method] and then sorting each domain |
| object within a category by using the tree viewer’s |
| label content provider’s getText method. |
| </p> |
| <p> |
| By default the viewer sorter considers all domain |
| objects to be in the same category. |
| </p> |
| <p> |
| If this two-phase default sorting behavior makes |
| sense for your application, then you likely only need to |
| override the category method in order to place your domain objects |
| into their respective categories. Otherwise you’re |
| probably better off implementing the compare |
| method to sort in the appropriate manner for your |
| application. We’ll take a look at both |
| approaches. |
| </p> |
| <p> |
| As with the view filter, the view sorter contains an |
| <code>isSorterProperty</code> method that can be used to |
| answer whether or not the particular sorter would be |
| affected by a change to the given property for the given |
| domain object. |
| </p> |
| <p> |
| The <code>isSorterProperty</code> method is used in |
| conjunction with the tree viewer’s update method. |
| More information about the update method can be found <a |
| href="#update">here</a>. |
| </p> |
| <p> |
| Our first sorter will rely on the default sorting |
| behavior and implement a sorter that orders the items in |
| such a way that books appear before moving boxes, which |
| appear before board games. To achieve this we can simply |
| override the category method like this: |
| </p> |
| <p> |
| <blockquote> |
| <pre> |
| /** Orders the items in such a way that books appear |
| * before moving boxes, which appear before board games. */ |
| public int category(Object element) { |
| if(element instanceof Book) return 1; |
| if(element instanceof MovingBox) return 2; |
| return 3; |
| } |
| </pre></blockquote> |
| |
| <p></p> |
| <p> |
| The default sorting behavior uses the category method to |
| place objects into bins, and the bins are arranged in |
| ascending order. |
| </p> |
| <p> |
| Our second sorter will use the text labels to order the items in an article |
| independent fashion. This means that the literals (e.g. |
| “the”, “a”, “El” and |
| “La” will be ignored in terms of sorting. For |
| example “A Zoo” will sort after “The |
| Car.” This was not true for the first sorter. |
| </p> |
| <p> |
| The default sort method uses a simple case insensitive |
| sort for items within the same category. We will |
| override the compare method to implement an |
| article-ignoring sort. |
| </p> |
| <p> |
| Here’s our compare method (which is mostly a copy |
| of ViewerSorter’s method). |
| </p> |
| <p><blockquote> |
| <pre> |
| public int compare(Viewer viewer, Object e1, Object e2) { |
| int cat1 = category(e1); |
| int cat2 = category(e2); |
| if (cat1 != cat2) return cat1 - cat2; |
| String name1, name2; |
| if (viewer == null || !(viewer instanceof ContentViewer)) { |
| name1 = e1.toString(); |
| name2 = e2.toString(); |
| } else { |
| IBaseLabelProvider prov = ((ContentViewer)viewer).getLabelProvider(); |
| if (prov instanceof ILabelProvider) { |
| ILabelProvider lprov = (ILabelProvider)prov; |
| name1 = lprov.getText(e1); |
| name2 = lprov.getText(e2); |
| } else { |
| name1 = e1.toString(); |
| name2 = e2.toString(); |
| } |
| } |
| if(name1 == null) name1 = ""; |
| if(name2 == null) name2 = ""; |
| name1 = stripArticles(name1); |
| name2 = stripArticles(name2); |
| return collator.compare(name1, name2); |
| } |
| </pre></blockquote> |
| <p></p> |
| <p> |
| This is a verbatim copy of the superclass’s method, |
| except for the addition of stripArticles near the bottom |
| of the method. The stripArticles method looks like this: |
| </p> |
| <p> |
| <blockquote> |
| <pre> |
| protected String stripArticles(String name) { |
| String test = name.toLowerCase(); |
| if(test.startsWith("the ") && test.length() > 3) { |
| return name.substring(4); |
| } else if(test.startsWith("a ") && test.length() > 1) { |
| return name.substring(2); |
| } else if(test.startsWith("el ") && test.length() > 2) { |
| return name.substring(3); |
| } else if(test.startsWith("la ") && test.length() > 2) { |
| return name.substring(3); |
| } |
| return name; |
| } |
| </pre></blockquote> |
| <p></p> |
| <p> |
| This sorter does exactly what we want and shows an |
| example of overriding the compare method to implement the search. |
| </p> |
| <p> |
| <img alt="tryit.gif (1K)" src="images/tryit.gif" height="13" width="61"> |
| </p> |
| <p> |
| Open the “Moving Box” view and select the |
| triangle near the edge of the window. Then choose the |
| sorters and verify that they work as you expected. |
| </p> |
| <p> |
| |
| </p> |
| <p> |
| <p align="center"><img alt="sorters.jpg (15K)" src="images/sorters.jpg" height="144" width="501"></p> |
| <p></p> |
| <h2> |
| Conclusion |
| </h2> |
| <p> |
| This article has demonstrated the basic usage of tree |
| viewers. Hopefully, you now understand how to use the tree |
| viewer and understand where the tree viewer fits within |
| the larger JFace framework. |
| </p> |
| <p> |
| There are a number of topics planned for an |
| “advanced” tree viewer article, including |
| drag and drop, auto expand nodes |
| within the tree, in-place editing and making use of domain |
| object properties. |
| </p> |
| <h2> |
| Frequently Asked Questions |
| </h2> |
| <p> |
| Here is a list of frequently asked questions about using |
| the tree viewer. |
| </p> |
| <h3> |
| How do I add or remove an item from the tree? |
| </h3> |
| <p> |
| Addition and removal of items is essentially the same. |
| </p> |
| <p> |
| To remove an item from the tree viewer you should remove |
| the domain object from your model. Then ask |
| the tree viewer to refresh the parent of the domain |
| model. When the update method is called on the tree |
| viewer, it will ask its content provider for |
| the children of the parent of the domain model. However, since you |
| removed it from the parent, it will not be there and |
| subsequently will be removed from the view. |
| </p> |
| <p> |
| If your domain objects trigger events, you will add |
| the content provider as a listener so that whenever removals |
| occur in your domain object, the content provider can be |
| notified and perform the steps above. |
| </p> |
| <p> |
| What about the tree viewer's remove methods? |
| If you read the Javadoc for these |
| methods, they will tell you the remove methods should be |
| called by the <b>content provider</b>. This makes sense |
| after all, because the content provider is responsible |
| for supplying the content. If you removed the item from |
| the tree viewer without “going through” the |
| content provider, the item would likely reappear the next |
| time the parent of the item is refreshed. |
| </p> |
| <h3> |
| How do I programmatically select an item in the tree? |
| </h3> |
| <p> |
| Call the tree viewer’s setSelection(new |
| StructuredSelection(<domain model>), true). |
| </p> |
| <h3> |
| What is the difference between the tree viewer’s |
| update and refresh methods? |
| </h3> |
| <p> |
| See this <a href= |
| "#refeshAndUpdateDifference">discussion</a>. |
| </p> |
| <h3> |
| How do I reveal a newly added model object that is hidden? |
| </h3> |
| <p> |
| Let’s assume you’ve added the domain object |
| book to your moving box. The moving box is |
| already in the tree but isn’t expanded when you add |
| the book to it. You want the book to be revealed when |
| you add it to the moving box. You would do this: |
| </p> |
| <p> |
| <blockquote><pre> |
| treeViewer.setExpandedState(movingBox, true); |
| treeViewer.refresh(movingBox, false); |
| </pre></blockquote> |
| <p></p> |
| <h3> |
| My tree viewer hangs or isn’t very responsive |
| because I’m hitting a database in my content |
| provider. What can I do? |
| </h3> |
| <p> |
| In most UI frameworks, the listeners are called by the |
| application’s UI thread. SWT is no exception. This |
| means you should be careful to return from listener or |
| provider methods in a timely fashion. If you don’t, |
| the GUI will not receive events, which makes for a |
| sluggish and potentially useless UI. |
| </p> |
| <p> |
| If you have a long running operation in your listener or |
| provider methods, you should consider forking another |
| thread to do the actual work. For example, if you are |
| populating your tree from a database, you can’t have |
| your content provider waiting for the database to return |
| the domain objects. Instead you could have the content |
| provider return a stub object that shows something like |
| “Loading Data…” while the background |
| thread loads the real data. |
| </p> |
| <p> |
| Once the background thread has loaded the data, it would |
| need to refresh the parent of the stub object so that the |
| parent would request its domain objects again. Now |
| when the parent requests the domain objects, they already |
| exist since they’ve been loaded from the database. |
| </p> |
| <h3> |
| How do I add a viewer filter to a tree viewer? |
| </h3> |
| <p> |
| See this <a href="#viewerFilters">discussion</a>. |
| </p> |
| <h3> |
| How do I change the order of the items in a tree viewer? |
| </h3> |
| <p> |
| See this <a href="#viewerSorters">discussion</a>. |
| </p> |
| <h3> |
| What does the setUseHashlookup() method do? |
| </h3> |
| <p> |
| The setUseHashlookup(boolean) method informs the tree |
| viewer that it should maintain a hash mapping of your |
| domain object to the corresponding SWT TreeItem. Whenever |
| the tree viewer needs to manipulate the SWT tree items, |
| it uses the hash lookup or performs a recursive search |
| starting at the root looking for the item. |
| </p> |
| <p> |
| The tree viewer manipulates SWT tree items whenever you |
| add, remove, collapse, expand, reveal, refresh or update |
| a domain object. In other words, the tree viewer is |
| manipulating tree items all the time. |
| </p> |
| <p> |
| You should probably always setUseHashlookup to true. |
| The only downside to doing so is that the tree viewer will |
| require more memory since it’s maintaining the |
| mapping, but it should perform much faster with a large |
| set of domain objects. |
| </p> |
| <p> |
| Another important point about tree viewers - in general, they don’t |
| handle domain objects that |
| map to the same value. This is because the tree viewer |
| uses the equals() method and potentially the hashCode() |
| method if you use the setUseHashlookup method. |
| </p> |
| </body> |
| </html> |
| |