Adding a view to a plug-in is as simple as using the org.eclipse.ui.views extension point, declaring a category for it to be listed in, and then declaring your view with some parameters. If you're feeling really ambitious, you can designate where this view should appear when loaded relative to other views, or even what perspective you want it to appear in. You can do this through the gui editor for the plugin.xml file, or you can do it manually.
In the included example, Example4Views, I've declared a plug-in with two views. The main plug-in class contains only the default information and nothing more. It contains only the boiler-plate code needed to allow the plug-in to run.
Looking at the plug-in.xml, however, we'll notice exactly what is detailed above. Whether you use the gui or look at the raw xml, what we have is an extension to org.eclipse.ui.views as well as one to org.eclipse.ui.perspectiveExtensions. For completeness, the xml for one view is listed below.
<extension point="org.eclipse.ui.views"> <category name="Sample Category" id="Example4Views"> </category> <view name="Table / List View" icon="icons/sample.gif" category="Example4Views" class="example4Views.views.TableView" id="example4Views.views.TableView"> </view> </extension> <extension point="org.eclipse.ui.perspectiveExtensions"> <perspectiveExtension targetID="org.eclipse.ui.resourcePerspective"> <view ratio="0.5" relative="org.eclipse.ui.views.TaskList" relationship="right" id="example4Views.views.TableView"> </view> </perspectiveExtension> </extension>
Creating the control for the view involves any amount of SWT layout coding that you find desirable. For our first two examples, TableView and TreeView the SWT is minimal. All we do there is create our JFace Viewer, which helps us organize and display our data. For those two classes, the actual SWT code is as follows:
// TreeView.java viewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); //TableView.java viewer = new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
The SWTView.java> has a much more SWT-oriented createPartControl method, and it may provide as a slight jumping off point for investigating SWT code, but it is really just a few simple examples.
NOTE: Having Views with ample SWT controls is not customary, and is often frowned upon in view implementations. However, it is possible to do it and so it was included as an example.
The content provider for a viewer helps organize the data from some underlying model and figure out how to display it in the viewer. The content provider is a JFace element and so rests a level above the actual list, table, or tree lying underneath. It provides APIs that will quickly allow you to change the SWT element's data without dealing with the annoyances of those underlying elements.
The TableViewer only requires an IStructuredContentProvider, which only requires three methods. I think the code is rather self-explanatory here, and there's not much more I can say on it.
inputChanged is used to change the top level of the model, to designate a new model, etc. In the two examples mentioned above, we use the viewSite as our input, but that's completely arbitrary amd just serves as an example. For a real model (as opposed to the strings example), the root element might be something like an xml Document, or whatever model your view intends to display data from.
For Completeness:
class ViewContentProvider implements IStructuredContentProvider { public void inputChanged(Viewer v, Object oldInput, Object newInput) { } public void dispose() { } public Object[] getElements(Object parent) { ArrayList list = new ArrayList(); list.addAll(Arrays.asList(new String[] {"One", "Two", "Three"})); list.add(new Integer(5)); return list.toArray(); } }
The TreeViewer requires an ITreeContentProvider, and its associated methods. This implementation also uses the viewSite as its root, however any calls requesting the children of the root are deferred to the proxy element, the invisibleroot. Then, from the children returned, further children can be found through the appropriate APIs.
The required methods for a complete ITreeContentProvider are listed below:
public void inputChanged(Viewer v, Object oldInput, Object newInput); public void dispose(); public Object[] getElements(Object parent); public Object getParent(Object child); public Object [] getChildren(Object parent); public boolean hasChildren(Object parent);
The label provider is used to show the text and the image associated with a given object element. Because of how simple the idea is, and also the implementation, it only requires two methods: getText and getImage, both taking an object to evaluate.
NOTE: If you use a shared image in the eclipse platform, you do not need to create or destroy it. If you use your own image, you are in charge of creating it AND destroying it. One good practice is to create shared images for your entire plug-in during it's initialization, and then destroy those images when your plug-in shuts down. This way you aren't creating and destroying images needlessly.
Context menus for your own view are a lot easier to add, and can be added or removed, set enabled or disabled, over a much wider range of criteria then those through the plugin.xml file.
If we look at the SWTView, we see that we register our menu during when our control is created in the createPartControl method call. We declare our actions right there, and register them with our menu manager. The menuManager can still be changed programatically, but that usually means the menu intends to stay relatively constant regardless of what items are selected.
The menu manager is actually pretty robust. You can mark it as dirty, add or remove actions from it, as well as other things. In the SWTView example, we've specifically noted not to clear itself before showing each time. To clear before showing every time would be characteristic of a menu that is very sensitive to what elements are selected, as we'll see in the other views. For the SWTView, we've created an unchanging menu that is not context sensitive at all.
// creating the Menu and registering it MenuManager menuMgr = new MenuManager("#PopupMenu"); menuMgr.setRemoveAllWhenShown(false); Menu menu = menuMgr.createContextMenu(viewer.getControl()); viewer.getControl().setMenu(menu); getSite().registerContextMenu(menuMgr, viewer); // create our action Action act1 = new Action() { public void run() { showMessage("Action1"); } } ; act1.setText("SWT Action 1"); // add it menuMgr.add(act1) ; // required, for extensions menuMgr.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
Looking at our TableView implementation, the first thing we do to start off the menu process is create our actions. We give them titles, descriptions, tooltips, and run methods. The next step is to create the menu manager and hook the actions into it.
The differing code is seen below:
menuMgr.setRemoveAllWhenShown(true); menuMgr.addMenuListener(new IMenuListener() { public void menuAboutToShow(IMenuManager manager) { TableView.this.fillContextMenu(manager); } });
Here, we declare that we want to clear out the menu every time it's about to be shown. Then we add a listener, where we can add actions to the menu in a much more fine-tuned manner.
If we go down to the fillContextMenu method, we get the selection from the viewer. I know it will return an IStructuredSelection, but I've decided to add no actions if more than one element is selected. I add one action regardless of what element is selected, but if a String is selected, I add the second action as well.
So when we run this example and open the List / Table View, if we right click on the integer element, we should only see one action, and if we right-click on one of the other three, we should see two. Whether the object is a String or an Integer goes back to the Content Providers, and what they have given us as their list of children. If we look at the TableView's ViewContentProvider inner class and its getElements method, the code returns a list with 3 Strings and one Integer.
public Object[] getElements(Object parent) { ArrayList list = new ArrayList(); list.addAll(Arrays.asList(new String[] {"One", "Two", "Three"})); list.add(new Integer(5)); return list.toArray(); }
Drilldown Adapters are a feature available for TreeViewers to help navigate through the data, instead of having to deal with ever expanding trees. In this way you can essentially zoom in to view just one part of the tree, i.e., drill down. Only two lines of code were added throughout the initialization of the viewer to invoke this feature in the context menu. Only one line was needed to add them to the view's toolbar. They are shown below
// Create the Drill Down Adapter drillDownAdapter = new DrillDownAdapter(viewer); // Add the options to the context menu (IMenuManager) drillDownAdapter.addNavigationActions(manager); // Add the options to the view's toolbar (IToolBarManager) drillDownAdapter.addNavigationActions(manager);
Without drilldown adapters, your tree's content provider could probably get away without completing the getParent method. If you do use drilldown adapters, however, this method will need to be completed so that the model can navigate both up and down the trees.
Other features are able to be added to views as well. Viewers can have doubleclick actions, and I didn't get into the specifics of adding the toolbar icons, but the code is all very short, concise, and in the examples. The ease with which one can make a view should help make adding them to your plug-in as painless as possible.
One thing of note to anyone using a view to add, remove, or change data to some underlying model. By convention, any changes made to data in a view (such as right clicking on an element and selecting a delete action) should be saved to the model right away, and to its underlying file resource if one exists. Editors, on the other hand, follow the open, save, store, model, where changes must be specifically saved by the user. Views should not behave in this manner. All changes should be live as soon as they are executed.
Finally, there will be many other code snippets for views in future examples, and occasional tips about specific quirks found when using them.