![]() |
||||||||||||||
|
Eleritec Docking Framework Tutorial
|
||||||||||||||
|
The Eleritec Docking Framework provides a simple, non-invasive means of adding runtime drag-n-drop docking functionality to otherwise standard components. Docking is accomplished by grabbing a component with the mouse cursor, dragging it to a desired location within the GUI, and dropping it into its new location. There are three main parties involved in a docking operation; the Dockable, the DockingPort, and the DockingManager. A Dockable is any component that has docking capabilities (implements the Dockable interface). A DockingPort is any container that is capable of accepting a new child Dockable (implements the DockingPort interface). The DockingManager is the class responsible for detecting drag operations, maintaining the state of
the Dockable while dragging, and initiating the docking operation into the DockingPort when dragging has ended.
Each DockingPort consists of five regions, which are analogous to the regions of a BorderLayout; NORTH, SOUTH, EAST, WEST, and CENTER. These regions are shown in Figure 1. When a drag operation is detected on a Dockable, the DockingManager class assumes responsibility for keeping track of the mouse coordinates. During dragging, the DockingManager maintains a rectangular outline of the Dockable component in concert with the mouse position. Additionally, the DockingManager queries any DockingPort instance beneath the current mouse cursor for the region enclosing the mouse location,
updating the cursor icon accordingly. If docking is allowed within the current region by the DockingPort, the cursor icon reflects the current region. Otherwise, the cursor icon displays an image indicating that docking will not be allowed.
Figure 2 shows this operation in action. The Dockable "Tasks View" component has been dragged over the DockingPort that contains the "J2EE Navigator". The rectangular outline of the dragged component is maintained by the DockingManager, and the current mouse cursor indicates that the mouse is over the EAST region of the target DockingPort. When the mouse is released, the drag operation ends and the DockingManager attempts to dock the selected Dockable within the DockingPort underneath the mouse cursor. If no DockingPort is underneath the mouse cursor, or the DockingPort underneath the mouse cursor doesn't allow docking within the current docking region, docking fails and the callback method dockingCanceled() is invoked on the selected Dockable. If docking is allowed, then the selected Dockable is removed from its parent component and is added to the target DockingPort with the specified component, region, and resize policy as parameters to the dock() method. The method dockingComplete(String region) is invoked against the target DockingPort and the callback method dockingCompleted() is invoked against the selected Dockable. The docking process is designed to be non-invasive with respect to application code. This is to say that, aside from implementing the Dockable and DockingPort interfaces, there is a minimum of supporting code required to enable docking. Furthermore, in some cases, the Dockable interface itself may be completely foregone in favor of allowing the DockingManager wrap dockable components within a DefaultComponentWrapper (a Dockable instance) automatically. In terms of drag initiation, no supporting listener management need be added to application code. Both overloaded static registerDockable() methods on the DockingManager class take care of listener management transparently. With respect to implementing the DockingPort interface, a DefaultDockingPort class has been provided. Docking, at this point, becomes as simple as adding a DefaultDockingPort to a Container, placing a Dockable or plain Component somewhere else, registering the dockable component via DockingManager.registerDockable(), and dragging the dockable component over to the DockingPort. The simplest docking case involves, appropriately enough, a simple Dockable instance. In this case, explicitly implementing the Dockable interface is not necessary. A plain old AWT Component will do. The only requirement
is to register the Component as dockable via DockingManager.registerDockable() and have a DockingPort present into which the Component may be docked.
This is referred to as a simple case because the dockable component itself is looked upon as a single entity. In order to detect a docking operation, some component must be listening for the start of a drag. While the
DockingManager is responsible for managing listeners internally, it needs to know which Component will be the drag initiator, versus which Component will actually be the dockable component. In the simple case, the dockable
component itself is both the drag initiator and the dockable component. Once registered with the DockingManager, the Component will respond to any drag operations as attempts to initiate docking.
SimpleDemo.java demonstrates the simple case by registering several JPanels of various colors with the DockingManager. This automatically makes the panels docking-enabled (the DockingManager keeps track of Dockable instances representing these panels internally), allowing the user to drag and dock panels in and out of any DockingPort on the screen. The simple case is useful in an academic sense as an introduction to docking features. However, it doesn't make much sense in a real-world application. For a complex GUI, docking features are typically enabled on regions of the screen, encompassing groups of components. Treating a single component as both drag initiator and docking source ends up making the component itself rather useless as the docking functionality would tend to interfere with normal use of the component. A compound case involves underscoring the difference between the drag initiator and the dockable component itself. A perfect case in point would be any windowing system. For a given window (or Frame, in the Swing world), there is, among other subcomponents, a titlebar. The titlebar is the drag initiator for a window, as
shown by the necessity for dragging a window's titlebar in order to move the window itself around the screen. On the other hand, the window itself is considered to be the draggable (or, for our purposes, 'dockable') component.
The Dockable interface contains both getInitiator() and getDockable() methods. The former returns the Component responsible for initiating drag operations on the Dockable instance. The latter is the actual dockable component. In the case of our window analogy, were a window to implement the Dockable interface, getInitiator() would return the window's titlebar and getDockable() would return a reference to the window itself. CompoundDemo.java demonstrates a compound case in which a series of JPanels contain colored labels in their NORTH region to simulate a type of embedded window (much like a JInternalFrame). These panels are similar in appearance to the View panes used by Eclipse. Each panel implements the Dockable interface, returning a reference to its
titlebar label with getInitiator() and a reference to itself via getDockable(). The titlebar text is also used by getDockableDesc(), which in turn serves as tab text in the event a panel is docked into a tabbed interface.
It is important to note that the procedure for enabling docking here is no different than in the simple case. DockingManager.registerDockable() handles docking-enablement in the same fashion as with the simple case, only a Dockable instance is registered instead of a Component. It is also worth noting that the JPanel itself does not directly implement the Dockable interface. The Dockable interface has been designed with flexibility in mind. Unfortunately, the amount of flexibility in a system is usually inversely proportional to its simplicity. In this situation, the Dockable interface mandates implementation of a myriad of methods that aren't even relevant to such a simple, contrived example. To alleviate this problem, the DockableAdapter provides a default implementation of the Dockable interface, allowing subclassing whereby only the relevant methods need be overridden. This is what has been done in CompoundDemo.java to keep the code simple, yet still provide a valid Dockable implementation. |
||||||||||||||
|
It is possible to customize the look and behavior of the DefaultDockingPort for use in applications. For a production quality application, it is probably undesireable to use the default split pane divider, tabbed panes, and borders provided by the DefaultDockingPort, not to mention the cursor icons used during docking operations. To be forced to use the default widgetry would most likely mean docking-enabled components would clash with any polished, professional look and feel used in a produciton app. Therefore, the DefaultDockingPort class has several facilities provided to allow delegation of runtime component creation, cursor generation, and border management.
CursorDemo.java displays a relatively simple example in which custom cursor icons have been used during docking operations on the NORTH dockable panel. This example is, in fact, a complete replica of the CompoundDemo.java shown earlier with a few small additions.
The images have been downloaded from http://www.codeguru.com/java/articles/656.shtml and were creaetd by Jonathan Bingham.
Since our class implements the Dockable interface, it has a public getCursorProvider() method. In CompoundDemo.java, this method returned a null reference, forcing the DockingManager to make use of default cursor icons. In this example, however, getCursorProvider() returns the private instance variable cursorProvider, which may or may not be null. As shown in the buildDockingPort() method, we set the Dockable instance's cursorProvider if it is the NORTH panel. This ensures that a custom set of images will be used for the Dockable in the NORTH panel as it is dragged over various DockingPort regions. All other Dockable instances in the application will continue to use the default set of cursor icons.
The static inner class CursorDelegate is our concrete implementation of the CursorProvider interface. It's a relatively simple class that merely returns a set of images on demand. Because of its simplicity and small size, it fits right into the CursorDemo class and most likely isn't worth creating and entire new top-level class.
It's worth noting that returning an image from every method in this class isn't strictly necessary. The DockingManager, when detecting a null Image returned from a CursorProvider, will simply resort to using the corresponding default icon for the specified DockingPort region. As components are docked into a DefaultDockingPort, a child Container hierarchy may be created. The DockingPort interface mandates that a DefaultDockingPort implements a getDockedComponent() method. If there are no docked components within a DefaultDockingPort, then this method returns a null reference. If there is a single Component docked within it, this component is returned. However, in the event there are multiple components docked within a DefaultDockingPort, getDockedComponent() may return either a JTabbedPane or a JSplitPane. The JTabbedPane contains multiple docked components within the CENTER region of the DefaultDockingPort, while the JSplitPane contains docked components that split the DefaultDockingPort into two sections. Creation of the JTabbedPane and JSplitPane are handled automatically by the DefaultDockingPort whenever dock() is invoked successfully. Their creation, however, may be delegated to any class implementing the SubComponentProvider interface. To assign delegation of these operations to a SubComponentProvider, one must pass an instance into the DefaultDockingPort's setComponentProvider() method. When more than one Component has been docked within a DefaultDockingPort's CENTER region, the DefaultDockingPort places each Component within a JTabbedPane and sets the new tabbed container as its currently docked component. Thus, a call to getDockedComponent() on the DefaultDockingPort will return a reference to this JTabbedPane.
The DefaultDockingPort will, by default, create a JTabbedPane with the zero-argument constructor. In order to override this behavior, one must implement a SubComponentProvider with a customized
createTabbedPane() method. In TabbedPaneDemo.java we have just such an example. Again, this example is derived from CompoundDemo.java with several modifications.
This demo uses a static inner class called ComponentProvider to implement the SubComponentProvider interface. When each DefaultDockingPort is created, a ComponentProvider instance is plugged in via setComponentProvider(). And the constructor for each ComponentProvider takes in an int argument, assigning it to an instance variable named tabPosition. This code is shown as follows:
As shown in TabbedPaneDemo.java, the description sent to each DefaultDockingPort is used to determine a corresponding tab position for use with a JTabbedPane, and each ComponentProvider keeps track of this value. When a Dockable is docked into the center area of a DefaultDockingPort and a JTabbedPane is required, the assigned SubComponentProvider's createTabbedPane() method is invoked.
The ComponentProvider customizes the tab position for each DefaultDockingPort based on its region within the underlying BorderLayout as determined in getTabPosition(). In this fashion, one has the option to customize any feature desired on the JTabbedPane before it is returned to the DefaultDockingPort. In this example, only the tab position has been customized. In practice, JTabbedPane customization is limited to the developer's desires and imagination. It is important to note in this example that the non-tab related methods on the ComponentProvider all inherit from the ComponentProviderAdapter superclass, which returns null or nonsense values. When a split pane is required by the DefaultDockingPort, the DefaultDockingPort class is intelligent enough to recognize null or nonesense split pane values returned by our tab-specific code and resort to default behavior in these instances. This concept will work similarly for split-pane-specific code that returns junk values for the tabbed pane method. As with the tabbed pane, the JSplitPane used for splitting the layout of the DefaultDockingPort may be customized. The JSplitPane is created when a Component is docked in the NORTH, SOUTH, EAST,
or WEST regions of the DefaultDockingPort. Orientation of the JSplitPane is handled by the DefaultDockingPort based on the current docking region and therefore cannot be customized within a SubComponentProvider instance. Other other attributes of the JSplitPane, however, may be customized by a SubComponentProvider.
SplitPaneDemo.java is derived from CompoundDemo.java and, like TabbedPaneDemo.java, it contains a static inner class named ComponentProvider that implements the SubComponentProvider interface. In this case, however, createChildPort(), createSplitPane(), and getInitialDividerLocation() are properly implemented and createTabbedPane() returns a null reference. In this demo, we replace the default JSplitPane with its heavy border and bulky divider in favor of a cleaner looking implementation.
The createSplitPane() method is analogous to the createTabbedPane() method in that, by assigning a custom SubComponentProvider to the DefaultDockingPort that properly implements this method, the creation of the JSplitPane itself has been delegated to the custom class. The getInitialDividerLocation() is a convenience method that allows one to specify the initial proportional divider location of the JSplitPane as a percentage of the vertical or horizontal dimensions of the enclosing DefaultDockingPort. The value returned may fall between 0.0 and 1.0. By default, the DefaultDockingPort uses a value of 0.5 to divide the split pane in half. If getInitialDividerLocation() returns a value that exceeds the bounds of 0.0 to 1.0, the default behavior takes over and a value of 0.5 is assumed. The reason for separating getInitialDividerLocation() from createSplitPane() has to do with component validation, an in-depth explanation of which is outside the scope of this tutorial. However, it is simple enough to explain that in createSplitPane(), when the JSplitPane has first been created, its has yet to be added to and layed out by a parent Container, so its dimensions are (0, 0). Taking a proportional value of these dimension will yield zero, and so any call to setDividerLocation() on the JSplitPane within createSplitPane() would be rendered meaningless. Thus, the call to getInitialDividerLocation() on the assigned SubComponentProvider happens separately and later in the docking process than createSplitPane(). While createTabbedPane() and createSplitPane() are straightforward enough, createChildPort() requires a little added explanation. When two Dockable instances have been added to a DefaultDockingPort in a JSplitPane configuration, the container hierarchy for the DefaultDockingPort itself takes on more than a JSplitPane and the two Dockable components. A DefaultDockingPort may only have one child component. For a split configuration, the getDockedComponent() method will return a reference to a JSplitPane. A JSplitPane, however, may only hold two child components. While this satisfies the need for docking two Components within a DefaultDockingPort, it places a restriction upon further docking within the DockingPort itself. This is best shown in an example. Let us assume that a DefaultDockingPort contains a single child component, and the DockingManager attempts to dock a second Dockable instance within the WEST region. The docking oepration completes successfully and the DefaultDockingPort now contains a JSplitPane, which contains both Dockable components. Subsequently, the DockingManager attempts to dock a third Dockable component in the EAST region of the DefaultDockingPort. Because a JSplitPane cannot hold more than two Components, the new Dockable cannot be added to the split pane itself. This issue is illustrated in Figure 3. ![]()
forcing it to return a new reference every time new components are added to the port.
Instead, the DefaultDockingPort uses sub-ports to manage multiple split configuration dockings as shown in Figure 4. When a DefaultDockingPort is split between two components, the JSplitPane returned by createSplitPane() does not contain each Dockable component directly. Rather, each side of the JSplitPane contains a new DockingPort instance, which in turn contain the Dockable components. Thus, sub-docking is achived by nesting a series of DefaultDockingPorts. The new component in the EAST region is not docked into the root DefaultDockingPort. Instead, it is docked into the EAST region of a child DefaultDockingPort within the top-level JSplitPane. The child DefaultDockingPort splits itself, and the container hierarchy deepens. These child DefaultDockingPorts are generated by the createChildPort() method on the SubComponentProvider interface. In SplitPaneDemo.java, the createChildPort() is as follows:
For all child DockingPorts, the ComponentProvider in our example sets the ComponentProvider for each child port before returning. This is to ensure that each child port will retain the same component creation characteristics of the root DefaultDockingPort. While this isn't absolutely necessary, and is up to the individual developer's discretion, it is recommended from an asthetic point of view. Sub-docking should appear seamless to the end-user, and leaving different component generation behavior on sub-ports will more than likely add a level of asthetic inconsistency to the application, making for an unattractive end-user experience. |
||||||||||||||
|
Swing applications are notorious for displaying a number of poor user interface design choices. One of those design flaws often stems from compounded borders that are created as a result of nesting components within one another. Because tabbed panes and split panes come prebuilt with their own borders, and the text components or panel components placed within them usually have their own borders, this nesting of borders creates a cluttered looking effect that greatly detracts from the user experience of the application as a whole.
This problem becomes a significant issue for any docking framework. Unlike an application with a relatively static component layout, the layout of an application with dockable components is constantly changing in unpredictable ways. It is therefore somewhat difficult and messy to programmatically add and remove borders within the application code itself as components are docked and undocked. The DefaultDockingPort takes these issues into account when performing docking operations. Its default behavior is to do nothing in terms of swapping in and out borders in these situations. However, the framework does provide a BorderManager interface for use with the DefaultDockingPort. A series of things happen as a component is docked and undocked from a DockingPort. The DockingPort interface itself provides dock() and undock() methods. In most circumstances, there is little need to invoke these methods directly, unless constructing an initial UI layout. Instead, the DockingManager assumes responsibility for invoking these methods behind the scenes. When the DockingManager docks a component into a DockingPort, it assumes the component must have come from somewhere. So before invoking dock() on the target DockingPort, it checks the component's existing parent. If the component currently resides within a DockingPort, undock() is invoked against that DockingPort to remove the component. Otherwise, a simple remove() is invoked against the component's parent container. After the component has been removed from its original parent, dock() is invoked against the target DockingPort to dock the component. In the DefaultDockingPort implementation, both dock() and undock() complete with a call to the public method evaluateDockingBorderStatus(), which is specific to DefaultDockingPort and is not a part of the standard DockingPort interface. evaluateDockingBorderStatus() checks the status of the container hierarchy an, if an assigned BorderManager is present, invokes the appropriate method
call against the assigned BorderManager instance. A very high-level overview of this sequence is illustrated in Figure 5.
DefaultDockingPort is intelligent enough to manage its own container hierarchy, adding necessary wrapper components during dock() and removing unnecessary wrapper components during undock(), so by the time evaluateDockingBorderStatus() is invoked, the state of the container hierarchy is predictable. By implementing a BorderManager instance, one is able to have a certain level of control over borders during docking and undocking operations. One only needs to add the BorderManger to the DefaultDockingPort using its setBorderManager() method for the desired behavior to take effect. The BorderManager is designed to manage borders for a specified DockingPort under four different situations. The DefaultDockingPort is responsible for determining its current state and invoking the appropriate BorderManager method, and this happens without any intervention from application code. BorderManager has a method managePortNullChild(DockingPort port) to handle container changes in which the specified DockingPort no longer has any child component docked within it. In this case, it may be desireable to remove all borders from the DockingPort, which would show an empty space on the screen. Or it may be desireable to set a special border to indicate visually that a DockingPort exists in the region and is available for docking. The descision is left to the discretion of the individual developer. The managePortSimpleChild(DockingPort port) method is used for container state changes in which the specified DockingPort ends up with a single child component docked within it. This excludes any cases in which said child component is a JTabbedPane or JSplitPane. In this case, it may be desireable to remove any border from the component itself and set a special border around the specified DockingPort, or vice versa. The point, though, is that this method gives the developer an opportunity to assign a border to one Component and not the other, thus avoiding a situation with nested borders that would detract from the overall end user experience. The managePortTabbedChild(DockingPort port) method is used for container state changes in which the specified DockingPort ends up with a JTabbedPane as its currently docked component. This method may be treated similarly to the managePortSimpleChild(DockingPort port) situation with the exception that invoking getDockedCompoent() against the specified DockingPort will yield the embedded JTabbedPane. This gives the developer opportunity to manage not only the DockingPort border and the JTabbedPane border, but also the border for each component embedded within the JTabbedPane itself. Finally, the managePortSplitChild(DockingPort port) method is used for container state changes in which the specified DockingPort ends up with a JSplitPane as its currently docked component. This method is extremely useful in that, when a JSplitPane is present, by definition, some level of nested docking is taking place. Invoking getDockedCompoent() against the specified DockingPort will yield the embedded JSplitPane. The method imlementation may then proceed to manage the border for the DockingPort, the JSplitPane, the JSplitPane divider, and each component embedded within the JSplitPane. This is, in practice, much less complicated than it may sound. One may, for instance, remove the border on the DockingPort, JSplitPane, and the divider, and set a border on each of the docked components to visually separate them from each other. But further border management down the container hierarchy to drill down inside of child DockingPorts is unnecessary. The very act of docking against a DockingPort implies that the specified DockingPort is, indeed, the deepest split DockingPort within the hierarchy. Sub-docking within the container hierarchy will be handled automatically by subsequent BorderManager calls as child DockingPorts manage their own docking operations. This greatly simplifies the code necessary for a decent implementation of managePortSplitChild(DockingPort port). It is important to note that it is only necessary to assign a BorderManager to the root DefaultDockingPort. During nested docking operations, the DefaultDockingPort will automatically assign its current BorderManager to all child DockingPorts, thus preserving the border management behavior throughout the container hierarchy.
The provided border management example consists of three classes; BorderDemo.java, DemoBorderManager.java, and
DockablePanel.java. They have been separated out into multiple classes based upon logical function
to help demonstrate how custom border management might be included into a more extensive application.
BorderDemo.java is the main class responsible for constructing the application. It instantiates a JFrame, adds several DefaultDockingPorts to the content pane, and docks a series of docking-enabled panels into a preconfigured layout. The construction code in this class is very similar to that used in previous examples. The method buildDockingPort() retrieves a DefaultDockingPort from createDockingPort() and adds a DockablePanel into it.
As shown here, the inner class ComponentProvider is used as the SubComponentProvider for the DefaultDockingPort. This inner class extends the ComponentProviderAdapter class, since only the createChildPort() and createSplitPane() methods are needed for this example. We also see references to our two other classes, DockablePanel and DemoBorderManager. DockablePanel has a getDockable() method that returns a Dockable instance responsible for delegation of its Dockable functionality. What is curiously absent here is the usual call to DockingManager.registerDockable(). Turning to DockablePanel.java, one finds that Dockable registration happens directly within the constructor. DockablePanel uses an inner class DockableImpl to handle its docking-related functionality. This is the object returned by getDockable(), and it is the object that is registered with the DockingManager at the end fo the DockablePanel constructor.
The DockablePanel is merely a generic JPanel with a TitledBorder, a BorderLayout, and a gray sidebar in the EAST region to act as a drag initiator. By itself, this is nothing we haven't seen before. The DemoBorderManager class is where the real substance of the example resides. It is designed specifically to work with the DockablePanel class, so its implementation is by no means generic. The private method getDesiredBorder() is responsible for creating a border for docked components.
This method is able to pull out the description of a DockablePanel, if one exists, and return a TitledBorder. Otherwise, it returns a dummy TitledBorder without a caption. The managePortNullChild() method merely assigns the dummy border to the entire DockingPort.
The managePortSimpleChild() method strips any border from the docked component and assigns the desired TitledBorder, complete with descriptive text, to the entire DockingPort.
The managePortTabbedChild() method removes the border from the DockingPort itself and assigns a desired border to each Component within the docked JTabbedPane. Because of the way getDesiredBorder() works, each TitledBorder in the JTabbedPane will match the current tab title.
The managePortSplitChild() method removes borders from the DockingPort, JSplitPane, and child DockingPorts. It then adds a desired border to each docked component in the split pane configuration.
If the code involved in writing a BorderManager seems somewhat verbose, that's because it is. Or, at least, can can be cumbersome to write over and over again for multiple applications. Fortunately, the basic algorithms involved are pretty straightforward. Consequently, the StandardBorderManager class is provided by the framework to handle the type of border management that will most likely be desired in the majority of applications. It is worth looking over the JavaDocs, as well as the source code, for the StandardBorderManager. Its functionality won't be detailed within this tutorial, as doing so would merely be a reiteration of the JavaDoc descriptions. |
||||||||||||||
It makes sense at a certain juncture to put all of this together into a workable demonstration to see how one might go about building a docking-enabled application. The example ElegantDemo.java is intended to be just such a demonstration. The enclosing frame constructs a series of ElegantPanels and a series of ElegantDockingPorts. A set of JSplitPanes are arranged to divide the frame's content pane into four sections, much like the J2EE Perspective in IBM's WSAD. Each section contains a ElegantDockingPort. ElegantPanels are then docked in such as fashion as to
simulate a the series of Views commonly shown in WSAD. A ShadowBorder has been used in conjunction with the StandardBorderManager to simulate Eclipse, and a gradient effect has been added to the titlebar for each
ElegantPanel, again like Eclipse. Custom classes have been implemented as Dockable and SubComponentProvider. They reside at the top-level to maintain a sense of separation between the application code and the docking-related code.
The ElegantDemo class is responsible for representing the application frame and constructing the initial component layout. In a real world situation, this represents the application itself. Aside from making use of the ElegantPanel and ElegantDockingPort classes, there is nothing in this class in the way of docking-related code. Its sole purpose is to play the role of application code. The class ElegantDockingPort.java is a relatively simple subclass of DefaultDockingPort. Its constructor automatically assigns a SubComponentProvider and BorderManager. It also provides an overloaded add(ElegantPanel panel) method to handle docking. There is nothing particularly special about this add() method as it merely wraps a call to dock(). Its only purpose is to provide a facade to the ElegantDemo class to hide from it the underlying concept of docking. Thus, ElegantDemo invokes add() on each ElegantDockingPort when constructing the GUI without knowledge of a dock() happening behind the scenes.
What is interesting to note within the ElegantDockingPort constructor is that we're making use of the StandardBorderManager to manage or border behavior for us at runtime during docking and undocking operations. There is no extra border management code involved throughout the rest of the application. The ElegantDockingPort uses ChildComponentDelegate.java as its SubComponentProvider. The method implementations are straightforward.
This SubComponentProvider creates a JTabbedPane with its tabs on the bottom, just like the standard Eclipse GUI. It also creates a JSplitPane without any borders and sets the initial divider location to be half of the available container size. Finally, it ensures that all child ports will share the same SubComponentProvider instance. This is nothing we haven't seen before. Turning our attention to the ElegantPanel.java, we notice once again that the class has been largely shielded from docking-related code. The purpose of this class is to provide a visual representation of the View that is moved around the screen. The code within this class is geared toward drawing a gradient titlebar in the northern region of the panel. Our only inclination that this class may be docking-related is the dockable instance variable and the method getDockable(), which returns an instance of DockableImpl.java. As with the BorderDemo example, the ElegantPanel class contains no docking registration with the DockingManager.
Looking inside the DockableImpl class, we find it is an extension of DockableAdapter. In this example, there are only four Dockable methods that are of immediate use:
Taking a look back at ElegantPanel, we remember that the DockableImpl was constructed by passing in the ElegantPanel and titlebar JLabel arguments As shown in DockableImpl, the ElegantPanel is returned by getDockable(), and the titlebar JLabel is returned by getInitiator(). This is enough for us to register with the DockingManager, and we do so at the very end of the DockableImpl constructor.
The final class involved in the ElegantDemo is the ShadowBorder. The source code for ShadowBorder.java is available. However, since it has little do do with docking, an explanation of is is outside the scope of this tutorial. Using a prebuilt docking framework can greatly simplify docking-enabled applications that would otherwise include docking-related code in with the application itself. This has been a rough overview of the highlights of a particular docking framework. Full source code is available, currently under the MIT license, and comments and contributions toward the advancement of the framework are greatly appreciated. |
||||||||||||||
|
||||||||||||||
|
|