





















































(For more resources on NetBeans, see here.)
Large desktop applications need to provide many different views for visualizing data. These views have to be managed and shown and the NetBeans Platform handles these requirements for you out of the box via its docking framework.
While it once might have been sufficient for a docking framework to provide static fixed window layouts, today the user expects far more flexibility. Windows should be able to be opened, movable, and, generally, customizable at runtime. The user tends to assume that the positions of views are modifiable and that they persist across restarts of the application. Not only that, but applications are assumed to be so fiexible that views should be detachable from the application's main window, enabling them to be displayed on multiple monitors at the same time. While once the simple fact of the availability of menus and toolbars was sufficient, today a far more dynamic handling is needed so that window content can be adapted dynamically. Connected to these expectations of flexibility, plugins are increasingly becoming a standard technology, with the user assuming their windows to be pluggable, too.
In short, the requirements for window management have become quite complex and can only be met by means of an external docking framework, otherwise all these various concerns would need to be coded (and debugged, tested, and maintained) by hand. The NetBeans Platform provides all of these features via its docking framework, known as the NetBeans Window System. It also provides an API to let you programmatically access the window system. Together, the window system and its API fulfill all the requirements described above, letting you concentrate on your domain knowledge and business logic rather than on the work of creating a custom window management facility for each of your applications.
This part of the article teaches you the following:
Rest is covered in the second part of this article series.
The NetBeans Window System simplifies window management by letting you use a default component for displaying windows. The default component, that is, the superclass of all windows, is the TopComponent class, which is derived from the standard JComponent class. It defines many methods for controlling a window and handles notification of main window system events.
The WindowManager is the central class controlling all the windows in the application. Though you can implement this class yourself, this is seldom done as normally the default WindowManager is sufficient. Similarly, you typically use the standard TopComponent class, rather than creating your own top-level Swing components. In contrast to the TopComponent class, the default WindowManager cannot manage your own top-level Swing components, so these cannot take advantage of the Window System API.
Now let's create a TopComponent and let it be an editor for working with tasks. This is done easily by using the New Window wizard.
You have used a wizard to create a new TopComponent. However, the wizard did more than that. Let's take a look at all the files that have been created and at all the files that have been modified, as well as how these files work together.
The only Java class that was generated is the TopComponent that will contain the TaskEditor, shown as follows:
@ConvertAsProperties(dtd = "-//com.netbeansrcp.taskeditor//TaskEditor//
EN", autostore = false)
public final class TaskEditorTopComponent extends TopComponent {
private static TaskEditorTopComponent instance;
/** path to the icon used by the component and its open action */
// static final String ICON_PATH = "SET/PATH/TO/ICON/HERE";
private static final String PREFERRED_ID = "TaskEditorTopComponent";
public TaskEditorTopComponent() {
initComponents();
setName(NbBundle.getMessage(TaskEditorTopComponent.class,
"CTL_TaskEditorTopComponent"));
setToolTipText(NbBundle.getMessage(TaskEditorTopComponent.class,
"HINT_TaskEditorTopComponent"));
// setIcon(ImageUtilities.loadImage(ICON_PATH, true));
}
/**This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
// <editor-fold defaultstate="collapsed" desc="Generated Code">
private void initComponents() {
javax.swing.GroupLayout layout = new javax.swing.
GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.
Alignment.LEADING).addGap(0, 555, Short.MAX_VALUE));
layout.setVerticalGroup(layout.createParallelGroup(
javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 442, Short.MAX_VALUE)
);
}// </editor-fold>
// Variables declaration - do not modify
// End of variables declaration
/**
* Gets default instance. Do not use directly: reserved for
*.settings files only,
* i.e. deserialization routines; otherwise you could get a
non-deserialized instance.
* To obtain the singleton instance, use {@link #findInstance}.
*/
public static synchronized TaskEditorTopComponent getDefault() {
if (instance == null) {
instance = new TaskEditorTopComponent();
}
return instance;
}
/**
* Obtain the TaskEditorTopComponent instance. Never call {
@link #getDefault} directly!
*/
public static synchronized TaskEditorTopComponent findInstance() {
TopComponent win = WindowManager.getDefault().findTopComponent
(PREFERRED_ID);
if (win == null) {
Logger.getLogger(TaskEditorTopComponent.class.getName()).
warning("Cannot find " + PREFERRED_ID + " component.
It will not be located properly in the window system.");
return getDefault();
}
if (win instanceof TaskEditorTopComponent) {
return (TaskEditorTopComponent) win;
}
Logger.getLogger(TaskEditorTopComponent.class.getName()).
warning("There seem to be multiple components with the '" +
PREFERRED_ID
+ "' ID. That is a potential source of errors and
unexpected behavior.");
return getDefault();
}
@Override
public int getPersistenceType() {
return TopComponent.PERSISTENCE_ALWAYS;
}
@Override
public void componentOpened() {
// TODO add custom code on component opening
}
@Override
public void componentClosed() {
// TODO add custom code on component closing
}
void writeProperties(java.util.Properties p) {
// better to version settings since initial version
as advocated at
// http://wiki.apidesign.org/wiki/PropertyFiles
p.setProperty("version", "1.0");
// TODO store your settings
}
Object readProperties(java.util.Properties p) {
if (instance == null) {
instance = this;
}
instance.readPropertiesImpl(p);
return instance;
}
private void readPropertiesImpl(java.util.Properties p) {
String version = p.getProperty("version");
// TODO read your settings according to their version
}
@Override
protected String preferredID() {
return PREFERRED_ID;
}
}
As expected, the class TaskEditorTopComponent extends the TopComponent class.
Let's look at it more closely:
What does the Java code do and not do?
The Java code only defines the visual aspects of the TaskEditorTopComponent and manages the singleton instance of this component. In no way does the code describe how and where the instance is shown. That's the task of the two XML files, described below.
Two small XML files are created by the wizard. The first is the TopComponent's settings file:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE settings PUBLIC "-//NetBeans//DTD Session settings 1.0//EN"
"http://www.netbeans.org/dtds/sessionsettings-1_0.dtd">
<settings version="1.0">
<module name="com.netbeansrcp.taskeditor" spec="1.0"/>
<instanceof class="org.openide.windows.TopComponent"/>
<instanceof class="com.netbeansrcp.taskeditor.
TaskEditorTopComponent"/>
<instance class="com.netbeansrcp.taskeditor.TaskEditorTopComponent"
method="getDefault"/>
</settings>
The settings file describes the persistent instance of the TopComponent. As you can see, the preceding configuration describes that the TopComponent belongs to the module TaskEditor in the specification version "1.0" and that it is an instance of the types TopComponent and TaskEditorTopComponent. Also described is that the instance that is created is done so using the method call TaskEditorTopComponent.getDefault().
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE tc-ref PUBLIC "-//NetBeans//DTD Top Component in Mode Properties
2.0//EN" "http://www.netbeans.org/dtds/tc-ref2_0.dtd">
<tc-ref version="2.0" >
<module name="com.netbeansrcp.taskeditor" spec="1.0"/>
<tc-id id="TaskEditorTopComponent"/>
<state opened="true"/>
</tc-ref>
The WSTCREF (window system creation file) describes the position of the TopComponent within the main window. This becomes clearer with the following file. The other important information in the WSTCREF file is the opened state at application start.
Typically, you do not have to change these two configuration files by hand. This is not true for the following file, the layer.xml, which you often need to change manually, to register new folders and files in the filesystem.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE filesystem PUBLIC "-//NetBeans//DTD Filesystem 1.2//EN" "http://
www.netbeans.org/dtds/filesystem-1_2.dtd">
<filesystem>
<folder name="Actions">
<folder name="Window">
<file name="com-netbeansrcp-taskeditor.TaskEditorAction.instance">
<attr name="component"
methodvalue="com.netbeansrcp.taskeditor.
TaskEditorTopComponent.findInstance"/>
<attr name="displayName"
bundlevalue="com.netbeansrcp.taskeditor.
Bundle#CTL_TaskEditorAction"/>
<attr name="instanceCreate" methodvalue="org.openide.windows.
TopComponent.openAction"/>
</file>
</folder>
</folder>
<folder name="Menu">
<folder name="Window">
<file name="TaskEditorAction.shadow">
<attr name="originalFile" stringvalue="Actions/Window/com
netbeansrcp-taskeditor-TaskEditorAction.instance"/>
</file>
</folder>
</folder>
<folder name="Windows2">
<folder name="Components">
<file name="TaskEditorTopComponent.settings"
url="TaskEditorTopComponentSettings.xml"/>
</folder>
<folder name="Modes">
<folder name="editor">
<file name="TaskEditorTopComponent.wstcref"
url="TaskEditorTopComponentWstcref.xml"/>
</folder>
</folder>
</folder>
</filesystem>
The layer.xml is integrated into the central registry (also known as the SystemFileSystem) using the via a registration entry in the module's manifest file. The SystemFileSystem is a virtual filesystem for user settings. Each module can supply a layer file for merging configuration data from the module into the SystemFileSystem.
The Window System API and the Actions API reserve a number of folders in the central registry for holding its configuration data. These folders enable specific subfolders and files relating to window system registration to be added to the filesystem.
As a result, important parts of the Window System API are not called programmatically, but are simply used declaratively. Declarative aspects include configuration and the positioning of windows, as well as the construction of the menu.
In addition, you discovered that the wizard for creating TopComponents always creates singleton views. If you would like to change that, you need to adapt the code created by the wizard. For the time being, it is sufficient to use the singleton approach, particularly as it is more resource-friendly.