Software Engineer and NASA contractor in the Washington D.C. area. Specializes in Java, the NetBeans Platform, D3.JS and ANSI C. Sean is a DZone MVB and is not an employee of DZone and has posted 9 posts at DZone. You can read more from them at their website. View Full User Profile

JavaFX Accordion Toolbar for the NetBeans Platform

05.28.2013
| 5377 views |
  • submit to reddit

In a previous tutorial in the Integrating JavaFX with the NetBeans Platform series we showed how to replace the NetBeans Swing MenuBar with a JavaFX Accordion type control which we called the AccordionMenu.  This JavaFX AccordionMenu  was then added to a NetBeans Platform SlideIn TopComponent.  This freed up value vertical space as well as provided a ButtonMenu interface that was more conducive to Touch Screen displays.  When not in use this menu slides in and minimizes automatically. 

You can find that tutorial here:  http://netbeans.dzone.com/articles/javafx-accordion-slide-out

That's great for Kiosk displays or HUD systems that may only need interaction on occasion, or maybe a NASA ground system  that needs to show a large 3D Visualization.  But what about the Toolbars? In the previous tutorial we just hid them to maximize the vertical space.  So now let's extend the Accordion concept to Toolbars.  It's easy!

Again JavaFX is part of the solution as it provides some capabilities that the base Swing components available in the NetBeans Platform do not.  To fully understand and follow this tutorial you will need to start with the previous tutorial mentioned above.  As stated in that article and again here both of these tutorials are evolved from the following links:

http://netbeans.dzone.com/articles/how-integrate-javafx-netbeans

http://netbeans.dzone.com/articles/how-integrate-javafx-netbeans-part2

These articles were written by Geertjan Wielenga  and it will become clear that much of the base code to accomplish this article was extended from Geertjan's example.  Actually... a better term would be "blatently copied" but let's not argue semantics.   Thanks again Geertjan!

Similar articles to this that may be helpful are below

http://netbeans.dzone.com/articles/javafx-fxml-meets-netbeans 
http://netbeans.dzone.com/articles/how-embed-javafx-chart-visual
http://netbeans.dzone.com/articles/how-integrate-javafx-netbeans-part3
http://netbeans.dzone.com/articles/how-integrate-javafx-netbeans-part4
http://netbeans.dzone.com/articles/how-integrate-javafx-netbeans-part5

All these articles are loosely coupled in a tutorial arc towards explaining and demonstrating the advantages of integrating JavaFX into the NetBeans Platform. 
We begin here assuming you have proceeded through the previous tutorial which replaces the MenuBar.

Step 1. Hide the default Toolbars: 

import org.openide.modules.ModuleInstall;

public class Installer extends ModuleInstall {

    @Override
    public void restored() {
            System.setProperty("netbeans.winsys.no_toolbars", "true");
    }
}

Step 2:  Build an "AccordionToolbar" using JavaFX

We will use a similar pattern to the AccordionMenu of the previous tutorial except much simpler.  In fact we can completely reuse the buildToolBarStructure() and getInputStream() methods from Geertjan's Toolbar tutorial.  However we need to have a custom createScene() workflow that is more like a simplified version of the AccordionMenu.  The basic algorithm is as such:

Create a component that extends JFXPanel
Implement the standard Platform.runLater() pattern for creating a JavaFX scene
Loop through each top level file object in the 'Toolbars' folder of the application file system:
Create a JavaFX TitledPane for each file object
Create a JavaFX ToolBar for each TitledPane
Add ButtonMenu items with icons and Actions to ToolBar
Add ToolBar to TitledPane
Add TitledPane to JavaFX VBox component
Add VBox to the scene
The JavaFX ToolBar component serves as a 1:1 replacement for the Swing Toolbar in this context so nothing new here.  However the JavaFX VBox/TitledPane combination provides some new functionality via a space saving collapsible view with some nice animation.  The previous tutorial used an Accordion pane but Accordions animate such that only one segment can be seen at a given moment.  The VBox/TitledPane combo allows the user to keep open multiple segments if so desired, which is more in line with how Toolbars behave.

Below is the code for my AccordionToolbar class.  You will see where I borrowed heavily from Geertjan's example and the previous AccordionMenu tutorial:

import java.awt.image.RenderedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TitledPane;
import javafx.scene.control.ToolBar;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javax.imageio.ImageIO;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.SwingUtilities;
import org.openide.awt.Actions;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.util.Exceptions;
import org.openide.util.ImageUtilities;

/**
 *
 * @author SPhillips (King of Australia)
 */
public class AccordionToolbar extends JFXPanel{
    
    public VBox vBoxPane;
    public String transparentCSS = "-fx-background-color: rgba(0,100,100,0.1);";
    
    public AccordionToolbar() {
        super();
        this.setBorder(null);        
        // create JavaFX scene
        Platform.setImplicitExit(false);
        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                createScene(); //Standard Swing Interop Pattern
            }
        });        
    }
    private void createScene() {
        FileObject toolbarsFolder = FileUtil.getConfigFile("Toolbars");
        FileObject[] toolbarKids = toolbarsFolder.getChildren();
        //for each toolbar folder need to create a TilePane and add it to an Accordion
        List<TitledPane> titledPaneList = new ArrayList<>();
        for (FileObject toolbarKid : FileUtil.getOrder(Arrays.asList(toolbarKids), true)) {
            ToolBar newToolbarFromFileObject = new ToolBar();
            //Blatently ripped off from Geertjan's example, similar to the MenuBar example
            try {
                buildToolBarStructure(toolbarKid, newToolbarFromFileObject);
            } catch (FileNotFoundException ex) {
                Exceptions.printStackTrace(ex);
            }            
            //The new toolbar can be added directly to the TitledPane
            TitledPane newTitledPaneFromFileObject = new TitledPane(toolbarKid.getName(), newToolbarFromFileObject );
            newTitledPaneFromFileObject.setAnimated(true);
            newTitledPaneFromFileObject.setStyle(transparentCSS);
            titledPaneList.add(newTitledPaneFromFileObject);
        }
        Group g = new Group();
        Scene scene = new Scene(g, 400, 400,new Color(0.0,0.0,0.0,0.0));
        scene.setFill(null);
        g.setStyle(transparentCSS);
        //Instead of an Accordion... a custom VBox allows us to leave sections expanded 
        vBoxPane = new VBox();
        vBoxPane.setStyle(transparentCSS);
        vBoxPane.setFillWidth(true);
        vBoxPane.getChildren().addAll(titledPaneList);
        g.getChildren().add(vBoxPane);
        setScene(scene);
        
        validate();
        this.setOpaque(true);
        this.setBackground(new java.awt.Color(0.0f, 0.0f, 0.0f, 0.0f));
    }
    
    private static void buildToolBarStructure(FileObject oneMainToolBarKid, ToolBar newToolbarFromFileObject) throws FileNotFoundException {
        for (FileObject oneSecondLevelToolBarKid : oneMainToolBarKid.getChildren()) {
            final Action instanceObj =
                    FileUtil.getConfigObject(
                    oneSecondLevelToolBarKid.getPath(),
                    Action.class);
            //Name stuff:
            String name = (String) instanceObj.getValue(Action.NAME);
            String cutAmpersand = Actions.cutAmpersand(name);
            //Icon stuff:
            Icon icon = (Icon) instanceObj.getValue(Action.SMALL_ICON);
            ImageView iv = new ImageView();
            //Optionally, include the name in the button:
            //Button toolbarButton = new Button(cutAmpersand);
            Button toolbarButton = new Button();
            if (icon != null) {
                java.awt.Image awtImage = ImageUtilities.icon2Image(icon);
                Image image = new Image(getInputStream(awtImage));
                iv.setImage(image);
                toolbarButton.setGraphic(iv);
            }
            toolbarButton.setOnAction(new EventHandler<ActionEvent>() {
                @Override
                public void handle(final ActionEvent t) {
                    try {
                        SwingUtilities.invokeAndWait(new Runnable() {
                            @Override
                            public void run() {
                                java.awt.event.ActionEvent event =
                                        new java.awt.event.ActionEvent(
                                        t.getSource(),
                                        t.hashCode(),
                                        t.toString());
                                instanceObj.actionPerformed(event);
                            }
                        });
                    } catch (InterruptedException ex) {
                        Exceptions.printStackTrace(ex);
                    } catch (InvocationTargetException ex) {
                        Exceptions.printStackTrace(ex);
                    }
                }
            });
            newToolbarFromFileObject.getItems().add(toolbarButton);
        }
    }    
    private static InputStream getInputStream(java.awt.Image image) {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        InputStream fis = null;
        try {
            ImageIO.write((RenderedImage) image, "png", os);
            fis = new ByteArrayInputStream(os.toByteArray());
        } catch (IOException ex) {
            Exceptions.printStackTrace(ex);
        }
        return fis;
    }    
}


Step 3:  Build a Slide in TopComponent for the new AccordionToolbar

Just like in the AccordionMenu tutorial now you can add your AccordionToolbar component to a slidein TopComponent.  Now that you have a JFXPanel Swing Interop component, your NetBeans Platform TopComponent doesn't need to know about JavaFX.  Use the Window wizard and select Left Sliding In as a mode.  I would also advise making this component not closable, otherwise the user could lose the ability to use the toolbar.  Here are the annotations and constructor code in my TopComponent:

@ConvertAsProperties(
        dtd = "-//javafxwizard.jfxtool//SlidingAccordionToolbar//EN",
        autostore = false)
@TopComponent.Description(
        preferredID = "SlidingAccordionToolbarTopComponent",
        //iconBase="SET/PATH/TO/ICON/HERE", 
        persistenceType = TopComponent.PERSISTENCE_ALWAYS)
@TopComponent.Registration(mode = "leftSlidingSide", openAtStartup = true)
@ActionID(category = "Window", id = "javafxwizard.jfxtool.SlidingAccordionToolbarTopComponent")
@ActionReference(path = "Menu/JavaFX" /*, position = 333 */)
@TopComponent.OpenActionRegistration(
        displayName = "#CTL_SlidingAccordionToolbarAction",
        preferredID = "SlidingAccordionToolbarTopComponent")
@Messages({
    "CTL_SlidingAccordionToolbarAction=SlidingAccordionToolbar",
    "CTL_SlidingAccordionToolbarTopComponent=Accordion Toolbar",
    "HINT_SlidingAccordionToolbarTopComponent=A Sliding Accordion Toolbar"
})
public final class SlidingAccordionToolbarTopComponent extends TopComponent {

    public AccordionToolbar accordionToolbar;
    
    public SlidingAccordionToolbarTopComponent() {
        initComponents();
        setName(Bundle.CTL_SlidingAccordionToolbarTopComponent());
        setToolTipText(Bundle.HINT_SlidingAccordionToolbarTopComponent());
        putClientProperty(TopComponent.PROP_CLOSING_DISABLED, Boolean.TRUE);
        putClientProperty(TopComponent.PROP_UNDOCKING_DISABLED, Boolean.TRUE);
        putClientProperty(TopComponent.PROP_KEEP_PREFERRED_SIZE_WHEN_SLIDED_IN, Boolean.TRUE);
        setLayout(new BorderLayout());
        //Standard JFXPanel Swing Interop Pattern
        accordionToolbar = new AccordionToolbar();
        //transparency
        Color transparent = new Color(0.0f, 0.0f, 0.0f, 0.0f);
        accordionToolbar.setOpaque(true);
        accordionToolbar.setBackground(transparent);
        this.add(accordionToolbar);
        this.setOpaque(true);
        this.setBackground(transparent);
        this.setBorder(null);
    }

Step 4.  See how great it looks

We now have a slide out collapsible application Toolbar provided by JavaFX components. These components can be "skinned" using CSS stylings and as such the menu can be crafted differently for different applications.  The new AccordionToolbar was designed so that multiple toolbars could be show simultaneously or none shown at all.

Combine that with the AccordionMenu you get a nice slide in based workspace and should look something like this:



Published at DZone with permission of Sean Phillips, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)