| <!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> | |