Component
Framework Proposal
Revision 1.0.3 By
Stefan Xenos and Nick Edgar Last modified 2004/11/02
Table of Contents
1.0 Introduction
This proposal outlines a framework for managing complex executable
extensions. This framework is a first step toward allowing views and
editors to be combined recursively, and created inside arbitrary SWT
composites. Although it is intended for creating reusable UI
components, the framework is useful for any extension point that
creates complicated Java objects. For this reason, we will refer to
the objects being created as "components" even though our
components are typically views and editors.
This proposal is
broken into three parts. The introduction describes the motivation
for creating the component framework and its requirements. The second
section describes the component framework in itself, which could be
used for any executable extension point. The third section describes
how the workbench will use the component framework for views and
editors.
Primary goals:
Robustness / leak proofing
Nesting of components
Allow components to be reused
outside the workbench
Scalability (allow an open-ended
set of components)
API versioning
Ease of use
1.1 Robustness / leak proofing
Much of the Eclipse API is currently accessed through singleton
objects. This makes it easy for a view or editor to leak listeners,
fail to clean up reference counts, leak OS resources, etc. since
there is no way of tracking which resources were allocated by a
particular view. This problem would be reduced if views and editors
were more like mini-applications. The view or editor would access the
rest of the world through a set of local services. When the view or
editor is destroyed so would all of the services allocated for it.
This gives each service a chance to clean up after itself.
For
example, instead of reaching into a global preference store, a view
or editor could access all preferences through a local preference
service. A unique instance of the local preference service [Space
issue?] would be created for each view, and would be disposed
with the view. When the preference service is destroyed it would
clear its listener list, ensuring that the view will not leak any
global listeners. This example shows what we mean by a component.
Essentially, a component is an object that communicates with the rest
of the application through a set of interfaces given to it in its
constructor.
1.2 Nesting of components
There is demand for the ability to embed views and editors inside
one another. Some examples:
An XML editor might embed the
properties view
A refactoring wizard might include
a source editor
A plugin may wish to create a set
of pluggable UI components that are not views or editors themselves,
but can be used inside any view or editor
Various workbench objects (like the PartSashContainer that
handles the layout of docked parts within the workbench) could be
exposed as API
Many downstream plugins have solved these problems by creating
their own frameworks for reusable UI components. Unfortunately,
[missing text?] only works for specific
views and editors, does not encourage interoperability between
plugins that have adopted different frameworks, and requires a lot of
work. The goal here is to adopt a framework in the workbench itself
that allows all editors, views, and other workbench objects to be
easily nested.
1.3 Creating parts outside the
workbench
It should be possible to instantiate views and editors outside the
workbench. Some examples:
Unit-test editors and views by
instantiating them within a JUnit test suite.
Create an RCP application that does not depend on the
workbench but includes view-like pluggable parts that could also be
used as views within Eclipse
1.4 Scalability
The workbench currently offers a closed set of services to parts.
This forces parts to reach to global objects whenever they need
something that isn't available from their site. It should be possible
for any plugin to contribute to the set of local API available to a
part, and it should be possible to instantiate a part even if its
parent doesn't know about all of a part's dependencies.
1.5 Ease of use
Views and editors currently have a complicated lifecycle that must
be managed by the workbench. This complexity should not be exposed to
client code.
It should only require one method
call to create a component and one method call to destroy it.
There should not be unnecessary
duplication between XML and java code.
Components should not be
responsible for dealing with error conditions (such as missing
dependencies) that can be detected by the framework.
Components should not need to
implement interfaces they don't care about.
A parent context should not need to provide child components
with interfaces it doesn't care about.
2.0 Component Framework
Components are pluggable objects that are build using constructor
injection. Constructor injection means that the framework looks at
the object's constructor to determine what it needs to do to build
that object.
Components:
Have exactly one constructor
Take zero or more other components
as arguments to their constructor
Are fully initialized by their constructor
Components do not:
Take arrays or primitives as
arguments to their constructor
Take more than one argument of the
same type in their constructor
Need to support any particular base class or interface
Any class with these properties can be used as a component. Since
components are plain-old-java-objects [POJO?
:-)], they do not need to depend on the component framework.
Components are fully initialized by their constructor, meaning it is
never necessary to call an initialize method or any combination of
set methods after constructing the object. Components never accept
null as an argument to their constructor.
For example, if a
view could be created as a component it might look like this:
/**
*
"Hello world" view using the service framework
*/
public
class HelloWorldView {
public
HelloWorldView(Composite parent) {
Label helloWorld = new Label(parent, SWT.NONE);
helloWorld.setText("Hello world");
}
}
The HelloWorldView component depends on one
service: a Composite created for it by whoever instantiated the view.
Compare this with the same view written using the existing API:
/**
*
"hello world" view using the Eclipse 3.0 API.
*/
public
class HelloWorldView extends ViewPart {
public void createPartControl(Composite parent) {
Label helloWorld = new Label(parent, SWT.NONE);
helloWorld.setText("Hello world");
}
public void setFocus() {
}
}
The main difference is that the component version does not
require the ViewPart base class and is fully initialized after
construction. In both cases, the extension point markup would look
like this:
<extension
point="org.eclipse.ui.views">
<view
name="Title
Test View"
icon="icons\view.gif"
class="org.eclipse.ui.mytest.HelloWorldView"
id="org.eclipse.ui.mytest.HelloWorldViewID">
</view>
</extension>
2.1 Instantiating a view
Components are instantiated using factories. For the moment, let's
focus on views before we explore components in general. Views could
be created using a method on IWorkbenchPage that would look something
like this. Note that this example is only intended to illustrate the
general idea -- the actual protocol for creating views is likely to
change.
/**
*
Creates a view of the given type inside the given composite. The
caller must dispose the container once they are done with it.
*
*
@param viewId id of the view extension to use
*
@param parentComposite parent composite for the view
*
@return an IContainer that contains all components needed for the
view
*/
IContainer
createView(String viewId, Composite parentComposite);
This creates a new instance of the view in the given
composite. Notice that the factory method doesn't return an instance
of the view itself but an instance of IContainer, which looks
something like this:
/**
*
Main interface to a component.
*/
public
interface IContainer extends IAdaptable {
public void dispose(); }
This interface wraps the component and all of its
dependencies. Once we've obtained an IContainer handle, we are
obligated to dispose it when we are done with it. All access to the
component is done through adapters, which insulates the application
from changes in individual views. For example, if a view that
previously implemented the IViewInterface1 migrates to using the
newer IViewInterface2, its container will continue to work as long as
someone has provided an adapter between the two versions of
IViewInterface. The getAdapter() method on IComponent searches for
adapters in the following order:
1. If the component itself
implements the adapter type, it returns the component. 2. If the
component itself implements IAdapter, and the component's
getAdapter(...) method returns non-null, we return that adapter. 3.
If the adapter manager has an adapter between the component and the
adapter type, we return that adapter. 4. If the container's
factory can construct a component of the requested type, we create
that component, add it as a local dependency to the IContainer, and
return it. 5. Return null
For example, it would be possible
to create our HelloWorldView (above) in a modal dialog like this:
[hm... toy example? E.g. you don't know that
the view can function reasonably in this context.]
void
createHelloWorldViewInADialog(IWorkbenchPage page) {
Display display = Display.getDefault()
Shell shell = new Shell(Display.getDefault());
shell.setLayout(new FillLayout());
// Create the view and its widgets
IContainer myView =
workbenchPage.createView("org.eclipse.ui.mytest.HelloWorldViewID",
shell);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch ()) display.sleep ();
}
// Dispose the view
myView.dispose();
}
2.2 Instantiating components in
general
All components are constructed using an IContainerFactory, however
most component-based extension points will provide some sort of
convenience method to wrap their IContainerFactory. The
IWorkbenchPage.createView method in the previous section is a
convenience method for the org.eclipse.ui.views extension
point. IContainerFactory looks like this:
/** *
Factory for IContainer instances. The default factory is returned
by * Components.getFactory(). Clients wishing to implement
their own specialized * factories should call
createDerivedFactory() to get access to a factory * whose
behavior can be modified programmatically. Not intended to be *
implemented by clients. * * @since 3.1 */ public
interface IContainerFactory {
/** * Creates and returns a new
IContainer instance, given the *
implementation class for its component. The caller MUST call
IContainer.dispose() * when it is done
with the component. The factory does not need any prior
* knowledge of the component class be [missing
words ] *
* @param componentImplementation concrete class to be instantiated by
the factory. The class *
must be a valid component (it must have
exactly one constructor which only *
references other component interfaces known
to this factory). * @return a newly
constructed <code>IContainer</code> instance
* @throws MissingDependencyException if the requested component
depends on another component that
* cannot be
constructed by this factory * @throws
CoreException if there is a more permanent problem with using the
given class as *
a component [ *** Hunh? *** ]
* @since 3.1 */
public IContainer createContainer(Class componentImplementation)
throws MissingDependencyException, CoreException;
/** * Creates a
specialization of this factory. By default, the specialized
* factory will have the same behavior as its parent. However, the
derived * factory can add, or override
the implementation for any components. *
* @return new factory instance that
allows individual components to be overridden.
* By default, the derived factory will delegate all of its behavior
to the receiver. * Changes in the
receiver will affect the derived factory.
* @since 3.1 */
public IMutableContainerFactory createDerivedFactory(); }
Notice that createComponent throws two types of exception. A
CoreException indicates a problem with the component itself, and a
MissingDependencyException indicates that some other component or
adapter could not be found. This can help locate the cause of
problems, and a robust extension point may want to disable components
that throw CoreExceptions.
All arguments to a component's
constructor are provided by its factory. Different factories know how
to create different types of components or will provide different
implementations for the same components. All factories are derived
from the root factory, which is returned by Components.getFactory().
A plugin can create derived factories to supply services to one
specific extension point. It is also possible to create a derived
factory in order to pass information to the constructor of one
specific object, as we will do in this example. In our case, we will
create a container factory to pass a specific Composite into the
constructor of HelloWorldView. The code looks like this:
// Get the
global factory
IContainerFactory viewFactory =
Components.getFactory();
// Create the Composite
for the view
Composite viewComposite = new
Composite(parentComposite, SWT.NONE);
viewComposite.setLayout(new
FillLayout();
// Create a specialized factory
that knows about the view's composite and plugin
bundle
IMutableContainerFactory derivedFactory =
viewFactory.createDerivedFactory();
// Add the
view's composite as a service
instance
derivedFactory.addComponentInstance(viewComposite);
//
Add the view's plugin bundle as a service instance (not required in
this example, but this
// is recommended practise for
any component created from an extension
point).
derivedFactory.addComponentInstance(pluginBundle);
//
Create the view. Provide its constructor and some context (the page
that created it).
IContainer view =
derivedFactory.createContainer(HelloWorldView.class);
//
Do something with the view
// ...
//
Now clean up
view.dispose();
viewComposite.dispose();
This code will work, but it has two
problems:
1. Even if HelloWorldView didn't require a
Composite, we would still have created one (which is wasteful). 2.
We need to manually dispose the view's composite after we're done
with it. This defeats the point of IComponent.dispose(), which is
supposed to clean up all of the component's dependencies
automatically.
2.3 Creating dependent components on
demand
Rather than managing the view's Composite ourselves, we could
supply a factory that knows how to create and destroy Composites as
needed. Such a factory would look like this:
/**
*
ServiceAdapter for creating and managing SWT Composites as
services.
*/
public class
CompositeFactory extends ComponentAdapter {
private Composite parent;
/**
* The SWT composites
created by this factory will all be children of the given
* composite.
*/
public CompositeFactory(Composite parent) {
this.parent = parent;
}
protected Object
create(IAdaptable availableServices) throws CoreException {
// Create a new Composite which will be used as a
service.
// If
we needed any additional
Composite newChild = new Composite(parent, SWT.NONE);
newChild.setLayout(new FillLayout());
return newChild;
}
protected void dispose(Object service) {
((Composite)service).dispose();
}
public String
getInterfaceName() {
// Return the fully qualified class name of the Composite class. The
framework will call
// this method to determine what type of service will be constructed
by this factory.
return Composite.class.getName();
}
}
Using CompositeFactory, we could construct HelloWorldView like
this:
// Get the
global factory
IContainerFactory viewFactory =
Components.getFactory();
// Create a specialized
factory that knows about the view's composite and plugin
bundle
IMutableContainerFactory derivedFactory =
viewFactory.createDerivedFactory();
// Add the
view's composite as a service
instance
derivedFactory.addComponentFactory(new
CompositeFactory(parentComposite));
// Add the
view's plugin bundle as a service instance (not required in this
example, but this
// is recommended practise for any
component created from an extension
point).
derivedFactory.addComponentInstance(pluginBundle);
//
Create the view. Provide its constructor and some context (the page
that created it).
IContainer view =
derivedFactory.createComponent(HelloWorldView.class);
//
Do something with the view
// ...
//
Now clean up
view.dispose();
The composite will now be allocated as needed and disposed
inside the call to view.dispose().
2.4 Declaring Global Services
Services are a type of component that can be used by other
components. Services are registered through the
org.eclipse.ui.component.service [obvious:
not in o.e.ui if it is generic] extension point. Services
are global in the sense that they are created by factories that are
visible everywhere, however each instance of a services is associated
with a specific IContainer. Each service implements a service
interface. Other components can use the service interface in their
constructor. When they do so, a new instance of the service will be
created for that component which will be managed by the same
IContainer.
Each component supplies an implementation and an
interface. Here is an example service interface:
/**
*
Service interface:
*
*
Provides a context for reporting and logging exceptions.
*/
public
interface IErrorContext {
/**
* Create an IStatus error describing the given throwable
*/
public IStatus
createStatus(Throwable t);
/**
* Create an IStatus
message with the given severity, message, and (optional)
throwable
*/
public IStatus createStatus(int severity, String message, Throwable
t);
/**
* Logs an IStatus message
*/
public void log(IStatus
status);
/**
* Logs an exception to the system log
*/
public void log(Throwable t);
}
Here is the associated service
implementation:
/**
*
Service implementation:
*
*
Creates and logs errors in the context of a plugin bundle
*/
public
class ErrorContext implements IErrorContext {
private Bundle pluginBundle;
public ExceptionLoggingService(Bundle pluginBundle) {
this.pluginBundle = pluginBundle;
}
public IStatus
createStatus(Throwable t) {
String message = t.getMessage();
if (message == null) {
message = t.getString();
}
return
createStatus(Status.ERROR, message, t);
}
public IStatus
createStatus(int severity, String message, Throwable t) {
return new Status(severity,
pluginBundle.getSymbolicName(), Status.OK, message, t);
}
public void log(Throwable t)
{
log(createStatus(t));
}
public void log(IStatus status) {
Plugin plugin =
Platform.getPlugin(pluginBundle.getSymbolicName());
plugin.getLog().log(status);
}
}
Finally, here is the extension markup:
<extension
point="org.eclipse.ui.component.service">
<service
class="org.eclipse.ui.workbench.ErrorContext"
interface="org.eclipse.ui.workbench.IErrorContext">
</service>
</extension>
Notice that the extension XML needs to indicate which service
interface is being implemented by the service, but not its
dependencies. When it comes time to create the service, the framework
detects that the ErrorContext needs to be associated with a plugin
Bundle by examining its constructor. This means that if plugin A
defines the ErrorContext service and plugin B defines a view that
uses the service, then all of the status messages constructed by the
view will be associated with the view's own plugin - not the plugin
that defined the ErrorContext service.
2.5 Using services
This example shows how a component can use a service.
public class
MyView {
private IErrorContext
errorContext;
public
MyView(Composite parent, IErrorContext errorContext) {
this.errorContext = errorContext;
Button errorButton = new Button(parent,
SWT.PUSH);
errorButton.setText("Log an exception");
errorButton.addSelectionListener(new SelectionAdapter() {
public void
widgetSelected(SelectionEvent e) {
try {
//
Throw a NPE
String myString = null;
String bogusCode = myString.substring(10, 30);
} catch
(Exception e) {
// Log the NPE using the error
context service
errorContext.log(e);
}
}
});
}
}
This view creates a button which, when
pressed, will throw a NPE and log it. The status message will be
constructed and logged using the IErrorContext service we defined
above. [Yikes! Now we have the “infinitely
complex service web” problem. In a complex system built from
this, what are the odds that “right” ErrorContext gets
chosen?]
2.6 Lifecycle
Many components need to do explicit cleanup. Components that need
to perform cleanup will implement the IDisposable interface. For
example, the following component attaches a listener to a global
preference store and detaches it when done.
/**
*
Provides access to a plugin's preference store in a manner that
prevents listener leaks.
*/
public
interface IPreferences {
public
String getString(String prefId);
public void setString(String prefId, String value);
public void addListener(IPropertyChangeListener l);
public void removeListener(IPropertyChangeListener l);
}
/**
*
Concrete implementation of the IPreferences interface
*/
public
LocalPreferenceStore implements IPreferences, IDisposable {
ListenerList listeners = new ListenerList();
Preferences prefs;
private class PropertyChangeListener implements
Preferences.IPropertyChangeListener {
/*
* @see
org.eclipse.core.runtime.Preferences.IPropertyChangeListener#propertyChange(org.eclipse.core.runtime.Preferences.PropertyChangeEvent)
*/
public void propertyChange(Preferences.PropertyChangeEvent event)
{
firePropertyChangeEvent(event.getProperty(), event.getOldValue(),
event.getNewValue());
}
}
PropertyChangeListener listener = new PropertyChangeListener();
/**
* Create a wrapper
around the given bundle's preference store.
*/
public
LocalPreferenceStore(Bundle pluginBundle) {
Plugin plugin =
Platform.getPlugin(pluginBundle.getSymbolicName());
prefs = plugin.getPluginPreferences();
prefs.addListener(listener);
}
private void
firePropertyChange(String name, Object oldValue, Object newValue)
{
PropertyChangeEvent event = new PropertyChangeEvent(this, name,
oldValue, newValue);
Object[] listeners = this.listeners.getListeners();
for (int i= 0; i < listeners.length; i++)
((IPropertyChangeListener)
listeners[i]).propertyChange(event);
}
/**
* This method will automatically be called when the component that
uses this service is disposed.
* It should clean up anything that has been allocated by the
service.
*/
public void dispose() {
prefs.removeListener(listener);
}
public String
getString(String prefId) {
return prefs.getString(prefId);
}
public void setString(String
prefId, String value) {
prefs.setValue(prefId, value);
}
}
Similarly, components may implement the IDisposable interface.
A component that implements IDisposable will be disposed before any
of its services, as shown by the following example:
public LifecycleService
implements IDisposable {
public
LifecycleService() {
System.out.println("LifecycleService created");
}
public void dispose() {
System.out.println("LifecycleService
destroyed");
}
}
public
LifecycleView implements IDisposable {
public LifecycleView(LifecycleService service) {
System.out.println("LifecycleView created");
}
public void dispose() {
System.out.println("LifecycleView destroyed");
}
}
Finally, we could execute the following
code to trace when the service is created and destroyed:
IMutableContainerFactory factory =
Components.getFactory().createDerivedFactory(); factory.addComponentImplementation(LifecycleService.class);
System.out.println("Creating
a LifecycleView");
IContainer viewComponent =
factory.createComponent(LifecycleView.class); viewComponent.dispose();
System.out.println("Creating
two LifecycleViews");
IContainer viewComponent2 =
factory.createComponent(LifecycleView.class); IContainer
viewComponent3 =
factory.createComponent(LifecycleView.class); viewComponent3.dispose(); viewComponent2.dispose();
The code will generate the following output:
Creating a
LifecycleView LifecycleService created LifecycleView
created LifecycleView destroyed LifecycleService
destroyed
Creating two LifecycleViews LifecycleService
created LifecycleView created LifecycleService
created LifecycleView created LifecycleView
destroyed LifecycleService destroyed LifecycleView
destroyed LifecycleService destroyed
2.7 Dynamic Services
Global service factories are dynamic in the sense that they come
and go as plugins are installed or uninstalled. If a plugin providing
a service is uninstalled, a new implementation is selected from a
different plugin. If no other plugin provides that service, the
service becomes unavailable and it will not be possible to construct
components that use it.
Service instances themselves are not
dynamic. Once a service is created, it will exist for as long as the
component it was created for. Even if the a plugin stops providing a
service, all instances of that service will continue to exist until
disposed. All services used by a component will be created before
that component, so components do not need to query for the existence
of services or to wait for services that will be created
after-the-fact.
Plugins that need more advanced dynamic
support should use OSGI [It's written “OSGi”]
services. The component framework is not intended as a
replacement for OSGI, and there is no point in reimplmenting things
that OSGI already does well. [Not clear. Do
OSGi services provide similar features? Would it be difficult to
implement something with your services and switch later? What are the
trade offs, etc.]
2.8 Optional Services
Sometimes a component does not require a service but can make use
of it service if it exists. This should usually be handled by
explicitly creating an empty global implementation of the service,
but sometimes it is more efficient to have the component explicitly
check if the service exists. This can be done by taking an argument
of type IAdaptable, like this:
/** * A component with
no dependencies, but which can optionally accept an IMemento for
initialization. */ public class MyComponent {
String myName = "default name";
public MyComponent(IAdaptable optionalServices) {
// Check if an IMemento service exists.
IMemento memento =
(IMemento)optionalServices.getAdapter(IMemento.class);
if (memento != null)
{ String name =
memento.getString("name");
if (name != null) {
myName = name;
} }
System.out.println("created component with name = "
+ myName); } }
We could create the component like this:
// Create a memento with the
name "custom name" IMemento memento =
XMLMemento.createWriteRoot("test"); memento.putString("name",
"custom name");
// Create a factory which knows
about our memento IMutableContainerFactory context =
Components.getFactory().createDerivedFactory(); context.addComponentInstance(memento);
//
Use the factory to instantiate MyComponent IContainer component =
context.createContainer(MyComponent.class); // Will print the
message "created component with name = custom
name" component.dispose();
We can also create the component without the optional IMemento:
IContainer
component =
Components.getFactory().createContainer(MyComponent.class); //
Will print the message "create component with name = default
name" component.dispose();
WARNING: This pattern should be used with care since it adds
special cases to the component code. It is mainly inteded for
situations where a component requires an open-ended set of services
or where it is not possible to offer a default implementation of a
service. [But wouldn't I want to make the
highest use possible of this capability, so that I could be used in
the widest range of situations?]
2.9 Multiplexing services
In UI code, it is common to create aggregate components that have
an "active" child. Many of the adapters that the aggregate
offers its parent will redirect their implementation to the active
child. We refer to this as "multiplexing" the service. The
aggregate itself will know how to select its active child, but it may
not know about all the services that need to be multiplexed. The
default implementation of a service determines how and if it should
be multiplexed.
This can be explained better using a concrete
example. Workbench parts offer an IFocusable adapter to their parent
that allows their parent to give them focus.
/** * Parts can
implement this interface if they wish to overload the default *
setFocus behavior. */ public interface IFocusable {
/** * Gives focus to the part
*/ public void setFocus(); }
This interface should be multiplexed by default. For example, if
we call setFocus() on a multi-page editor that doesn't explicitly
implement the IFocusable interface, it should redirect focus to its
active child. If the part doesn't have an active child, focus should
go directly to the part's main control. To supply this default
behavior, we provide an implementation of IFocusable as a global
service:
/** * Default
implementation of the IFocusable service. If a part doesn't
explicitly * provide an adapter for IFocusable, this
implementation will be used. */ public class
DefaultFocusable implements IFocusable {
private Composite control; private IMultiplexer
activePart; /**
* Creates the default implementation of IFocusable, given the
main control * of the part and an
IMultiplexer that can be queried for the active child.
* * @param toGiveFocus main
control of the pane * @param
activePartProvider multiplexer that returns the active part
*/ public DefaultFocusable(Composite
toGiveFocus, IMultiplexer activePartProvider) {
control = toGiveFocus;
activePart = activePartProvider; }
/** * First, try
to give focus to the active child. If there is no active child, give
focus to * the main control.
*/ public void setFocus() {
// If this part has the notion of an active child,
give that child focus Object
current = activePart.getCurrent();
if (current != null) {
IFocusable focusable = (IFocusable)Components.getAdapter(current,
IFocusable.class);
if (focusable != null) {
focusable.setFocus();
return;
}
}
// If the part has no children, then give focus to the part
itself.
control.setFocus(); } }
Here is the XML markup to register the service:
<service
interface="org.eclipse.ui.workbench.services.IFocusable"
class="org.eclipse.ui.internal.part.serviceimplementation.DefaultFocusable"/>
All of the services we have looked at so far have been offered
by a parent for use in a child's constructor. In this case, the
service is offered as an adapter on the child to be used by the
parent. In both cases, the service is declared in the same way. Any
service that supports multiplexing takes an IMultiplexer in its
constructor. If the part does not support multiplexing, the
IMultiplexer will always returns null from getCurrent. Here is an
example part that supports multiplexing:
/** *
Part that explicitly implements IFocusable. */ public
class Page implements IFocusable { Text
textField;
public Page(Composite control)
{ textField = new Text(control,
SWT.NONE); }
public
void setFocus() {
textField.setFocus(); } }
/** *
Non-multiplexing part that relies on the DefaultFocusable service *
to give focus to its composite. */ public class Page2
{ Text textField;
public Page(Composite control) {
textField = new Text(control, SWT.NONE);
} }
/** * Multiplexing part that contains a Page,
a Page2, and a checkbox. * When the checkbox is selected,
the first page will be active. When the * checkbox is
deselected, the second page will be active. Calling setFocus *
on the MultiplexingView will always give focus to the active
page. */ public class MultiplexingView implements
IDisposable, IAdaptable { private IContainer
page1; private IContainer page2;
// Helper class that implements the IMultiplexer interface
private Multiplexer multiplexer = new Multiplexer();
private Button checkBox;
public
MultiplexingView(Composite myControl) {
myControl.setLayout(new RowLayout());
checkBox = new Button(myControl, SWT.CHECK);
checkBox.addSelectionListener(new SelectionAdapter() {
public void
widgetSelected(SelectionEvent e) {
updateActivePart();
}
});
checkBox.setText("Activate
part 1");
// Create the
child controls
IMutableComponentFactory childFactory =
Components.getFactory().createDerivedFactory();
// Use the CompositeAdapter class we created earlier in
order to provide each page // with
its own control.
childFactory.addComponentInstance(new
CompositeAdapter(myControl));
page1 = childFactory.createContainer(Page.class);
page2 = childFactory.createContainer(Page2.class);
// Activate the initial part
updateActivePart();
}
private final void updateActivePart() {
if (checkBox.getSelection()) {
multiplexer.setCurrent(page1);
} else {
multiplexer.setCurrent(page2);
}
}
/** * In order to indicate that this is a
multiplexing part, we need to implement *
the IMultiplexer adapter. We simply redirect to the multiplexer
object. */ public
Object getAdapter(Class adapterType) {
if (adapterType == IMultiplexer.class) {
return multiplexer;
} }
/**
* Release any resources allocated in the constructor
*/ public void dispose() {
page1.dispose();
page2.dispose(); } }
Notice that MultiplexingView itself doesn't know about the
IFocusable interface, however if we were to create a MultiplexingView
and ask its container for an IFocusable interface, we would get an
interface that behaves as expected.
// Stupid
example that creates a MultiplexingView, gives it focus, then
destroys it
// Create a
MultiplexingView
IMutableComponentFactory
childFactory =
Components.getFactory().createDerivedFactory(); childFactory.addComponentInstance(new
CompositeAdapter(myControl));
IContainer viewContainer =
childFactory.createContainer(MultiplexingView.class);
// Give
focus to the active part IFocusable focusable =
viewContainer.getAdapter(IFocusable.class); focusable.setFocus();
//
Destroy the view viewContainer.dispose();
This example also shows how
the same service can work for both multiplexing and non-multiplexing
parts. The Page2 class doesn't implement IFocusable or IMultiplexing
part, but we are still able to ask for an IFocusable interface and
use it to give focus to the part.
3.0 Views and Editors as Components
TODO [...]
|