DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • JavaFX Gets Video Capabilities
  • The Challenges of a JavaFX Reboot
  • JDev Flies on NetBeans
  • NetBeans Platform Control Application for Testing of Cellular Networks

Trending

  • Building a Real-Time Change Data Capture Pipeline With Debezium, Kafka, and PostgreSQL
  • How to Convert XLS to XLSX in Java
  • Customer 360: Fraud Detection in Fintech With PySpark and ML
  • Introducing Graph Concepts in Java With Eclipse JNoSQL, Part 3: Understanding Janus
  1. DZone
  2. Coding
  3. Java
  4. JavaFX Accordion Slide Out Menu for the NetBeans Platform

JavaFX Accordion Slide Out Menu for the NetBeans Platform

By 
Sean Phillips user avatar
Sean Phillips
·
May. 17, 13 · Interview
Likes (0)
Comment
Save
Tweet
Share
19.9K Views

Join the DZone community and get the full member experience.

Join For Free

Let's say you have a NetBeans Platform application that puts a premium on vertical space.  Maybe a Heads Up Display on a Touch Screen?  Wouldn't it be great to have the menu slide out from the edge of the screen only when you need it?  Well the NetBeans Platform provides slide-in TopComponents, of course, but a JMenu just isn't going to work out so well inside one.

We can use JavaFX as part of the solution as it provides some capabilities that the base Swing components available in the NetBeans Platform do not.  Let's say we take all of our root MenuBar items and place them within an Accordion type pane.  Each collapsible TitledPane of the Accordion control could then contain the sub-menu items, maybe represented by a JavaFX MenuButton.  This would allow for a recursive Menu like effect but the overall container could be placed anywhere.

Something like the screenshot below:

What we see here is the described effect sliding out and overlayed on top of the Favorites tab.  I sprinkled in some transparency for good measure.  Notice how we are able to completely eliminate the Menu Bar and Tool Bar gaining potentially valuable real estate?  The rest of this tutorial will explain the steps necessary to achieve something like this.


That article was 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.  Thanks again Geertjan!

Similar articles to this that may be helpful are below:


https://dzone.com/articles/javafx-fxml-meets-netbeans 


https://dzone.com/articles/how-embed-javafx-chart-visual


All these articles are loosely coupled in a tutorial arc towards explaining and demonstrating the advantages of integrating JavaFX into the NetBeans Platform.  The following two steps are borrowed exactly as found from Geertjan's tutorial:


Step 1. Remove the default menubar and replace with your own:  


import org.openide.modules.ModuleInstall;

public class Installer extends ModuleInstall {

    @Override
    public void restored() {
            System.setProperty("netbeans.winsys.menu_bar.path", "LookAndFeel/MenuBar.instance");
    }
}

Step 2:  In the layer.xml file define your Swing replacement menubar.  


<?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>
    <file name="Toolbars_hidden"/>
    <folder name="LookAndFeel">
        <file name="MenuBar.instance">
            <attr name="instanceOf" stringvalue="org.openide.awt.MenuBar"/>
            <attr name="instanceCreate" newvalue="polaris.javafxwizard.jfxmenu.HiddenMenuBar"/>
        </file>
    </folder>
</filesystem>

I have also taken the liberty to hide the Toolbars as well.  Now why are we replacing the old MenuBar with a new MenuBar if we intend to hide it?  Well if you hide the MenuBar via the layer.xml as I did the Toolbars the filesystem folder tree will not be instantiated.  That means we won't be able to dynamically determine the Menu Folder tree to rebuild our custom AccordionMenu.  The solution?  Make an empty Menubar.


package polaris.javafxwizard.jfxmenu;

import javax.swing.JMenuBar;

/**
 *
 * @author SPhillips (King of Australia)
 */
public class HiddenMenuBar extends JMenuBar {
    public HiddenMenuBar() {
        super();
    }
}


Step 3:  Build an "AccordionMenu" using JavaFX


This is where the tutorials diverge and this process gets a bit more complicated.  Our task is to use the JavaFX/Swing Interop pattern to create a component that extends JFXPanel yet can give the user access to all the items that were once in the Menu Bar.  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 Menu folder of the application file system:
Create a JavaFX Flow Pane for each file object
Recursively create JavaFX ButtonMenu items for submenus
Add ButtonMenu items to FlowPanes
Add FlowPane to JavaFX TitledPane
Add TitledPane to JavaFX Accordion component
Add Accordion to scene

So instead of Menus and SubMenus, we are using MenuButtons which can be recursively added to other MenuButtons and MenuItems.  The Accordion control gives us a space saving collapsible view with some nice animation.  The FlowPane makes it easy to layout the MenuButtons horizontally in a way that maximizes space.  Below is the code for my AccordionMenu class.  You will see where I borrowed heavily from Geertjan's example:


polaris.javafxwizard.jfxmenu;

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.geometry.Orientation;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Accordion;
import javafx.scene.control.Button;
import javafx.scene.control.MenuButton;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TitledPane;
import javafx.scene.effect.DropShadow;
import javafx.scene.layout.FlowPane;
import javafx.scene.paint.Color;
import javax.swing.Action;
import javax.swing.SwingUtilities;
import org.openide.awt.Actions;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.loaders.DataFolder;
import org.openide.loaders.DataObject;
import org.openide.util.Exceptions;

/**
 *
 * @author SPhillips (King of Australia)
 */
public class AccordionMenu extends JFXPanel{
    
    public Accordion accordionPane;
    public String transparentCSS = "-fx-background-color: rgba(0,100,100,0.1);";
    
    public AccordionMenu() {
        super();
        // create JavaFX scene
        Platform.setImplicitExit(false);
        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                createScene(); //Standard Swing Interop Pattern
            }
        });        
    }
    private void createScene() {
        FileObject menuFolder = FileUtil.getConfigFile("Menu");
        FileObject[] menuKids = menuFolder.getChildren();
        //for each Menu folder need to create a TilePane and add it to an Accordion
        List<TitledPane> titledPaneList = new ArrayList<>();
        for (FileObject menuKid : FileUtil.getOrder(Arrays.asList(menuKids), true)) {
            //Build a Flow pane based on menu children
            //TOP level menu items should all be flow panes
            FlowPane flowPane = buildFlowPane(menuKid);
            flowPane.setStyle(transparentCSS);
            TitledPane newTitledPaneFromFileObject = new TitledPane(menuKid.getName(), flowPane);
            newTitledPaneFromFileObject.setAnimated(true);
            newTitledPaneFromFileObject.autosize();
            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);
        accordionPane = new Accordion();
        accordionPane.setStyle(transparentCSS);
        accordionPane.getPanes().addAll(titledPaneList); 
        g.getChildren().add(accordionPane);
        setScene(scene);
        validate();
        this.setOpaque(true);
        this.setBackground(new java.awt.Color(0.0f, 0.0f, 0.0f, 0.0f));
    }
    private FlowPane buildFlowPane(FileObject fo) {
        //FlowPanes are made up of Buttons and MenuButtons built from actions and sub menus 
        FlowPane flowPane = new FlowPane(Orientation.HORIZONTAL,5,5);
        flowPane.setStyle(transparentCSS);
        //If anything at the Flow Pane level is an action we need to add it as a button
        //otherwise we can recursively build it as a MenuButton
        DataFolder df = DataFolder.findFolder(fo);
        DataObject[] childs = df.getChildren();
        for (DataObject oneChild : childs) {
            //If child is folder we need to build recursively
            if (oneChild.getPrimaryFile().isFolder()) {
                FileObject childFo = oneChild.getPrimaryFile();
                MenuButton newMenuButton = new MenuButton(childFo.getName());
                buildMenuButton(childFo, newMenuButton);
                flowPane.getChildren().add(newMenuButton);
            } else {
                Object instanceObj = FileUtil.getConfigObject(oneChild.getPrimaryFile().getPath(), Object.class);
                if (instanceObj instanceof Action) {
                    //If it is an Action we have reached an endpoint
                    final Action a = (Action) instanceObj;
                    String name = (String) a.getValue(Action.NAME);
                    String cutAmpersand = Actions.cutAmpersand(name);
                    Button buttonItem = new Button(cutAmpersand);
                    MenuEventHandler meh = new MenuEventHandler(a);
                    buttonItem.setOnAction(meh);
                    buttonItem.setEffect(new DropShadow());
                    flowPane.getChildren().add(buttonItem);
                }
            }
        }        
   
        return flowPane;
    }
    private void buildMenuButton(FileObject fo, MenuButton menuButton) {
        DataFolder df = DataFolder.findFolder(fo);
        DataObject[] childs = df.getChildren();
        for (DataObject oneChild : childs) {
            //If child is folder we need to build recursively
            if (oneChild.getPrimaryFile().isFolder()) {
                FileObject childFo = oneChild.getPrimaryFile();
                //Menu newMenu = new Menu(childFo.getName());
                MenuButton newMenuButton = new MenuButton(childFo.getName());
                //menu.getItems().add(newMenu);
                buildMenuButton(childFo, newMenuButton);
            } else {
                Object instanceObj = FileUtil.getConfigObject(oneChild.getPrimaryFile().getPath(), Object.class);
                if (instanceObj instanceof Action) {
                    //If it is an Action we have reached an endpoint
                    final Action a = (Action) instanceObj;
                    String name = (String) a.getValue(Action.NAME);
                    String cutAmpersand = Actions.cutAmpersand(name);
                    MenuItem menuItem = new MenuItem(cutAmpersand);
                    MenuEventHandler meh = new MenuEventHandler(a);
                    menuItem.setOnAction(meh);
                    menuButton.getItems().add(menuItem);
                }
            }
        }        
    }
    
    private class MenuEventHandler implements EventHandler<ActionEvent> {

        public Action theAction;
        
        public MenuEventHandler(Action action) {
            super();
            theAction = action;
        }
        
        @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());
                        theAction.actionPerformed(event);
                    }
                });
            } catch (    InterruptedException | InvocationTargetException ex) {
                Exceptions.printStackTrace(ex);
            }
        }
        
    }
}

I took the liberty of placing a few CSS stylings here and there, trying to play with the transparency.  Also I found that it looked better if a JavaFX Button was used for any Actions found at the very top level, instead of a MenuButton with a single item.


Step 4:  Build a Slide in TopComponent for the new AccordionMenu


Now that you have a JFXPanel Swing Interop component, your NetBeans Platform TopComponent doesn't need to know about JavaFX.  However in this scenario the Platform also is contributing via its wonderful docking framework.  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 menu.  Here are the annotations and constructor code in my TopComponent:


@ConvertAsProperties(
        dtd = "-//polaris.javafxwizard.jfxmenu//SlidingAccordion//EN",
        autostore = false)
@TopComponent.Description(
        preferredID = "SlidingAccordionTopComponent",
        iconBase="polaris/javafxwizard/jfxmenu/categories.png", 
        persistenceType = TopComponent.PERSISTENCE_ALWAYS)
@TopComponent.Registration(mode = "leftSlidingSide", openAtStartup = true)
@ActionID(category = "Window", id = "polaris.javafxwizard.jfxmenu.SlidingAccordionTopComponent")
@ActionReference(path = "Menu/JavaFX" /*, position = 333 */)
@TopComponent.OpenActionRegistration(
        displayName = "#CTL_SlidingAccordionAction",
        preferredID = "SlidingAccordionTopComponent")
@Messages({
    "CTL_SlidingAccordionAction=SlidingAccordion",
    "CTL_SlidingAccordionTopComponent=SlidingAccordion Window",
    "HINT_SlidingAccordionTopComponent=This is a SlidingAccordion window"
})
public final class SlidingAccordionTopComponent extends TopComponent {
    public AccordionMenu accordionMenu;
    public SlidingAccordionTopComponent() {
        initComponents();
        setName(Bundle.CTL_SlidingAccordionTopComponent());
        setToolTipText(Bundle.HINT_SlidingAccordionTopComponent());
        putClientProperty(TopComponent.PROP_CLOSING_DISABLED, Boolean.TRUE);
        putClientProperty(TopComponent.PROP_DRAGGING_DISABLED, Boolean.TRUE);
        putClientProperty(TopComponent.PROP_MAXIMIZATION_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
        accordionMenu = new AccordionMenu();
        //transparency
        Color transparent = new Color(0.0f, 0.0f, 0.0f, 0.0f);
        accordionMenu.setOpaque(true);
        accordionMenu.setBackground(transparent);
        this.add(accordionMenu);
        this.setOpaque(true);
        this.setBackground(transparent);
    }


Step 5.  See how great it looks


We now have a slide out collapsible application menu provided by JavaFX components. These components can be "skinned" using CSS stylings and as such the menu can be crafted differently for different applications.  (By the way if anyone reading this has some ideas please contact me because I am not a CSS guy at all) 


Best of all we have adapted our application to work nicely with a Heads Up Display or Kiosk view that typically run on touchscreen computers.  This is because we have saved real estate and implemented an interface that is more condusive to single touches versus mouse drag events. 


Hey let's see how it might look with an application that needs all the space it can get?



JavaFX NetBeans

Opinions expressed by DZone contributors are their own.

Related

  • JavaFX Gets Video Capabilities
  • The Challenges of a JavaFX Reboot
  • JDev Flies on NetBeans
  • NetBeans Platform Control Application for Testing of Cellular Networks

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!