Instead of every object having to have its own custom information dialog, the Eclipse IDE provides a generic Properties view (in the org.eclipse.ui.views
plug-in), which can be used to show information about the currently selected object. The properties are discovered generically from an object and accessed through the IPropertySource
interface. This allows an object to provide an abstracted way of computing the fields shown in the property view.
The easiest way to create a property source is to let the object in question implement its own IPropertySource
interface. This works when the source code can be modified, but in many cases (such as the TimeZone
, or a Map.Entry
containing a String
key and a TimeZone
) the source code cannot be modified.
MANIFEST/META-INF.MF
of the plug-in, and add org.eclipse.ui.views
as a dependency via the Dependencies tab, or by adding it to the bundles in the Require-Bundle
entry. Without this, the IPropertySource
interface won't be found.TimeZonePropertySource
in the com.packtpub.e4.clock.ui.internal
package that implements the IPropertySource
interface. Take a single TimeZone
instance in the constructor.public class TimeZonePropertySource implements IPropertySource { private TimeZone timeZone; public TimeZonePropertySource(TimeZone timeZone) { this.timeZone = timeZone; } }
getPropertyValue()
and getPropertyDescriptors()
. (The other methods, such as getEditableValue()
and isPropertySet()
, can be ignored, because they only get invoked when performing edit operations. These should be left empty or return null
/false
.) The accessors are called with an identifier; while the latter returns an array of PropertyDescriptors
combining pairs of identifiers and a displayable name. Add the following to the TimeZonePropertySource
class:private static final Object ID = new Object(); private static final Object DAYLIGHT = new Object(); private static final Object NAME = new Object(); public IPropertyDescriptor[] getPropertyDescriptors() { return new IPropertyDescriptor[] { new PropertyDescriptor(ID, "Time Zone"), new PropertyDescriptor(DAYLIGHT, "Daylight Savings"), new PropertyDescriptor(NAME, "Name") }; } public Object getPropertyValue(Object id) { if (ID.equals(id)) { return timeZone.getID(); } else if(DAYLIGHT.equals(id)) { return timeZone.inDaylightTime(new Date()); } else if (NAME.equals(id)) { return timeZone.getDisplayName(); } else { return null; } }
IAdaptable
interface, which allows a class to virtually implement an interface. Since the TimeZone
cannot implement the IAdaptable
interface directly, an IAdapterFactory
is needed.TimeZoneAdapterFactory
in the com.packtpub.e4.clock.ui.internal
package that implements the IAdapterFactory
interface.public class TimeZoneAdapterFactory implements IAdapterFactory { public Class[] getAdapterList() { return new Class[] { IPropertySource.class }; } public Object getAdapter(Object o, Class type) { if(type == IPropertySource.class && o instanceof TimeZone) { return new TimeZonePropertySource((TimeZone)o); } else { return null; } } }
plugin.xml
file declaratively.<extension point="org.eclipse.core.runtime.adapters"> <factory adaptableType="java.util.TimeZone" class="com.packtpub.e4.clock.ui.internal.TimeZoneAdapterFactory"> <adapter type="org.eclipse.ui.views.properties.IPropertySource"/> </factory> </extension>
createPartControl()
method in the TimeZoneTreeView
view creation:System.out.println("Adapter is " + Platform.getAdapterManager(). getAdapter(TimeZone.getDefault(),IPropertySource.class));
Adapter is com.packtpub.e4.clock.ui.internal.TimeZonePropertySource@7f8a6fb0
createPartControl()
method of the TimeZoneTreeView
class will solve the problem.//The following commented code needs to be removed /*System.out.println("Adapter is " + Platform.getAdapterManager(). getAdapter(TimeZone.getDefault(),IPropertySource.class));*/ getSite().setSelectionProvider(treeViewer);
TimeZone
is selected, the Properties, view will be updated automatically. Run the Eclipse instance and open the Time Zone Tree View, select a time zone, and open the Properties view:E4: To hook a viewer up to the selection provider, the code looks similar to this:
@Inject ESelectionService selectionService; ISelectionChangedListener selectionListener; @PostConstruct public void postConstruct() { selectionListener = new ISelectionChangedListener() { public void selectionChanged(SelectionChangedEvent e) { if (selectionService != null) selectionService.setSelection(e.getSelection()); } }; treeViewer.addSelectionChangedListener(selectionListener); } @PreDestroy public void preDestroy() { if(selectionListener != null) treeViewer.removeSelectionChangedListener (selectionListener); selectionListener = null; }
To update the state of the Workbench's selection, the view's selection provider was connected with that of the page (via the getSite()
method). When the selection in the viewer changes, it sends a message to registered listeners of the page's selection service so that they can update their views, if necessary.
E4: The selection listener needs to be (un)registered manually to provide a hook between the viewer and the selection service. Instead of being ISelectionService
, it's ESelectionService
. The interface is slightly different, because the ISelectionService
is tied to the IWorkbenchPart
class, but the ESelectionService
is not.
To provide information to the Properties view, an IPropertySource
was created for the TimeZone
and associated with the Platform IAdapterManager
through the declaration in the plugin.xml
file.
It's generally better to provide hooks declaratively in the plugin.xml
file rather than hooking it up with the start()
and stop()
activator methods. That's because the start on the Activator
may not be called until the first class is loaded from the bundle; in the case of the adaptor, the declarative registration can provide the information before it is first required.
The adaptor factory provides the getAdapter()
method, which wraps or converts the object being passed into one of the desired type. If the object is already an instance of the given type, it can just be returned as it is—but otherwise return a POJO, proxy, or wrapper object that implements the desired interface. It's quite common to have a class (such as TimeZonePropertySupport
) whose sole job is to implement the desired interface, and which wraps an instance of the object (TimeZone
) to provide the functionality.
The IPropertySupport
interface provides a basic means to acquire properties from the object, and to do so it uses an identifier for each property. These can be any object type; in the preceding example, new Object
instances were used. Although it is possible to use String
(plenty of other examples do), this is not recommended, since the value of the String
has no importance and it takes up space in the JVM's PermGen memory space. In addition, using a plain Object
means that the instance can be compared with ==
without any concerns, whereas doing so with String
is likely to fail the code reviews or automated style checkers. (The preceding example uses the .equals()
method to encourage its use when not using an Object
, but a decent JIT will in-line it—particularly since the code is sending the message to a static final
instance.)