Geertjan's Blog

  • May 21, 2010

Pluggable JXTaskPane

Geertjan Wielenga
Product Manager
Some of the students on the recent NetBeans Platform Certified Training at the TU Braunschweig are creating an application in the medical domain. I was shown their application, which included a JXTaskPane from the SwingX project.

Each task in the task pane represents a different department at the hospital. E.g., one task for "Anamnesis" and another task for "Radiology", each with their own labels, icons, and actions. So then, I stated, it would be completely logical for each task to be provided by a different module since each module could then more easily be created by different departments, the application could be created for different users, and the users themselves could potentially have a choice in the features available in their local distribution. Today I experimented with this scenario. And, as you can see, my medical application has one module that displays tasks, with supporting modules coming from each department in the hospital (currently two only for demo purposes):

To the end user, there's no difference, of course, since the UI is the same regardless of whether the application is modular or not:

In the TaskDisplayer module, I have a TopComponent with the following added to the constructor:

//Read the "tasks" folder in the layer file via the NetBeans Filesystems API:
FileObject tasksFolder = FileUtil.getConfigFile("tasks");
//Get all the children, which represent tasks:
FileObject[] tasks = tasksFolder.getChildren();
//Create a JXTaskPaneContainer:
JXTaskPaneContainer taskpanecontainer = new JXTaskPaneContainer();
//Create the JXPanel:
JXPanel panel = new JXPanel();
panel.setLayout(new BorderLayout());
//Iterate through the tasks in the order
//specified by the "position" attribute:

for (FileObject task : FileUtil.getOrder(Arrays.asList(tasks), true)) {
String name = task.getName();
String room = task.getAttribute("location").toString();
//Create a new JXTaskPane
//for each task:

JXTaskPane taskpane = new JXTaskPane();
taskpane.setTitle(name + " (" + room + ")");
//Add Actions registered in the layer:
List<? extends Action> actions = Utilities.actionsForPath("Actions/" + name);
for (Action action : actions) {
//Add the JXTaskPane to the JXTaskPaneContainer:
//Add the JXTaskPaneContainer to the JXTaskPanel:
panel.add(taskpanecontainer, BorderLayout.CENTER);
panel.setPreferredSize(new Dimension(250, 200));
//Add the JXTaskPanel to the TopComponent:
add(panel, BorderLayout.CENTER);

And then, in each module for the departments, I have content like this in the layer file, which is what is read by the code above:

<folder name="tasks">
<folder name="Radiology">
<attr name="location" stringvalue="Sector 3B"/>
<attr name="position" intvalue="200"/>
<folder name="Actions">
<folder name="Radiology">
<file name="org-medical-task-radiology-AnalyzeAction.instance">
<attr name="delegate" newvalue="org.medical.task.radiology.AnalyzeAction"/>
<attr name="displayName" bundlevalue="org.medical.task.radiology.Bundle#CTL_AnalyzeAction"/>
<attr name="instanceCreate" methodvalue="org.openide.awt.Actions.alwaysEnabled"/>
<attr name="noIconInMenu" boolvalue="false"/>
<attr name="position" intvalue="200"/>
<file name="org-medical-task-radiology-ScanAction.instance">
<attr name="delegate" newvalue="org.medical.task.radiology.ScanAction"/>
<attr name="displayName" bundlevalue="org.medical.task.radiology.Bundle#CTL_ScanAction"/>
<attr name="instanceCreate" methodvalue="org.openide.awt.Actions.alwaysEnabled"/>
<attr name="noIconInMenu" boolvalue="false"/>
<attr name="position" intvalue="100"/>

Finally, it would even be possible to make supplemental tasks available depending on the actions selected in the tasks above. I.e., the "Radiology" task would only be added once "Assess Symptoms" has been completed in the "Anamnesis" task. To really optimize performance, you could load a module on demand, i.e., only load the "Radiology" module at the time that that module becomes relevant.

And, to make the solution even better, each task could be created from a Node, meaning that each task would then have its own context (i.e., its own Lookup), so that it could be context sensitive, i.e, depending on the current patient (a Patient POJO in the Lookup), different capabilities could be made available.

In other news. For more on NetBeans Platform in the medical domain, read Interview: NetBeans Platform Helps Advance Biomechanics Research, which was published today.

Join the discussion

Comments ( 4 )
  • guest Friday, August 19, 2011

    Where I can download the sample project?

  • Geertjan Saturday, August 20, 2011

    What sample project are you talking about? Nowhere in this blog entry is a sample project mentioned. You're going to have to create the project yourself, using the instructions above to help you.

  • Craig McDonnell Monday, January 13, 2014

    Hi Geertjan,

    Have you seen any projects that present BeanTreeViews or the like inside of a JXTaskPane? I've described my approach in the following forum post and would be glad to hear your thoughts.


    Keep up the good work!

  • Craig McDonnell Monday, January 13, 2014

    Hi Geertjan!

    I'm working on a project that contains a TopComponent a bit like Outlook's task bar. As it's reasonable to restrict the user to a single selection across all the ExplorerManagers, and since this is simpler than tracking which JPanel has focus, this is what I'm doing until there's a compelling reason to do otherwise.

    Taking Outlook's task bar as an example, here's what I'm doing:

    I have a TaskBarTopComponent that contains a TaskBar (JPanel containing L2FPROD's JTaskPane) with many Sections (JPanel to add to TaskBar inside JTaskPaneGroup).

    * Each Section implements ExplorerManager.Provider - This allows the BeanTreeView or ListView to look up what it needs to display.

    * Each Section also implements Lookup.Provider - This exposes the ExplorerManager selected nodes that are eventually returned by the TopComponent - The lookup is created using ExplorerUtils.createLookup(explorerManager, actionMap).

    * TaskBar implements Lookup.Provider by returning the Lookup of the "active" Section or Lookup.EMPTY if there's no selection.

    * TopComponent uses Lookups.proxy on TaskBar so it can be refreshed when TaskBar's active Section changes.

    When TaskBar observes any Section's ExplorerManager fire a PROP_SELECTED_NODES property change.

    * The other ExplorerManager selections are cleared - setSelectedNodes(new Node[0])

    * The "active" Section variable is stored

    * The TopComponent Lookups.Proxy is queried to flush through the change

    * The TopComponent activated nodes property is updated

    This approach seems to be working well for me, but I'd be glad to hear your thoughts.

    PS. Keep up the good work!

Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.