Статьи

Выдвижное меню JavaFX Accordion для платформы NetBeans

Допустим, у вас есть приложение на платформе NetBeans, которое дает преимущество вертикальному пространству. Может быть, дисплей Heads Up на сенсорном экране? Разве не было бы замечательно выдвинуть меню с края экрана только тогда, когда оно вам нужно? Конечно, платформа NetBeans предоставляет скользящие TopComponents, но JMenu просто не сработает внутри.

Мы можем использовать JavaFX как часть решения, поскольку оно предоставляет некоторые возможности, которых нет у базовых компонентов Swing, доступных в платформе NetBeans. Допустим, мы берем все наши корневые элементы MenuBar и помещаем их в панель типов Accordion. Каждая сворачиваемая титульная панель элемента управления Accordion может затем содержать элементы подменю, которые могут быть представлены JavaFX MenuButton. Это позволило бы создать рекурсивный эффект, подобный меню, но общий контейнер можно было бы разместить где угодно. 

Что-то вроде скриншота ниже:

То, что мы видим здесь, это описанный эффект, выдвигающийся и накладываемый поверх вкладки «Избранное». Я посыпал немного прозрачности для хорошей меры. Обратите внимание, как мы можем полностью исключить панель меню и панель инструментов, получая потенциально ценную недвижимость? Остальная часть этого урока объяснит шаги, необходимые для достижения чего-то подобного. 

Чтобы полностью понять и следовать этому учебному пособию, вам нужно начать с учебного пособия, которое четко разработано в этом учебном пособии, ссылка на который приведена ниже:

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

Эта статья была написана Geertjan Wielenga, и станет ясно, что большая часть базового кода для выполнения этой статьи была расширена на примере Geertjan. Еще раз спасибо Гертьян!

Статьи, похожие на это, могут быть полезны ниже:

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

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

Все эти статьи слабо связаны между собой в уроке по объяснению и демонстрации преимуществ интеграции JavaFX в платформу NetBeans. Следующие два шага заимствованы в точности так, как указано в руководстве Geertjan:

Шаг 1. Удалите панель меню по умолчанию и замените ее собственной: 

import org.openide.modules.ModuleInstall;

public class Installer extends ModuleInstall {

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

Шаг 2: В файле layer.xml определите замещающую строку меню Swing. 

<?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>

Я также позволил себе скрыть панели инструментов. Теперь, почему мы заменяем старый MenuBar новым MenuBar, если мы намерены его скрыть? Хорошо, если вы скрываете MenuBar через layer.xml, как я делал на панелях инструментов, дерево папок файловой системы не будет создано. Это означает, что мы не сможем динамически определить дерево папок меню для перестройки нашего пользовательского AccordionMenu. Решение? Сделать пустой менубар.

package polaris.javafxwizard.jfxmenu;

import javax.swing.JMenuBar;

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

Шаг 3. Создайте «AccordionMenu» с использованием JavaFX.

Здесь учебные материалы расходятся, и этот процесс становится немного сложнее. Наша задача — использовать шаблон JavaFX / Swing Interop для создания компонента, расширяющего JFXPanel, который может предоставить пользователю доступ ко всем элементам, которые когда-то были в строке меню. Основной алгоритм таков:


Создайте компонент, расширяющий JFXPanel. Реализуйте

стандартный шаблон Platform.runLater () для создания
цикла сцены JavaFX
через каждый объект файла верхнего уровня в папке Menu файловой системы приложения:


Создание панели потока JavaFX для каждого объекта файла.

Рекурсивное создание элементов JavaFX ButtonMenu для подменю.

Добавление элементов ButtonMenu в FlowPanes.

Добавление FlowPane в JavaFX. TitledPane.

Добавление TitledPane в компонент JavaFX Accordion.

Добавить Аккордеон к сцене

Поэтому вместо Menus и SubMenus мы используем MenuButtons, которые можно рекурсивно добавлять к другим MenuButtons и MenuItems. Элемент управления Accordion дает нам компактное складное представление с хорошей анимацией. FlowPane позволяет легко расположить кнопки меню горизонтально, чтобы максимизировать пространство. Ниже приведен код моего класса AccordionMenu. Вы увидите, где я заимствовал из примера Гиртджана:

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);
            }
        }
        
    }
}

Я позволил себе разместить несколько CSS-стилей тут и там, пытаясь поиграть с прозрачностью. Также я обнаружил, что это выглядело бы лучше, если бы кнопка JavaFX использовалась для любых действий, найденных на самом верхнем уровне, вместо кнопки MenuButton с одним элементом.

Шаг 4. Создание слайда в TopComponent для нового AccordionMenu

Теперь, когда у вас есть компонент Swing Interop JFXPanel, вашему TopComponent платформы NetBeans не нужно знать о JavaFX. Однако в этом сценарии платформа также вносит свой вклад через свою замечательную среду стыковки. Воспользуйтесь мастером окон и выберите «Скольжение влево» в качестве режима. Я бы также посоветовал сделать этот компонент закрытым, иначе пользователь может потерять возможность пользоваться меню. Вот аннотации и код конструктора в моем 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);
    }

Шаг 5. Посмотрите, как это здорово выглядит

Теперь у нас есть выдвижное меню приложений, предоставляемое компонентами JavaFX. Эти компоненты могут быть «оформлены» с помощью стилей CSS, и поэтому меню может быть создано по-разному для разных приложений. (Кстати, если у кого-то, кто читает это, есть какие-то идеи, пожалуйста, свяжитесь со мной, потому что я вообще не CSS-парень). 

Лучше всего мы адаптировали наше приложение для приятной работы с Heads Up Display или представлением Kiosk, которые обычно работают на сенсорных компьютерах. Это связано с тем, что мы сохранили недвижимость и внедрили интерфейс, который более удобен для одиночных касаний, чем для событий перетаскивания мышью. 

Эй, давайте посмотрим, как это может выглядеть с приложением, которому нужно все
пространство, которое он может получить?