Java, Programming

NetBeans Platform 6.9: Working with Window System

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.

In this two-part article series by Jürgen Petri, author of NetBeans Platform 6.9 Developer’s Guide, you will be introduced to the most important topics relating to working with the NetBeans Window System. Not only did you learn how to create new windows, that is, TopComponents, but you also learned how to position them, group them, and extend their persistence. In this article series we will learn:

·         How to define views

·         How to position views in the main window

·         How to customize the default window layout

·         How to group views so that they open and close as a unit

·         How to change the persistence of views across restarts of the application

Window System

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:

·         How to define views

·         How to position views in the main window

Rest is covered in the advance segment of Automation Window Lifecycle Management

 

Creating a window

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 theTopComponent 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.

1.In the Projects window, right-click the TaskEditor module project node and choose New | Window.

2.On the first page of the wizard select Editor for Window Position and Open on Application Start. Click Next.


3.In the next page of the wizard, type TaskEditor in Class Name Prefix. This prefix is used for all the generated files. It is possible to specify an icon that will be displayed in the tab of the new window, but let’s skip that for the moment. Click Finish and all the files are generated into your module source structure.


4.Next, open the newly created TaskEditorTopComponent and drag the TaskEditorPanel from the Palette, which is where you put it at the end of the last chapter, onto the form.

5.The size of the component automatically adjusts to the required size of he panel. Position the panel with the preferred spacing to the left and top and activate the automatic resizing of the panel in horizontal and vertical direction. The form should now look similar to the following screenshot:


6.Start the application. You now see a tab containing the new TaskEditor Window, which holds your form.


Examining the generated files

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:

·         For efficient resource usage, the generated TopComponent is implemented as a singleton. A private constructor prohibits its incorrect usage from outside by disallowing direct instantiation of the class. The static attribute instance holds the only instance in existence. The static method getDefault creates and returns this instance if necessary on demand. Typically, getDefault should never be called directly. Instead of this, you should use findInstance, which delegates to getDefault if necessary. findInstancetries to retrieve the instance using the Window Manager and the ID of the TopComponent before falling back to the singleton instance. This ensures the correct usage of persistent information.

·         The constructor creates the component tree for the TaskEditorTopComponent by calling the method init Components(). This method contains only code generated via the NetBeans “Matisse” Form Builder and is read-only in the NetBeans Java editor. You can change the code in this method using the Form Builder’s Property Sheet, as will be shown later.

·         The static property PreferredID holds the TopComponent ID used for identification of theTopComponent. As indicated by its name, the ID can be changed by the Window System, if name clashes occur. The ID is used throughout all the configuration files.

·         The methods componentOpened() and componentClosed() are part of the lifecycle of theTopComponent.

·         You learn about the method getPersistenceType() later, in the section about the persistence ofTopComponents.

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 callTaskEditorTopComponent.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 theWSTCREF 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 theSystemFileSystem.

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.

·         Let’s have a look at the folder Windows2Windows2 contains a folder named Components, which contains a virtual file with the name of the TopComponent and the extension .settings. This .settings file redirects to the real settings file. It is used to make the configuration known to the Window System.

·         In addition, the Windows2 folder contains a folder named Modes, which contains a folder named editor. Modes represent the possible positions at which TopComponents can be shown in the application. The editor folder contains a .wstcref file for our TopComponent, which refers to the real WSTCREF file. This registers the TopComponent in the mode editor, so it shows up where typically editor windows are opened, which is the central part of the main window.

·         Next , take a look at the folder Actions. It contains a folder named Window which contains a file declaring the action opening the TaskEditorTopComponent. The name is typically following Java class naming conventions with dots replaced by dashes and ending in .instance. The declaration of the virtual file itself consists of three critical parts. The attribute component describes how to create the component (methodvalue declares which method to call). The attribute displayName describes the default action name as shown in the example, in menu items. A possible declaration is the bundle value which describes the bundle and key to use to retrieve the display name. The attributeinstanceCreate uses a static method call to create a real action to use.

·         The folder Menu describes the application main menu. The folder Window contains a .shadow file. The attribute originalFile uses the full path in the SystemFileSytem to delegate to the original action declaration. As described above, .shadow files are used as symbolic links to real-defined virtual files. This declaration adds the action to the real menu bar of the application.

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.

Tagged , , , , , , , , , , ,