| <!doctype html public "-//w3c//dtd html 4.0 transitional//en"> | |
| <html> | |
| <head> | |
| <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> | |
| <meta name="Author" content="Build"> | |
| <meta name="GENERATOR" content="Mozilla/4.5 [en] (WinNT; I) [Netscape]"> | |
| <title>Creating an Eclipse View</title> | |
| <link rel="stylesheet" href="../default_style.css"> | |
| </head> | |
| <body> | |
| <div align=right> <font face="Times New Roman, Times, serif"><font size=-1>Copyright | |
| © 2001 Object Technology International, Inc.</font></font></div> | |
| <table BORDER=0 CELLSPACING=0 CELLPADDING=2 WIDTH="100%" > | |
| <caption><TBODY> | |
| <br></TBODY></caption> | |
| <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> | |
| <h1> | |
| <img SRC="Idea.jpg" height=86 width=120 align=CENTER></h1> | |
| <center> | |
| <h1> | |
| Creating an Eclipse View</h1></center> | |
| <blockquote> | |
| <b>Summary</b> | |
| <br> | |
| In the Eclipse Platform a view is typically used to navigate a hierarchy | |
| of information, open an editor, or display properties for the active editor. | |
| In this article the design and implementation of a view will be examined | |
| in detail. You'll learn how to create a simple view based on SWT, | |
| and a more advanced view using the JFace viewer hierarchy. We'll | |
| also look at ways to achieve good integration with many of the existing | |
| features in the workbench, such as the window menu and toolbar, view linking, | |
| workbench persistence and action extension. | |
| <p><b>By Dave Springgay, OTI</b> | |
| <br> <font size="-1">November 2, 2001</font> | |
| </blockquote> | |
| <hr WIDTH="100%"> | |
| <h2> | |
| Introduction</h2> | |
| In the Eclipse Platform the workbench contains a collection of workbench | |
| windows. Each workbench window contains one or more pages, and each | |
| page contains a collection of editors and views. An editor is typically | |
| used to edit or browse a document or input object. Modifications | |
| made in an editor follow an open-save-close lifecycle model. A view | |
| is typically used to navigate a hierarchy of information, open an editor, | |
| or display properties for the active editor. In contrast to an editor, | |
| modifications made in a view are saved immediately. The layout of | |
| editors and views within a page is controlled by the active perspective. | |
| <p>The workbench contains a number of standard components which demonstrate | |
| the role of a view. For instance, the Navigator view is used to display | |
| and navigate through the workspace. If you select a file in the Navigator, | |
| you can open an editor on the contents of the file. Once an editor | |
| is open, you can navigate the structure in the editor data using the Outline | |
| view, or edit the properties of the file contents using the Properties | |
| view. | |
| <p>In this article we'll look at how you, as a plug-in developer, can add | |
| new views to the workbench. First we'll examine the process through | |
| the creation of a simple Label view. This view just displays the | |
| words "Hello World". Then we'll design and implement a more comprehensive | |
| view, called Word view, which displays a list of words which can be modified | |
| through addition and deletion. And finally, we'll look at ways to | |
| achieve good integration with many of the existing features in the workbench. | |
| <h2> | |
| Source Code</h2> | |
| <p>To run the example or view the source for code for this article you can unzip | |
| <a href="viewArticleSrc.zip">viewArticleSrc.zip</a> into your <i>plugins/ </i>subdirectory. | |
| </p> | |
| <p> </p> | |
| <h2> | |
| <a NAME="Adding a New Perspective"></a>Adding a New View</h2> | |
| A new view is added to the workbench using a simple three step process. | |
| <br> | |
| <ol> | |
| <li> | |
| Create a plug-in.</li> | |
| <li> | |
| Add a view extension to the plugin.xml file.</li> | |
| <li> | |
| Define a view class for the extension within the plug-in.</li> | |
| </ol> | |
| <p><br>To illustrate this process we'll define a view called "Label", which | |
| just displays the words "Hello World". | |
| <h3> | |
| Step 1: Create a Plug-in</h3> | |
| In step 1 we need to create a new plug-in. The process of plug-in | |
| creation is explained in detail in <a href="http://www.eclipse.org/articles/Your%20First%20Plug-in.html">Your | |
| First Plugin</a>, by Jim Amsden, so we won't go into the details here. | |
| To be brief, we need to create a plugin project, and then define a plugin.xml | |
| file (shown below). This file contains a declaration of the plug-in | |
| id, name, pre-requisites, etc. | |
| <pre><?xml version="1.0" encoding="UTF-8"?> | |
| <plugin | |
| name="Views Plugin" | |
| id="org.eclipse.ui.articles.views" | |
| version="1.0.0" | |
| provider-name="OTI"> | |
| <requires> | |
| <import plugin="org.eclipse.core.boot"/> | |
| <import plugin="org.eclipse.core.runtime"/> | |
| <import plugin="org.eclipse.core.resources"/> | |
| <import plugin="org.eclipse.swt"/> | |
| <import plugin="org.eclipse.ui"/> | |
| </requires> | |
| <runtime> | |
| <library name="views.jar"/> | |
| </runtime> | |
| </plugin></pre> | |
| <h3> | |
| Step 2: Add a View Extension to the plugin.xml file</h3> | |
| Once we have a plugin, we can define a view extension. The org.eclipse.ui | |
| plug-in defines a single extension point for view contribution: org.eclipse.ui.views. | |
| In the XML below, we use this extension point to add the Label view extension. | |
| This XML is added to the plugin.xml file, after the runtime element, and | |
| contains the basic attributes for the view: id, name, icon and class. | |
| <pre><extension point="org.eclipse.ui.views"> | |
| <view id="org.eclipse.ui.articles.views.labelview" | |
| name="Label View" | |
| <img SRC="tag_a.jpg" BORDER=0 height=13 width=24> class="org.eclipse.ui.articles.views.LabelView" | |
| <img SRC="tag_b.jpg" BORDER=0 height=13 width=24> icon="icons\view.gif"/> | |
| </extension></pre> | |
| A complete description of the view extension point and the syntax are available | |
| in the developer documentation for <tt>org.eclipse.ui</tt>. The attributes | |
| are described as follows. | |
| <br> | |
| <ul> | |
| <li> | |
| <b>id</b> - a unique name that will be used to identify this view</li> | |
| <li> | |
| <b>name</b> - a translatable name that will be used in the UI for this | |
| view</li> | |
| <li> | |
| <b>class</b> - a fully qualified name of the class that implements <tt>org.eclipse.ui.IViewPart</tt>. | |
| A common practice is to subclass <tt>org.eclipse.ui.part.ViewPart</tt> | |
| in order to inherit the default functionality.</li> | |
| <li> | |
| <b>icon</b> - a relative name of the icon that will be associated with | |
| the view.</li> | |
| </ul> | |
| <p><br>Perhaps the most important attribute is <i>class </i>(<img SRC="tag_a.jpg" height=13 width=24 align=ABSCENTER>). | |
| This attribute must contain the fully qualified name of a class that implements | |
| <tt>org.eclipse.ui.IViewPart</tt>. | |
| The <tt>IViewPart</tt> interface defines the minimal responsibilities of | |
| the view, the chief one being to create an SWT Control within the workbench. | |
| This provides the presentation for an underlying model. In the XML | |
| above, the class for the Label view is defined as <tt>org.eclipse.ui.articles.views.LabelView</tt>. | |
| The implementation of <tt>LabelView</tt> will be examined in a few paragraphs. | |
| <p>The <i>icon</i> attribute (<img SRC="tag_b.jpg" height=13 width=24 align=ABSCENTER>) | |
| contains the name of an image that will be associated with the view. | |
| This image must exist within the plugin directory or one of the sub directories. | |
| <h3> | |
| Step 3: Define a View Class for the Extension within the Plug-in</h3> | |
| Now we need to define the view class. To help us out, the platform | |
| contains an abstract class, named <tt>org.eclipse.ui.part.ViewPart</tt>, | |
| which implements most of the default behavior for an <tt>IViewPart</tt>. | |
| By subclassing this, we inherit all of the behavior. The result is | |
| LabelView (shown below). The methods in this class implement the | |
| view specific presentation. The <tt>createPartControl</tt> method | |
| (<img SRC="tag_b.jpg" height=13 width=24 align=ABSCENTER>) creates | |
| an SWT <tt>Label</tt> object to display the phrase "Hello World". | |
| The <tt>setFocus</tt> (<img SRC="tag_a.jpg" height=13 width=24 align=ABSCENTER>) | |
| method gives focus to this control. Both of these methods will be | |
| called by the platform. | |
| <pre>package org.eclipse.ui.articles.views; | |
| import org.eclipse.swt.widgets.*; | |
| import org.eclipse.ui.part.ViewPart; | |
| public class LabelView extends ViewPart { | |
| private Label label; | |
| public LabelView() { | |
| super(); | |
| } | |
| <img SRC="tag_a.jpg" height=13 width=24 align=ABSCENTER> public void setFocus() { | |
| label.setFocus(); | |
| } | |
| <img SRC="tag_b.jpg" height=13 width=24 align=ABSCENTER> public void createPartControl(Composite parent) { | |
| label = new Label(parent, 0); | |
| label.setText("Hello World"); | |
| } | |
| }</pre> | |
| Once the LabelView class has been declared and compiled, we can test the | |
| results. To do this, invoke the Perspective > Show View > Other menu | |
| item. A dialog will appear containing all of the view extensions: | |
| the Label View item will appear in the Other category. If you select | |
| the Label View item and press OK, the view will be instantiated and opened | |
| in the current perspective. Here is a screen snapshot of the Label | |
| view after it has been opened within the Resource perspective. | |
| <center> | |
| <p><img SRC="LabelView.jpg" height=397 width=534></center> | |
| <h3> | |
| View Lifecycle</h3> | |
| The lifecycle of a view begins when the view is added to a workbench page. | |
| This can occur during the creation of the page, or afterwards, if the user | |
| invokes Perspective > Show View. In either case the following lifecycle | |
| is performed. | |
| <br> | |
| <ol> | |
| <li> | |
| An instance of the view class is instantiated. If the view class | |
| does not implement <tt>IViewPart</tt> an <tt>PartInitException</tt> is | |
| thrown.</li> | |
| <li> | |
| The <tt>IViewPart.init</tt> method is called to initialize the context | |
| for the view. An <tt>IViewSite</tt> object is passed, and contains | |
| methods to get the containing page, window, and other services is passed.</li> | |
| <li> | |
| The <tt>IViewPart.createPartControl</tt> method is called. Within | |
| this method the view can create any number of SWT controls within a parent | |
| <tt>Composite</tt>. | |
| These provide the presentation for some underlying, view specific model.</li> | |
| </ol> | |
| <p><br>When the view is closed the lifecycle is completed. | |
| <br> | |
| <ol> | |
| <li> | |
| The parent <tt>Composite</tt> passed to <tt>createPartControl</tt> is disposed. | |
| This children are also implicitly disposed. If you wish to run any | |
| code at this time, you must hook the control dispose event.</li> | |
| <li> | |
| The <tt>IViewPart.dispose</tt> method is called to terminate the part lifecycle. | |
| This is the last method which the workbench will call on the part. | |
| It is an ideal time to release any fonts, images, etc.</li> | |
| </ol> | |
| <h3> | |
| View Position</h3> | |
| As we have already seen, the user can open a view by invoking Perspective | |
| > Show View. In this scenario, the initial position of the new view | |
| is determined by the active perspective within the current page. | |
| You can think of a perspective as a layout containing views, folders, and | |
| place holders. A <i>folder</i> is a stack of views. A <i>place | |
| holder</i> marks the desired position of a view, should the user open it. | |
| When a new view is opened the platform will search the perspective for | |
| a place holder with the same <tt>id</tt> as the view. If such a place | |
| holder is found, it will be replaced by the new view. Otherwise, | |
| the new view will be placed on the right hand side of the page. | |
| <p>As a plugin developer, you may also define a new perspective which contains | |
| your view, or add your view to a perspective which has been contributed | |
| by another plug-in. In either case, you have greater control over view | |
| position. For more information about the use of perspectives see | |
| <a href="http://www.eclipse.org/articles/using-perspectives/PerspectiveArticle.html">Using | |
| Perspectives in the Eclipse UI</a>. | |
| <h3> | |
| Can I Declare A View Through API?</h3> | |
| Every view within the workbench must be declared in XML. There are | |
| two reasons for this. | |
| <br> | |
| <ol> | |
| <li> | |
| Within Eclipse, XML is typically used to declare the presence of an extension | |
| and the circumstances under which it should be loaded. This information | |
| is used to load the extension and its plugin lazily for better startup | |
| performance. For views in particular, the declaration is used to | |
| populate the Show View menu item before the plugin is loaded.</li> | |
| <li> | |
| The view declaration is also used for workbench persistence. When | |
| the workbench is closed, the id and position of each view within the workbench | |
| is saved to a file. It is possible to save the specific view class. | |
| However, the persistence of class names in general is brittle, so view | |
| id's are used instead. On startup, the view id is mapped to a view | |
| extension within the plugin registry. Then a new instance of the | |
| view class is instantiated.</li> | |
| </ol> | |
| <h2> | |
| The Word View</h2> | |
| In this section we'll implement a more comprehensive view, called Word | |
| view, which displays a list of words which can be modified through addition | |
| and deletion. | |
| <h3> | |
| Adding a View Extension to the plugin.xml file</h3> | |
| To start out, we add another view extension to the plugin.xml (shown below). | |
| This extension has the same format as the Label view: id, name, icon and | |
| class. In addition, the extension contains a <i>category</i> element | |
| (<img SRC="tag_a.jpg" height=13 width=24 align=ABSCENTER>). A <i>category</i> | |
| is used to group a set of views within the Show View dialog. A category | |
| must be defined using a category element. Once this has been done, | |
| we can populate the category by including the category attribute in the | |
| Word view declaration (<img SRC="tag_b.jpg" height=13 width=24 align=ABSCENTER>). | |
| The category, and the Word view within it, will both be visible in the | |
| Show View dialog. | |
| <pre><extension point="org.eclipse.ui.views"> | |
| <img SRC="tag_a.jpg" height=13 width=24 align=CENTER> <category | |
| id="org.eclipse.ui.article" | |
| name="Article"> | |
| </category> | |
| <view id="org.eclipse.ui.articles.views.wordview" | |
| name="Word View" | |
| icon="icons\view.gif" | |
| <img SRC="tag_b.jpg" height=13 width=24 align=CENTER> category="org.eclipse.ui.article" | |
| class="org.eclipse.ui.articles.views.WordView"/> | |
| </extension></pre> | |
| <h3> | |
| Defining a View Class for the Extension within the Plug-in</h3> | |
| Now we need to define a view class for the Word view. For simplicity, | |
| we subclass ViewPart to create a WordView class (shown below), and then | |
| implement <tt>setFocus</tt> and <tt>createPartControl</tt>. The implementation | |
| of this class is discussed after the code. | |
| <pre>public class WordView extends ViewPart | |
| { | |
| WordFile input; | |
| ListViewer viewer; | |
| Action addItemAction, deleteItemAction, selectAllAction; | |
| IMemento memento; | |
| | |
| /** | |
| * Constructor | |
| */ | |
| <img SRC="tag_a.jpg" height=13 width=24 align=CENTER> public WordView() { | |
| super(); | |
| input = new WordFile(new File("list.lst")); | |
| } | |
| /** | |
| * @see IViewPart.init(IViewSite) | |
| */ | |
| <img SRC="tag_b.jpg" height=13 width=24 align=CENTER> public void init(IViewSite site) throws PartInitException { | |
| super.init(site); | |
| // Normally we might do other stuff here. | |
| } | |
| /** | |
| * @see IWorkbenchPart#createPartControl(Composite) | |
| */ | |
| <img SRC="tag_c.jpg" height=13 width=24 align=CENTER> public void createPartControl(Composite parent) { | |
| // Create viewer. | |
| <img SRC="tag_d.jpg" height=13 width=24 align=CENTER> viewer = new ListViewer(parent); | |
| viewer.setContentProvider(new WordContentProvider()); | |
| viewer.setLabelProvider(new LabelProvider()); | |
| viewer.setInput(input); | |
| // Create menu and toolbars. | |
| <img SRC="tag_e.jpg" height=13 width=24 align=CENTER> createActions(); | |
| createMenu(); | |
| createToolbar(); | |
| createContextMenu(); | |
| hookGlobalActions(); | |
| | |
| // Restore state from the previous session. | |
| <img SRC="tag_f.jpg" height=13 width=24 align=CENTER> restoreState(); | |
| } | |
| | |
| /** | |
| * @see WorkbenchPart#setFocus() | |
| */ | |
| public void setFocus() { | |
| viewer.getControl().setFocus(); | |
| }</pre> | |
| In the <tt>WordView</tt> constructor, the model, a <tt>WordFile</tt>, is | |
| created (<img SRC="tag_a.jpg" height=13 width=24 align=ABSCENTER>). | |
| The actual shape of the model is somewhat irrelevant, as we wish to focus | |
| on the details of view creation, and the appropriate model will vary with | |
| the problem domain. To be brief, a <tt>WordFile</tt> is simply a | |
| list of words which are stored in a file. The storage location of | |
| the <tt>WordFile</tt> is passed to the constructor. If this file | |
| exists the <tt>WordFile</tt> will read it. Otherwise it will create | |
| the file. The <tt>WordFile</tt> also has simple methods to add, remove, | |
| and get all of the words. Each word is stored in a <tt>Word</tt> | |
| object, which is just a wrapper for a <tt>String</tt>. The Word and | |
| WordFile classes are supplied with the source of this article. | |
| <p>After the view is instantiated, the <tt>IViewPart.init</tt> method is | |
| called with an <tt>IViewSite</tt> (<img SRC="tag_b.jpg" height=13 width=24 align=ABSCENTER>). | |
| The site is the primary interface between the view part and the outside | |
| world. Given the site, you can access the view menu, toolbar, status | |
| line, containing page, containing window, shell, etc. In the code | |
| above we simply call the superclass, ViewPart, where the site is stored | |
| in an instance variable. It can be retrieved at any time by calling | |
| getViewSite(). | |
| <p>The <tt>createPartControl</tt> method (<img SRC="tag_c.jpg" height=13 width=24 align=ABSCENTER>) | |
| is called to create an SWT Control for the <tt>WordFile</tt> model. | |
| The creation of this presentation can be done with raw SWT widgets. | |
| However, the platform UI contains a class framework, known as JFace, which | |
| provides a viewer hierarchy for list, tree, table, and text widgets. | |
| A <i>viewer</i> is a wrapper for an SWT control, adding a model based interface | |
| to it. If you need to display a simple list model, tree model, | |
| or table model, use a viewer. Otherwise, it is probably easier to | |
| use raw SWT widgets. In the Word view the model is a simple list | |
| of words, so a ListViewer is used for the presentation. | |
| <p>On the first line of <tt>createPartControl</tt> the <tt>ListViewer</tt> | |
| is created (<img SRC="tag_d.jpg" height=13 width=24 align=ABSCENTER>). | |
| The constructor chosen here automatically creates an SWT List control in | |
| the parent. Then a <i>content provider</i> is defined. A content | |
| provider is an adapter for a domain specific model, wrapping it in an abstract | |
| interface which the viewer invokes to get the model root and its children. | |
| If the model (the <tt>WordFile</tt>) changes, the content provider will | |
| also refresh the state of the <tt>ListViewer</tt> to make the changes visible. | |
| A <i>label provider</i> is also defined. This serves up a label and | |
| image for each object which is supplied by the content provider. | |
| And finally, we set the input for the <tt>ListViewer</tt>. The input | |
| is just the | |
| <tt>WordFile</tt> created in the <tt>WordView</tt> constructor. | |
| <p>The <tt>createPartControl</tt> method also contains several method calls for | |
| the creation of a menu, toolbar, context menu, and global actions (<img SRC="tag_e.jpg" height=13 width=24 align=ABSCENTER>). | |
| These methods will be examined in later sections. For now, it is sufficient | |
| to say that each view has a local menu and toolbar which appear in the view | |
| title area. The view itself may contribute menu and toolbar items to these | |
| areas. The view may define a context menu (pop-up menu) for each control | |
| it creates, or hook a global action in the parent window. A <i>global | |
| action</i> refers to those actions in the window menu and toolbar which are | |
| always visible, but delegate their implementation to the active part. | |
| For instance, the Cut, Copy and Paste actions are always visible in the Edit | |
| menu. If invoked, their implementation is delegated to the active part. | |
| The last line (<img SRC="tag_f.jpg" height=13 width=24 align=ABSCENTER>) relates | |
| to state persistence between sessions. We will examine the code for each | |
| of these features in later sections. | |
| <p>Once the Word view has been declared and compiled we can test the results. | |
| To do this, invoke Perspective > Show View > Other, and then select the | |
| Word View in the Show View dialog. The Word view will appear in the | |
| Resource perspective, as shown below. | |
| <center> | |
| <p><img SRC="WordView.jpg" height=397 width=534></center> | |
| <h2> | |
| Menus and Toolbars</h2> | |
| As the Word view exists now it isn't very useful. We can browse a | |
| list of words, but we can't modify that list. In this section we'll | |
| add some menu and toolbar items to the view so the user can add and delete | |
| words within the list. | |
| <p>But first, some background information. Each view has a local | |
| menu and toolbar. The toolbar is located to the right of the view | |
| caption. The menu is initially invisible, but becomes visible if | |
| you click on the menu button, which is just to the left of the close button. | |
| For instance, here is a snapshot of the Word view with the menu and toolbar | |
| we are about to define. | |
| <center> | |
| <p><img SRC="menus.jpg" height=247 width=264></center> | |
| <p>The menu and toolbar controls for a view are initially empty and invisible. | |
| As the view adds items to each, the menu or toolbar will become visible. | |
| A view can also contribute items to the status line which appears at the | |
| bottom of the parent window. Together, the local menu, toolbar and | |
| status line are known as the <i>action bars</i>. | |
| <p>In code, access to the action bars is provided by the view site. | |
| The site is the primary interface between the view part and the outside | |
| world. If your view is a subclass of ViewPart, the site can be accessed | |
| by calling ViewPart.getViewSite. If not, then you must manage your | |
| own storage and retrieval of the site, which is passed to the view in the | |
| IViewPart.init method. Given the site, you can call <tt>getActionBars().getMenuManager</tt> | |
| to get an <tt>IMenuManager</tt>, or getActionBars().getToolBarManager() | |
| to get an IToolBarManager. | |
| <p>The IMenuManager and IToolBarManager interfaces are a JFace abstraction. | |
| They wrap an SWT <tt>Menu</tt> or <tt>Toolbar</tt> object so that you, | |
| the developer, can think in terms of action and action contribution. An | |
| <i>action</i> | |
| represents a command which can be triggered by the user from a menu or | |
| toolbar. It has a run method, plus other methods which return the | |
| menu or tool item presentation (text, image, etc.). In JFace, you | |
| create a menu or toolbar by adding instances of org.eclipse.jface.action.IAction | |
| to an IMenuManager or IToolBarManager. | |
| <h3> | |
| Defining our Actions</h3> | |
| We will contribute an "Add..." and "Delete" action to the Word view toolbar, | |
| and a "Select All" action to the view menu. In general, the initial | |
| actions within a view are contributed using Java™ code, and other plugin | |
| developers extend the menus and toolbars of a view using XML. | |
| <p>In the WordView class, the actions are created in the createActions | |
| method (shown below), which is called from createPartControl. For | |
| simplicity, each action is defined as anonymous subclass (<img SRC="tag_a.jpg" height=13 width=24 align=ABSCENTER>) | |
| of <tt>org.eclipse.jface.actions.Action</tt>. We override the run | |
| method (<img SRC="tag_b.jpg" height=13 width=24 align=ABSCENTER>) to implement | |
| the actual behavior of the action. | |
| <pre> public void createActions() { | |
| <img SRC="tag_a.jpg" height=13 width=24 align=CENTER> addItemAction = new Action("Add...") { | |
| <img SRC="tag_b.jpg" height=13 width=24 align=CENTER> public void run() { | |
| addItem(); | |
| } | |
| }; | |
| <img SRC="tag_c.jpg" height=13 width=24 align=CENTER> addItemAction.setImageDescriptor(getImageDescriptor("add.gif"));</pre> | |
| <pre> deleteItemAction = new Action("Delete") { | |
| public void run() { | |
| deleteItem(); | |
| } | |
| }; | |
| deleteItemAction.setImageDescriptor(getImageDescriptor("delete.gif"));</pre> | |
| <pre> selectAllAction = new Action("Select All") { | |
| public void run() { | |
| selectAll(); | |
| } | |
| }; | |
| | |
| // Add selection listener. | |
| <img SRC="tag_d.jpg" height=13 width=24 align=CENTER> viewer.addSelectionChangedListener(new ISelectionChangedListener() { | |
| public void selectionChanged(SelectionChangedEvent event) { | |
| updateActionEnablement(); | |
| } | |
| }); | |
| }</pre> | |
| The label of each action is defined in the action constructor (<img SRC="tag_a.jpg" height=13 width=24 align=ABSCENTER>). | |
| In addition, an image is defined for the "Add..." and "Delete" actions | |
| (<img SRC="tag_c.jpg" height=13 width=24 align=ABSCENTER>), since they | |
| will appear in the toolbar. These images are defined by invoking | |
| getImageDescriptor (shown below), a method which returns an image descriptor | |
| for a file stored in the plugin directory. Within this method the | |
| ViewsPlugin object is retrieved, the install URL (plugin directory) is | |
| determined, and then an ImageDescriptor is created for a path based on | |
| this URL. | |
| <pre> /** | |
| * Returns the image descriptor with the given relative path. | |
| */ | |
| private ImageDescriptor getImageDescriptor(String relativePath) { | |
| String iconPath = "icons/"; | |
| try { | |
| ViewsPlugin plugin = ViewsPlugin.getDefault(); | |
| URL installURL = plugin.getDescriptor().getInstallURL(); | |
| URL url = new URL(installURL, iconPath + relativePath); | |
| return ImageDescriptor.createFromURL(url); | |
| } | |
| catch (MalformedURLException e) { | |
| // should not happen | |
| return ImageDescriptor.getMissingImageDescriptor(); | |
| } | |
| }</pre> | |
| <p><br>On the last line of createActions (<img SRC="tag_d.jpg" height=13 width=24 align=ABSCENTER>), | |
| a selection listener is created and added to the list viewer. In | |
| general, the enablement state of a menu or tool item should reflect the | |
| view selection. If a selection occurs in the Word view, the enablement | |
| state of each action will be updated in the updateActionEnablement method | |
| (shown below). | |
| <pre> private void updateActionEnablement() { | |
| IStructuredSelection sel = | |
| (IStructuredSelection)viewer.getSelection(); | |
| deleteItemAction.setEnabled(sel.size() > 0); | |
| }</pre> | |
| <p><br>The approach taken for action enablement in the Word view is just | |
| one way to go. It is also possible to imagine an implementation where | |
| each action is a selection listener, and will enable itself to reflect | |
| the selection. This approach would make the actions reusable beyond | |
| the context of the original view. | |
| <h3> | |
| Adding our Actions</h3> | |
| Once the actions have been created we can add them to the existing menu | |
| and toolbar for the view. In the WordView class, this is done in | |
| the createMenu and createToolbar methods (shown below), which are called | |
| from createPartControl. In each case the view site is used to access | |
| the menu and toolbar managers, and then actions are added. | |
| <pre> /** | |
| * Create menu. | |
| */ | |
| private void createMenu() { | |
| IMenuManager mgr = getViewSite().getActionBars().getMenuManager(); | |
| mgr.add(selectAllAction); | |
| } | |
| | |
| /** | |
| * Create toolbar. | |
| */ | |
| private void createToolbar() { | |
| IToolBarManager mgr = getViewSite().getActionBars().getToolBarManager(); | |
| mgr.add(addItemAction); | |
| mgr.add(deleteItemAction); | |
| }</pre> | |
| At this point the definition of the menu and toolbar are complete. | |
| If the Word view is opened in the workbench, the toolbar will be populated | |
| with the Add and Delete items. If you click the down arrow for the | |
| menu, you will see the Select All item there. | |
| <h2> | |
| <a NAME="Context Menus"></a>Context Menus</h2> | |
| In Eclipse, it is very common to expose and invoke actions from the context | |
| menu. For instance, if you press and release the right mouse button | |
| over an IFile in the Navigator, a context menu appears with standard file | |
| operations like Open, Rename, and Delete. Experienced users come | |
| to expect this, so in this section we will define a context menu for the | |
| Word view. | |
| <p>But first, some background info. One of the primary goals for | |
| the platform UI is extensibility. In fact, it is this extensibility | |
| which gives you the freedom to add new views, editors, perspectives, actions, | |
| etc., to the platform. Of course, extensibility is a two way street. | |
| While you may wish to extend the platform, others may wish to extend your | |
| view. It is common for one plug-in to add actions to the menu, toolbar, | |
| or context menu of a view from another plugin. | |
| <p>Support for context menu extension is accomplished by collaboration | |
| between the view and the platform. In contrast to the local menu | |
| and toolbar for each view, which are created by the platform and populated | |
| by the view, each context menu within a view must be created by the view | |
| itself. This is because the context menus within a view are beyond | |
| the scope of platform visibility, and there may be zero, one, or many context | |
| menus within the view. Once a context menu has been created, the | |
| view must explicitly register each context menu with the platform. | |
| This makes it available for extension. | |
| <p>A context menu can be created using the SWT | |
| <tt>Menu</tt> and <tt>MenuItem</tt> | |
| classes directly, or it can be created using the JFace | |
| <tt>MenuManager</tt> | |
| class. If you define a context menu using the MenuManager class, | |
| the platform can extend this menu with actions. However, if you define | |
| a context menu using the SWT Menu and MenuItem classes directly, this is | |
| not possible. | |
| <h3> | |
| Creating the Context Menu</h3> | |
| In the WordView class, the context menu is created in the createContextMenu | |
| method (shown below), which is called from createPartControl. | |
| Within this method a MenuManager is created (<img SRC="tag_a.jpg" height=13 width=24 align=ABSCENTER>), | |
| a menu is created (<img SRC="tag_b.jpg" height=13 width=24 align=ABSCENTER>), | |
| and then the menu manager is passed to the platform for extension with | |
| other actions (<img SRC="tag_c.jpg" height=13 width=24 align=ABSCENTER>). | |
| <pre> private void createContextMenu() { | |
| // Create menu manager. | |
| <img SRC="tag_a.jpg" height=13 width=24 align=CENTER> MenuManager menuMgr = new MenuManager(); | |
| menuMgr.setRemoveAllWhenShown(true); | |
| menuMgr.addMenuListener(new IMenuListener() { | |
| public void menuAboutToShow(IMenuManager mgr) { | |
| fillContextMenu(mgr); | |
| } | |
| }); | |
| | |
| // Create menu. | |
| <img SRC="tag_b.jpg" height=13 width=24 align=CENTER> Menu menu = menuMgr.createContextMenu(viewer.getControl()); | |
| viewer.getControl().setMenu(menu); | |
| | |
| // Register menu for extension. | |
| <img SRC="tag_c.jpg" height=13 width=24 align=CENTER> getSite().registerContextMenu(menuMgr, viewer); | |
| }</pre> | |
| In the first few lines of <tt>createContextMenu</tt> a new <tt>MenuManager</tt> | |
| is created (<img SRC="tag_a.jpg" height=13 width=24 align=ABSCENTER>), | |
| and it is configured for dynamic action population. In other words, | |
| w<font color="#000000">henever the menu is opened, the old actions will | |
| be removed, and new actions will be added to reflect the current selection. | |
| This behavior is a requirement for action extension, and if not implemented, | |
| the action extensions contributed by the platform will soon flood the context | |
| menu. To accomplish this, we invoke setRemoveAllWhenShown(true) | |
| to clear the menu when it opens. Then we add a menu listener to populate | |
| the menu when it opens. The menu listener just calls fillContextMenu, | |
| which will be discussed below.</font> | |
| <p>After creating the MenuManager, we can create the menu, and then register | |
| the menu manager with the site (<img SRC="tag_c.jpg" height=13 width=24 align=ABSCENTER>). | |
| Take a good look at the last line of createContextMenu. In this statement | |
| we pass the menu manager and the viewer to the site. In general, | |
| the platform will only add action extensions to a menu if it opens. | |
| At that time, the action extensions for the menu are determined by examining | |
| the menu id and view selection. So where is the menu id? To | |
| answer, we need to look at the definition of IWorkbenchPartSite.registerContextMenu. | |
| There are two registerContextMenu methods (shown below). If there | |
| is only one context menu in the view, we should call the first. The | |
| menu id will be derived from the part id to ensure consistency. So | |
| in this case, our menu id is "org.eclipse.ui.articles.views.wordview". | |
| If there is more than one context menu in the view, we should use the second, | |
| where the menu id is explicit. | |
| <pre> // Defined on IWorkbenchPartSite. | |
| public void registerContextMenu(MenuManager menuManager, | |
| ISelectionProvider selectionProvider); | |
| public void registerContextMenu(String menuId, MenuManager menuManager, | |
| ISelectionProvider selectionProvider);</pre> | |
| If the user opens our context menu, the fillContextMenu method will be | |
| called. This method adds the Add, Delete and SelectAll actions | |
| to the menu. It is important to notice that a <tt>GroupMarker</tt> | |
| is also added to the menu for "additions" (<img SRC="tag_a.jpg" height=13 width=24 align=ABSCENTER>). | |
| This group will be used as a target for action extension by other plug-ins. | |
| If it does not exist, all of the action extensions will appear at the end | |
| of the menu. | |
| <pre> private void fillContextMenu(IMenuManager mgr) { | |
| mgr.add(addItemAction); | |
| <img SRC="tag_a.jpg" height=13 width=24 align=CENTER> mgr.add(new GroupMarker(IWorkbenchActionConstants.MB_ADDITIONS)); | |
| mgr.add(deleteItemAction); | |
| mgr.add(new Separator()); | |
| mgr.add(selectAllAction); | |
| }</pre> | |
| <h3> | |
| Supporting Extension of the Context Menu</h3> | |
| In the last section extensibility was a prominent issue. If we want | |
| to support context menu extension in the Word view, or any other view, | |
| the following process should be followed. | |
| <br> | |
| <ol> | |
| <li> | |
| If you create a context menu, publish the menu id in a public interface | |
| which other plug-in developers can reference.</li> | |
| <li> | |
| Add a GroupMarker with <i>id == IWorkbenchActionConstants.MB_ADDITIONS</i> | |
| to each context menu. Other plug-in developers will use this as a | |
| target for menu extensions.</li> | |
| <li> | |
| If you have additional groups in the menu, publish each id.</li> | |
| <li> | |
| Register each context menu with your IWorkbenchPartSite. If you don't | |
| do this the workbench can't extend it.</li> | |
| <li> | |
| Define an IActionFilter for each of the objects in your model. Within | |
| this filter, publish the filter attributes. This allows for more | |
| accurate targeting of menu extensions.</li> | |
| </ol> | |
| <p><br>The first couple of items have already been discussed. At | |
| this point we will focus on the last point: define an IActionFilter for | |
| each object in your model. | |
| <h3> | |
| The Simple Case of Action Extension</h3> | |
| An action can be added to a context menu by defining an extension for the | |
| <tt>org.eclipse.ui.popupMenus</tt> | |
| extension point in the workbench plug-in. For instance, the following | |
| XML can be used to add the "Word Action" to the context menu for Word objects. | |
| Without going into too many details, the target for the action is specified | |
| by declaring an <tt>objectContribution </tt>(<img SRC="tag_a.jpg" height=13 width=24 align=ABSCENTER>) | |
| with an <tt>objectClass</tt> of <tt>org.eclipse.ui.articles.views.Word | |
| </tt>(<img SRC="tag_b.jpg" height=13 width=24 align=ABSCENTER>). | |
| Once this declaration is made, the action will appear within the context | |
| menu for any Word. | |
| <pre><extension point="org.eclipse.ui.popupMenus"> | |
| <img SRC="tag_a.jpg" height=13 width=24 align=CENTER> <objectContribution | |
| id="org.eclipse.ui.articles.pm1" | |
| <img SRC="tag_b.jpg" height=13 width=24 align=CENTER> objectClass="org.eclipse.ui.articles.views.Word"> | |
| <action | |
| id="org.eclipse.ui.articles.a1" | |
| label="Word Action" | |
| menubarPath="additions" | |
| class="org.eclipse.ui.articles.views.WordActionDelegate"/> | |
| </objectContribution> | |
| </extension></pre> | |
| The target for an <tt>objectContribution</tt> can be defined using the | |
| <tt>objectClass</tt> | |
| attribute and an optional <tt>nameFilter</tt> attribute. The <tt>nameFilter</tt> | |
| is used to specify objects with a specific label. Together, these | |
| attributes provide reasonable accuracy when targeting objects. | |
| <h3> | |
| The Problem Case of Action Extension</h3> | |
| In some situations the <tt>objectClass</tt> and <tt>nameFilter</tt> attributes | |
| are not enough to describe the intended target object. For instance, | |
| what if we wanted to add an action to a .java file which is read only, | |
| or a project which has the java nature? Luckily, the platform can | |
| help. | |
| <p>Within an <tt>objectContribution</tt>, a plug-in developer may define | |
| <tt>filter</tt> | |
| sub elements. Each <tt>filter</tt> describes one attribute of the target | |
| object using a name value pair. For instance, the following XML can | |
| be used to target an action at any Word object with <i>name = "Blue"</i>. | |
| Notice how the <tt>filter</tt> sub element (<img SRC="tag_a.jpg" height=13 width=24 align=ABSCENTER>) | |
| is used to define an attribute value pair. | |
| <pre><extension point="org.eclipse.ui.popupMenus"> | |
| <objectContribution | |
| id="org.eclipse.ui.articles.pm2" | |
| objectClass="org.eclipse.ui.articles.views.Word"> | |
| <img SRC="tag_a.jpg" height=13 width=24 align=CENTER> <filter name="name" value="Blue"/> | |
| <action | |
| id="org.eclipse.ui.articles.a2" | |
| label="Blue Action" | |
| menubarPath="additions" | |
| class="org.eclipse.ui.articles.views.BlueActionDelegate"/> | |
| </objectContribution> | |
| </extension></pre> | |
| Now let's look at the implementation of the <tt>filter</tt> sub element. | |
| The attributes for an object are type specific, and beyond the domain of | |
| the workbench itself, so the workbench will delegate filtering at this | |
| level to the selection itself. To do this, the platform will test | |
| to see if the selected object implements an <tt>org.eclipse.ui.IActionFilter</tt>. | |
| This is a type specific filtering strategy. If the selection does | |
| not implement <tt>IActionFilter</tt>, the platform will ask the selection | |
| for a filter using the IAdaptable mechanism defined in org.eclipse.core.runtime. | |
| If a filter is found, the platform will pass each name value pair to the | |
| filter to determine if it matches the state of the selected object. | |
| If so, or there is no IActionFilter, the action will be added to the context | |
| menu for the object. | |
| <p>In order to support the XML above, we need to define an IActionFilter | |
| for the Word class. To do this, we define a class called WordActionFilter | |
| (shown below), which simply implements the IActionFilter interface (<img SRC="tag_a.jpg" height=13 width=24 align=ABSCENTER>). | |
| This class is defined as a singleton for memory efficiency (<img SRC="tag_c.jpg" height=13 width=24 align=ABSCENTER>). | |
| It also publishes the filter attributes for the Word class (<img SRC="tag_b.jpg" height=13 width=24 align=ABSCENTER>), | |
| so that they can be referenced outside the plugin. | |
| <pre><img SRC="tag_a.jpg" height=13 width=24 align=TEXTTOP> public class WordActionFilter implements IActionFilter { | |
| <img SRC="tag_b.jpg" height=13 width=24 align=CENTER> public static final String NAME = "name"; | |
| private static WordActionFilter singleton; | |
| <img SRC="tag_c.jpg" height=13 width=24 align=TEXTTOP> public static WordActionFilter getSingleton() { | |
| if (singleton == null) | |
| singleton = new WordActionFilter(); | |
| return singleton; | |
| } | |
| | |
| /** | |
| * @see IActionFilter#testAttribute(Object, String, String) | |
| */ | |
| public boolean testAttribute(Object target, String name, String value) { | |
| if (name.equals(NAME)) { | |
| Word le = (Word)target; | |
| return value.equals(le.toString()); | |
| } | |
| return false; | |
| } | |
| }</pre> | |
| The action filter for the Word class will be exposed using the IAdaptable | |
| mechanism. To do this, we need to implement the IAdaptable interface | |
| on our Word object, and return the WordActionFilter singleton if the platform | |
| asks for an IActionFilter. The implementation of getAdapter is shown | |
| below. | |
| <pre> public Object getAdapter(Class adapter) { | |
| if (adapter == IActionFilter.class) { | |
| return WordActionFilter.getSingleton(); | |
| } | |
| return null; | |
| }</pre> | |
| At this point the WordActionFilter is complete. If we open the Word | |
| view and select a Word with <i>name = Blue</i>, the "Blue Action" will | |
| appear in the context menu. Stepping back from this example, the | |
| WordActionFilter is actually fairly useless. However, it does illustrate | |
| how precise action extension targeting should be supported by a model within | |
| a view (or editor). | |
| <p>In the platform, action filters already exist for markers, resources, | |
| and projects. The attribute names for each are defined in IMarkerActionFilter, | |
| IResourceActionFilter, and IProjectActionFilter within org.eclipse.ui, | |
| so other plug-in developers can reference them easily. If we look | |
| at an existing, production quality filter like IResourceActionFilter we | |
| can see that it contains matching attributes for NAME, EXTENSION, PATH, | |
| READ_ONLY and PROJECT_NATURE. You should define attributes which | |
| reflect your own model and permit precise action targeting. | |
| <h2> | |
| Integration with the Window Menu and Toolbar</h2> | |
| In the workbench window menu there are a number of pre-existing actions | |
| which target the active part (as indicated by a shaded title bar) . | |
| For instance, if the Delete action within the Edit menu is invoked when | |
| the Navigator view is active, the Delete implementation is delegated to | |
| the Navigator view. If the Tasks view is active the Delete implementation | |
| is delegated to that view. The Delete action is just one of many | |
| actions, known as <i>global actions</i>, which are always visible within | |
| the window menu and, when invoked, delegate their implementation to the | |
| active part. | |
| <p>In the Word view there are three actions (Add, Delete and Select All). | |
| The complete set of global actions is declared in IWorkbenchActionConstants.GLOBAL_ACTIONS | |
| (shown below). A quick comparison will demonstrate that there is | |
| no semantic equivalent to Add, but the Delete (<img SRC="tag_a.jpg" height=13 width=24 align=ABSCENTER>) | |
| and Select all actions (<img SRC="tag_b.jpg" height=13 width=24 align=ABSCENTER>) | |
| are represented within the global actions. So the Word view will | |
| hook both of these actions. | |
| <pre> /** | |
| * From IWorkbenchActionConstants. | |
| * Standard global actions in a workbench window. | |
| */ | |
| public static final String [] GLOBAL_ACTIONS = { | |
| UNDO, | |
| REDO, | |
| CUT, | |
| COPY, | |
| PASTE, | |
| PRINT, | |
| <img SRC="tag_a.jpg" height=13 width=24 align=CENTER><font color="#000000"><b> </b>DELETE, | |
| FIND, | |
| <img SRC="tag_b.jpg" height=13 width=24 align=CENTER> SELECT_ALL, | |
| </font> BOOKMARK | |
| }; | |
| }</pre> | |
| In the WordView class, the global actions are hooked within hookGlobalActions | |
| (shown below), which is called from createPartControl. Within this | |
| method, the Word view retrieves the action bars from the workbench part | |
| site (<img SRC="tag_a.jpg" height=13 width=24 align=ABSCENTER>) and then | |
| calls setGlobalActionHandler for each action (<img SRC="tag_b.jpg" height=13 width=24 align=ABSCENTER>). | |
| This establishes a link between the global action in the window and the | |
| implementation within the view. | |
| <pre> private void hookGlobalActions() { | |
| <img SRC="tag_a.jpg" height=13 width=24 align=CENTER> IActionBars bars = getViewSite().getActionBars(); | |
| <img SRC="tag_b.jpg" height=13 width=24 align=CENTER> bars.setGlobalActionHandler(IWorkbenchActionConstants.SELECT_ALL, selectAllAction); | |
| bars.setGlobalActionHandler(IWorkbenchActionConstants.DELETE, deleteItemAction); | |
| <img SRC="tag_c.jpg" height=13 width=24 align=CENTER> viewer.getControl().addKeyListener(new KeyAdapter() { | |
| public void keyPressed(KeyEvent event) { | |
| if (event.character == SWT.DEL && | |
| event.stateMask == 0 && | |
| deleteItemAction.isEnabled()) | |
| { | |
| deleteItemAction.run(); | |
| } | |
| } | |
| }); | |
| }</pre> | |
| On the last line of hookGlobalActions (<img SRC="tag_c.jpg" height=13 width=24 align=ABSCENTER>) | |
| we add a key listener to the viewer control. If the "delete" key | |
| is pressed, the Delete action should run. In the case of every other | |
| action except Delete, the accelerator is defined and implemented by the | |
| workbench, so there is no need for a key listener. In the case of | |
| Delete, however, a key listener must be defined in the part. This | |
| extra work is required because the platform cannot define an accelerator | |
| containing the delete key. In doing so, it would break any text editors | |
| where the "delete" key has two different behaviors: delete the selection, | |
| and delete the next character. | |
| <p>Registration of the global action handlers is complete. If the | |
| Select All or Delete action in the window is invoked, and the Word view | |
| is active, the corresponding handler within the Word view will be invoked. | |
| The approach taken here can be used for other actions, and you are encouraged | |
| to do so. For instance, most editors provide a handler for all of | |
| the global actions. The Navigator implements the Delete and Bookmark | |
| actions. The Tasks view implements the Delete and Select All actions. | |
| <h3> | |
| Can I add new actions to the window menu or toolbar?</h3> | |
| In general all view actions should be contributed to the local menu, toolbar, | |
| or context menu for a view. There is no way to add view specific | |
| actions to a window menu or toolbar. The justification for this is simple: | |
| the proximity of view actions to the view itself creates a stronger coupling | |
| between the two, and it also helps to reduce clutter on the window menu | |
| and toolbar. | |
| <h2> | |
| Integration with Other Views</h2> | |
| No view is an island. In most cases, a view co-exists with other | |
| views and selection within one view may affect the input of another. | |
| In this section we'll create a Listener view, which will listen for the | |
| selection of objects in the Word view. If a Word object is selected, | |
| the Listener view will display the word attributes. This behavior | |
| is similar to the existing Properties view in the workbench standard components. | |
| <p>To start out, we need to create a new Listener view. We've already | |
| done this twice, so let's skip the declaration within the plugin.xml and | |
| concentrate on the ListenerView class (shown below). This class is | |
| very similar to LabelView. In createPartControl we create a simple | |
| SWT Label (<img SRC="tag_a.jpg" height=13 width=24 align=ABSCENTER>), where | |
| we will display the word attributes. | |
| <pre>public class ListenerView extends ViewPart | |
| implements ISelectionListener | |
| { | |
| private Label label; | |
| public ListenerView() { | |
| super(); | |
| } | |
| public void setFocus() { | |
| label.setFocus(); | |
| } | |
| <img SRC="tag_a.jpg" height=13 width=24 align=CENTER> public void createPartControl(Composite parent) { | |
| label = new Label(parent, 0); | |
| label.setText("Hello World"); | |
| <img SRC="tag_b.jpg" height=13 width=24 align=CENTER> getViewSite().getPage().addSelectionListener(this); | |
| } | |
| /** | |
| * @see ISelectionListener#selectionChanged(IWorkbenchPart, ISelection) | |
| */ | |
| <img SRC="tag_c.jpg" height=13 width=24 align=CENTER> public void selectionChanged(IWorkbenchPart part, ISelection selection) { | |
| if (selection instanceof IStructuredSelection) { | |
| Object first = ((IStructuredSelection)selection).getFirstElement(); | |
| if (first instanceof Word) { | |
| label.setText(((Word)first).toString()); | |
| } | |
| } | |
| } | |
| }</pre> | |
| Things get interesting on the last line of createPartControl (<img SRC="tag_b.jpg" height=13 width=24 align=ABSCENTER>). | |
| The Listener view exists in a page with other views. If a selection | |
| is made in any of those views, the platform will forward the selection | |
| event to all interested parties. We are an interested party. | |
| To register our interest, we get the site, get the page, and add the ListenerView | |
| as a selection listener. If a selection occurs within the page, the | |
| ListenerView.selectionChanged method will be called (<img SRC="tag_c.jpg" height=13 width=24 align=ABSCENTER>). | |
| Within this method, the selection is examined and, if it is a Word, the | |
| label text will be updated to reflect the Word name. | |
| <p>So far so good. The Listener view is ready to receive selection | |
| events. However, we have a problem: the Word view doesn't publish | |
| any selection events. | |
| <p>To reconcile our problem we add one line of code to the Word view (<img SRC="tag_d.jpg" height=13 width=24 align=ABSCENTER>) | |
| Within the createPartControl method, a selection provider is passed to | |
| the site. Fortunately, the viewer itself is an ISelectionProvider, | |
| so it is very easy to define the selection provider for the view. | |
| When the Word view is active (as indicated by shading in the title bar) | |
| the platform will redirect any selection events fired from viewer to selection | |
| listeners within the page. | |
| <pre> public void createPartControl(Composite parent) { | |
| // Create viewer. | |
| viewer = new ListViewer(parent); | |
| viewer.setContentProvider(new ListContentProvider()); | |
| viewer.setLabelProvider(new LabelProvider()); | |
| viewer.setInput(input); | |
| <img SRC="tag_d.jpg" height=13 width=24 align=CENTER> getSite().setSelectionProvider(viewer); | |
| // Create menu and toolbars. | |
| createActions(); | |
| createMenu(); | |
| createToolbar(); | |
| createContextMenu(); | |
| hookGlobalActions(); | |
| | |
| // Restore state from the previous session. | |
| restoreState(); | |
| }</pre> | |
| Now we can try out the Listener view. Open up the Word view and the | |
| Listener view. Add a word to the Word view and then select it. | |
| The name of the Word will be displayed in the Listener view. A snapshot | |
| of the result is shown below. | |
| <center> | |
| <p><img SRC="ListenerView.jpg" height=397 width=534></center> | |
| <p>The linking approach demonstrated here can be described as <i>activation | |
| linking</i>. In activation linking, the listener receives selection | |
| from the active part. The benefit of this approach is that there | |
| is a loose coupling between the Listener view and other views within the | |
| same page. If we were to introduce another view, similar to the Word | |
| view, which published selection events for objects of type Word, these | |
| objects would also appear in the Listener view. This makes it very | |
| easy to add, remove, or replace the views in the workbench while maintaining | |
| good integration between views. | |
| <p>In contrast to activation linking, a view may also implement <i>explicit | |
| linking</i>. In explicit linking, the listener tracks selection within | |
| a specific source view, which is usually discovered by calling <tt>IWorkbenchPage.findView(String | |
| id)</tt>. There are two drawbacks to this approach. First, | |
| the listener will be disabled if the specific source view is nonexistent | |
| or closed by the user, and second, the listener will never work with additional | |
| views added by the user, or another plug-in. For this reason, the | |
| use of explicit linking is discouraged. | |
| <h2> | |
| Integration with the WorkbenchPage Input</h2> | |
| In a large, real world project the workspace may contain hundreds or thousands | |
| of resources. These resources are split among many projects, containing | |
| many folders, containing many files. It's a tree, and each subtree | |
| within the whole defines a physical subset of information. To avoid | |
| the information overload which can sometimes occur by looking at the whole, | |
| the user can "open a perspective" on any resource subtree within the workspace. | |
| This is done by selecting a resource in the Navigator view and invoking | |
| "Open Perspective". The result is a new workbench page where only | |
| the children of the subtree root are visible. This subtree root is | |
| known as the <i>input</i>. | |
| <p>In practice, the visibility of resources within a page is implemented | |
| through collaboration between the page and the views within that page. | |
| The page itself is just a visual container for views and editors. | |
| It doesn't provide any presentation for resources. That is delegated | |
| to the parts within the page. The hand off usually occurs during | |
| part creation. In the early stages of part lifecycle a part can obtain | |
| a handle to the containing <tt>IWorkbenchPage,</tt> and from this it can | |
| call <tt>IWorkbenchPage.getInput</tt>. The result can be used as | |
| the initial input for the view. For instance, if a new perspective | |
| containing the Navigator is opened, the Navigator will use the page input | |
| as its own input. The Packages view does the same. | |
| <p>The strategy should be adopted by any view which displays resources | |
| within the workspace. This will ensure a consistent navigational | |
| paradigm for users within the platform. | |
| <h2> | |
| State Persistence</h2> | |
| One of the primary goals for the platform UI is to provide efficient interaction | |
| with the workspace. In the platform this is promoted by saving the | |
| state of the workbench when a session ends. When a new session is | |
| started the state of the workbench is recreated. The state of each | |
| window, page, view and editor is persisted between sessions, reducing the | |
| time required for the user to get back to work. In this section we'll | |
| examine how the state of the Word view is saved. | |
| <p>Within any view there are at least two elements of state: the model | |
| itself and model presentation (widget state). In our Word view, the model | |
| is stored as a file in the file system, so it is a non issue. However, | |
| the widget state should be saved. To do this we need to implement | |
| the IViewPart.init and IViewPart.saveState methods (shown below) in WordView. | |
| <pre> public void init(IViewSite site,IMemento memento) throws PartInitException; | |
| public void saveState(IMemento memento);</pre> | |
| But first, some background info. When the workbench is closed, the | |
| IViewPart.saveState method is called on every view. An IMemento is | |
| passed to the saveState method. This is a structured, hierarchical | |
| object where you can store integers, floats, Strings, and other IMementos. | |
| The platform will store the IMemento data in a file called <tt>workbench.xml</tt> | |
| within the <tt>org.eclipse.ui</tt> metadata directory. When a new | |
| session is started, the platform will read this file, recreate each window, | |
| page, and view, and then pass an IMemento to each view in the init method. | |
| The view can use this to recreate the state of the previous session. | |
| <p>Now we can do some coding. We will implement the saveState method | |
| first (shown below). Within this method a memento is created for | |
| the selection (<img SRC="tag_a.jpg" height=13 width=24 align=ABSCENTER>) | |
| and then one item is added to it for every word which is selected (<img SRC="tag_b.jpg" height=13 width=24 align=ABSCENTER>). | |
| In our code, the word string is used to identify the word. This may | |
| be an inaccurate way to identify words (two words may have the same string), | |
| but this is only an article. You should develop a more accurate strategy | |
| which reflects your own model. | |
| <pre> public void saveState(IMemento memento){ | |
| IStructuredSelection sel = (IStructuredSelection)viewer.getSelection(); | |
| if (sel.isEmpty()) | |
| return; | |
| <img SRC="tag_a.jpg" height=13 width=24 align=CENTER> memento = memento.createChild("selection"); | |
| Iterator iter = sel.iterator(); | |
| while (iter.hasNext()) { | |
| Word word = (Word)iter.next(); | |
| <img SRC="tag_b.jpg" height=13 width=24 align=CENTER> memento.createChild("descriptor", word.toString()); | |
| } | |
| }</pre> | |
| Next we implement the init method. When a new session is started, | |
| the init method will be called just after the part is instantiated, but | |
| before the createPartControl method is called. In our code all of | |
| the information within the memento must be restored to the control, so | |
| we have no choice but to hold on to the memento until createPartControl | |
| is called. | |
| <pre> public void init(IViewSite site,IMemento memento) throws PartInitException { | |
| init(site); | |
| this.memento = memento; | |
| }</pre> | |
| Within createPartControl, the restoreState method is called. Within | |
| restoreState the selection memento is retrieved (<img SRC="tag_a.jpg" height=13 width=24 align=ABSCENTER>), | |
| and each descriptor within it is mapped to a real Word (<img SRC="tag_b.jpg" height=13 width=24 align=ABSCENTER>). | |
| The resulting array of words is used to restore the selection for the viewer | |
| (<img SRC="tag_c.jpg" height=13 width=24 align=ABSCENTER>). | |
| <pre> private void restoreState() { | |
| if (memento == null) | |
| return; | |
| <img SRC="tag_a.jpg" height=13 width=24 align=CENTER> memento = memento.getChild("selection"); | |
| if (memento != null) { | |
| <img SRC="tag_b.jpg" height=13 width=24 align=CENTER> IMemento descriptors [] = memento.getChildren("descriptor"); | |
| if (descriptors.length > 0) { | |
| ArrayList objList = new ArrayList(descriptors.length); | |
| for (int nX = 0; nX < descriptors.length; nX ++) { | |
| String id = descriptors[nX].getID(); | |
| Word word = input.find(id); | |
| if (word != null) | |
| objList.add(word); | |
| } | |
| <img SRC="tag_c.jpg" height=13 width=24 align=CENTER> viewer.setSelection(new StructuredSelection(objList)); | |
| } | |
| } | |
| memento = null; | |
| updateActionEnablement(); | |
| }</pre> | |
| You may notice certain limitations in the IMemento interface. For | |
| instance, it can only be used to store integers, floats, and Strings. | |
| This limitation is a reflection of the persistence requirements in the | |
| platform UI. | |
| <br> | |
| <ol> | |
| <li> | |
| Certain objects need to be saved and restored across platform sessions.</li> | |
| <li> | |
| When an object is restored, an appropriate class for an object might not | |
| be available. It must be possible to skip an object in this case.</li> | |
| <li> | |
| When an object is restored, the appropriate class for the object may be | |
| different from the one when the object was originally saved. If so, the | |
| new class should still be able to read the old form of the data.</li> | |
| </ol> | |
| <p><br>Java serialization could be used for persistence. However, | |
| serialization is only appropriate for RMI and light-weight persistence | |
| (the archival of an object for use in a later invocation of the same program). | |
| It is not considered robust for long term storage, where class names and | |
| structure may change. If any of these occur serialization will throw | |
| an exception. | |
| <h2> | |
| State Inheritance</h2> | |
| If we take a close look at the standard components in Eclipse an interesting | |
| pattern emerges. Most of them provide some degree of customization. | |
| For instance, in the Navigator view there are two actions in the menu, | |
| Sort and Filter, which can be used to sort or select the visible resources | |
| in the Navigator. Similar functionality exists within the Tasks view. | |
| In general, customization is a desirable feature. In this section | |
| we're not going to look at customization itself, as customization is very | |
| specific to a particular view and the underlying model. Instead, | |
| we'll look at some different strategies which you could use to share user | |
| preferences between views. | |
| <p>Let's start out with the assumption that the Word view has two sorting | |
| algorithms. The user may select one as the preferred algorithm. | |
| How do we share this preference with other views of the same type? | |
| <p><b>Approach #1: We don't.</b> | |
| <p>The initial value of the preference will be hard coded in some way, | |
| probably as a constant in the class declaration. If the user changes | |
| this preference in one view, it will not be reflected in other views. | |
| For instance, the Navigator preference for "Sort" is a local preference, | |
| and is not shared with other Navigator views. | |
| <p><b>Approach #2: We Use a Preference Page</b> | |
| <p>The initial value of the preference will be exposed in a preference | |
| page in the Workbench Preferences. If the user changes this preference, | |
| the new preference will apply to all instances of the view. For instance, | |
| the Navigator preference for "Link Navigator selection to active editor" | |
| is controlled by a check box in the Workbench Preferences. This option | |
| applies to all Navigator views. | |
| <p><b>Approach #3: Inheritance.</b> | |
| <p>The initial value of the preference will be defined in the plugin metadata. | |
| If a view is opened, it will read the plugin metadata to determine the | |
| preference. If the user changes the preference in one view, it will | |
| not affect other views, but it will be saved to the plugin metadata, so | |
| that any view created in the future will inherit the preference. | |
| For instance, if the user changes the sort preference in one Word view, | |
| it will have no affect on other Word views. However, if the user | |
| opens a new Word view it will inherit the most recent preference. | |
| <p>While each of these is a valid approach, the correct choice should reflect | |
| your own view and the underlying model. | |
| <h2> Summary</h2> | |
| In this article we have examined the design and implementation of two views, | |
| a simple Label view and a more comprehensive Word view. The features | |
| of your own view will probably vary greatly from what we have created. | |
| However, the integration of your view with the workbench will follow the | |
| same pattern demonstrated by these views. Further information on view implementation | |
| is available in the Platform Plug-in Developer Guide and in the javadoc | |
| for <tt>org.eclipse.ui</tt>. | |
| <p><small>Java and all Java-based trademarks and logos are trademarks or registered | |
| trademarks of Sun Microsystems, Inc. in the United States, other countries, or | |
| both.</small></p> | |
| </body> | |
| </html> |