Drombler Commons provides an Action Framework, which keeps the state (enabled/ disabled, selected/ unselected etc.), the information (texts, image etc.) and the logic between menu items and toolbar buttons in sync. In addition Drombler ACP provides annotations to easily register new actions, menus, menu items, toolbars and toolbar buttons.

The rest of this section describes how to use this framework in a Drombler FX application.

1. Action Implementations supported by Drombler FX

Drombler FX supports 3 ways to implement actions.

1.1. Implement org.drombler.commons.action.ActionListener;

The easiest way to implement ActionListener is to extend AbstractActionListener:

import org.drombler.commons.action.AbstractActionListener;

public class Test1Action extends AbstractActionListener<Object> {

    @Override
    public void onAction(Object event) {
        System.out.println("Test1 Action!");
    }
}

Pros:

  • maximally reusable; GUI toolkit agnostic (no dependencies on JavaFX)

Cons:

  • dependencies on Drombler Commons

Note: For Drombler FX applications, the generic type parameter passed to ActionListener/ AbstractActionListener must be javafx.event.ActionEvent or one of its super-classes (here: Object).

1.2. Implement javafx.event.EventHandler<javafx.event.ActionEvent>

import javafx.event.ActionEvent;
import javafx.event.EventHandler;

public class Test2Action implements EventHandler<ActionEvent> {

    @Override
    public void handle(ActionEvent t) {
        System.out.println("Test2 Action!");
    }
}

Pros:

  • very close to standard JavaFX code

  • no dependencies on Drombler Commons (apart from some annotations; see further down)

Cons:

  • dependencies on JavaFX

1.3. Implement org.drombler.commons.action.fx.FXAction

FXAction is an extension of javafx.event.EventHandler<javafx.event.ActionEvent>.

The easiest way to implement FXAction is to extend AbstractFXAction:

import javafx.event.ActionEvent;
import org.drombler.commons.action.fx.AbstractFXAction;

public class Test3Action extends AbstractFXAction {

    @Override
    public void handle(ActionEvent t) {
        System.out.println("Test3 Action!");
    }
}

Pros:

  • maximal control

Cons:

  • dependencies on JavaFX

  • dependencies on Drombler Commons

2. Toggle Action Implementations supported by Drombler FX

Drombler FX supports 2 ways to implement toggle actions.

2.1. Implement org.drombler.commons.action.ToggleActionListener

The easiest way to implement ToggleActionListener is to extend AbstractToggleActionListener:

import org.drombler.commons.action.AbstractToggleActionListener;

public class Test1ToggleAction extends AbstractToggleActionListener<Object> {

    @Override
    public void onSelectionChanged(boolean oldValue, boolean newValue) {
        System.out.println("Test1 Toggle Action selection changed: " + newValue);
    }
}

Note: For Drombler FX applications, the generic type parameter passed to ToggleActionListener/ AbstractToggleActionListener must be javafx.event.ActionEvent or one of its super-classes (here: Object).

Pros:

  • maximally reusable; GUI toolkit agnostic (no dependencies on JavaFX)

Cons:

  • dependencies on Drombler Commons

2.2. Implement org.drombler.commons.action.fx.FXToggleAction

FXToggleAction is an extension of FXAction.

The easiest way to implement FXToggleAction is to extend AbstractFXAction:

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import org.drombler.commons.action.fx.AbstractFXAction;
import org.drombler.commons.action.fx.FXToggleAction;

public class Test2ToggleAction extends AbstractFXAction implements FXToggleAction {

    private final BooleanProperty selected = new SimpleBooleanProperty(this, "selected");

    public Test2ToggleAction() {
        selected.addListener((ObservableValue<? extends Boolean> ov, Boolean oldValue, Boolean newValue)
                -> System.out.println("Test1 Toggle Action selection changed: " + newValue));
    }

    @Override
    public BooleanProperty selectedProperty() {
        return selected;
    }

    @Override
    public final boolean isSelected() {
        return selectedProperty().get();
    }

    @Override
    public final void setSelected(boolean selected) {
        selectedProperty().set(selected);
    }

    @Override
    public void handle(ActionEvent t) {
        // do nothing
    }
}

Pros:

  • maximal control

Cons:

  • dependencies on JavaFX

  • dependencies on Drombler Commons

  • currently: more code is required

3. Registering Actions, Menu Items and Toolbar Buttons

The simplest way to register your actions, menu items and toolbar buttons is to use some annotations on your Action impementation.

3.1. Register an Action

To register an Action use the @Action annotation:

import org.drombler.acp.core.action.Action;

@Action(id = "test1", category = "test", displayName = "%test1.displayName",
        accelerator = "Shortcut+T", icon = "test1.png")
Property Value

id

used by @MenuEntry and @ToolbarEntry

category

used to group actions (currently this has not effect, but might be used in future)

displayName

the text to be displayed, e.g. as the text for menu items or the tooltip for toolbar buttons. If the value starts with '%' the rest of the value is interpreted as a property key and the value gets looked-up in the Bundle.properties file (or a locale specific derivation of this file), which has to be in the same package as the annotated action. Note: Maven expects resources to be in the resources directory.

accelerator

the accelerator to be used for this action

icon

the icon to be used for this action. Note that this only specifies the name pattern. Drombler FX looks for <icon-base-name>16.<icon-extension> for menu items (expected to be 16x16 pixels) and <icon-base-name>24.<icon-extension> for toolbar buttons (expected to be 24x24 pixels). In the example above Drombler FX would look for test116.png and test124.png. Note: Maven expects resources to be in a resources directory. If you’re using drombler-fx-parent as your parent POM, it’s best to put binary files under the resources-bin directory as this directory has been configured not to be filtered for variables.

Action Annotation Properties

Note: If the displayName contains an underscore ('_'), the following character is the mnemonic character of this action.

3.2. Register a Menu Item

To register a menu item use the @MenuEntry annotation:

import org.drombler.acp.core.action.MenuEntry;

@MenuEntry(actionId = "test1", path = "File", position = 20)
Property Value

actionId

the id of the Action to be registered as a menu item. This property can be omitted, if there is an @Action annotation on the same class.

path

a slash '/' delimited path of Menu IDs (see further down)

position

the position to order the menu items in a menu. It’s a best practice to leave out some positions between entries to allow other bundles to register entries between some existing ones.

Separators: If the positions of two consecutive entries are not in the same thousand group, Drombler FX will add a separator between them.

3.3. Register a Toolbar Button

To register a toolbar button use the @ToolBarEntry annotation:

import org.drombler.acp.core.action.ToolBarEntry;

@ToolBarEntry(actionId = "test1", toolBarId = "file", position = 30)
Property Value

actionId

the id of the Action to be registered as a toolbar button. This property can be omitted, if there is an @Action annotation on the same class.

toolBarId

the ID of the toolbar this toolbar button should be registered to (see further down)

position

the position to order the toolbar buttons in a toolbar. It’s a best practice to leave out some positions between entries to allow other bundles to register entries between some existing ones.

3.4. Register a Toggle Action

Toggle Actions need to be registered using the @ToggleAction annotation. The properties are the same as for the @Action annotation (see above).

3.5. Register a Toggle Menu Item

To register a toggle menu item use the @ToggleMenuEntry annotation. The properties are the same as for the @MenuEntry annotation (see above), but the actionId must be the ID of a toggle action and there is an additional property:

toggleGroupId: if present, only one menu item of the same toggle group can be selected, else any number of menu items may be selected.

3.6. Register a Toggle Toolbar Button

To register a toggle toolbar button use the @ToolBarToggleEntry annotation. The properties are the same as for the @ToolBarEntry annotation (see above), but the actionId must be the ID of a toggle action and there is an additional property:

toggleGroupId: if present, only one toolbar button of the same toggle group can be selected, else any number of toolbar buttons may be selected.

3.7. Register Menus and Sub-Menus

Menus and sub-menus can be registered using the @Menu annotation on a package (in package-info.java):

@Menu(id = "Custom", displayName = "%customMenu.displayName", position = 110)
@Menu(id = "Sub", displayName = "%subMenu.displayName", path = "Custom", position = 30)
package tutorial.action.menu;

import org.drombler.acp.core.action.Menu;
Property Value

id

the ID of the menu to be registered. This ID can be used in the path property of menus and menu items.

displayName

the text of the menu. If the value starts with '%' the rest of the value is interpreted as a property key and the value gets looked-up in the Bundle.properties file (or a locale specific derivation of this file), which has to be in the same package. Note: Maven expects resources to be in the resources directory.

path

a slash '/' delimited path of Menu IDs. If it is omitted, then the menu will be registered directly in the menu bar.

position

the position to order the menus in a parent menu/ menu bar. It’s a best practice to leave out some positions between entries to allow other bundles to register entries between some existing ones.

Note: If the displayName contains an underscore ('_'), the following character is the mnemonic character of this menu.

3.8. Register Toolbars

Toolbars can be registered using the @ToolBar annotation on a package (in package-info.java):

@ToolBar(id = "myToolbar1", displayName = "%myToolbar1.displayName", position = 50)
@ToolBar(id = "myToolbar2", displayName = "%myToolbar2.displayName", position = 100,
        visible = false)
package tutorial.action.toolbar;
Property Value

id

the ID of the menu to be registered. This ID can be used as the toolBarId property of toolbar buttons.

displayName

the text of the toggle menu item registered at "View/Toolbars". If the value starts with '%' the rest of the value is interpreted as a property key and the value gets looked-up in the Bundle.properties file (or a locale specific derivation of this file), which has to be in the same package. Note: Maven expects resources to be in the resources directory.

position

the position to order the toolbars in the toolbar container and in the "View/Toolbars" menu. It’s a best practice to leave out some positions between entries to allow other bundles to register entries between some existing ones.

visible

indicates if the toolbar should initially be visible or not. The default is "true". The visibility can be toggled in the "View/Toolbars" menu.

Note: If the displayName contains an underscore ('_'), the following character is the mnemonic character of the registered toggle menu item.

Note: For every registered toolbar a toggle menu item will registered at "View/Toolbars".

4. The Standard Actions, Menus and Toolbars

As of this writing Drombler FX provides the following standard actions, menus, menu items, toolbars and toolbar buttons:

Menu IDs:

  • File (position = 10)

  • View (position = 40)

    • Toolbars (position = 5100)

  • Help (position = 900)

Toolbar IDs:

  • file (position = 10)

Actions, Menu Items and Toolbar Buttons:

Save:

@Action(id = "standard.save", category = "core", displayName = "%save.displayName",
        accelerator = "Shortcut+S", icon = "save.gif")
@MenuEntry(path = "File", position = 4200)
@ToolBarEntry(toolBarId = "file", position = 50)

Save All:

@Action(id = "standard.saveAll", category = "core", displayName = "%saveAll.displayName",
        accelerator = "Shortcut+Shift+S", icon = "saveAll.png")
@MenuEntry(path = "File", position = 4210)
@ToolBarEntry(toolBarId = "file", position = 60)

The "Save" action looks for an instance of the interface org.drombler.acp.core.standard.action.Savable in the active context.

The "Save All" action looks for all instances of the same interface in the application context.

See Context Framework for more information about the Context Framework.

Exit:

@Action(id = "platform.exit", category = "core", displayName = "%exit.displayName", accelerator = "Shortcut+Q")
@MenuEntry(path = "File", position = 9900)

5. Context Sensitive Actions

Drombler FX checks if an Action implements org.drombler.commons.context.ActiveContextSensitive or org.drombler.commons.context.ApplicationContextSensitive and injects the active/ application-wide context.

See Context Framework for more information about the Context Framework.

6. Samples

In this section you will find some annotated Action samples.

6.1. Basic Sample

The following sample shows a simple action implementation, which is registered as a menu item in the menu with the ID "File" and as a toolbar button in the toolbar with the ID "file".

package tutorial.action.sample.impl;

import org.drombler.acp.core.action.Action;
import org.drombler.acp.core.action.MenuEntry;
import org.drombler.acp.core.action.ToolBarEntry;
import org.drombler.commons.action.AbstractActionListener;

@Action(id = "test1", category = "test", displayName = "%test1.displayName",
        accelerator = "Shortcut+T", icon = "test1.png")
@MenuEntry(path = "File", position = 20)
@ToolBarEntry(toolBarId = "file", position = 30)

public class MyAction1 extends AbstractActionListener<Object> {

    @Override
    public void onAction(Object event) {
        System.out.println("Test1 Action!");
    }
}

6.2. Active Context Sensitive Sample

The following sample shows a active context sensitive action implementation. It looks for a MyCommand instance in the active context and listens for changes of the active context. If it finds a MyCommand instance, the Action gets enabled, else it gets disabled. If the Action gets triggered (onAction-method), then a method of MyCommand gets called.

package tutorial.action.sample.impl;

import tutorial.action.sample.MyCommand;
import org.drombler.acp.core.action.Action;
import org.drombler.acp.core.action.MenuEntry;
import org.drombler.acp.core.action.ToolBarEntry;
import org.drombler.commons.action.AbstractActionListener;
import org.drombler.commons.context.ActiveContextSensitive;
import org.drombler.commons.context.Context;

@Action(id = "myaction", category = "mycategory", displayName = "%myaction.displayName",
        accelerator = "Shortcut+M", icon = "myaction.gif")
@MenuEntry(path = "File", position = 3200)
@ToolBarEntry(toolBarId = "file", position = 42)
public class MyAction2 extends AbstractActionListener<Object> implements ActiveContextSensitive {

    private MyCommand myCommand;
    private Context activeContext;

    public MyAction2() {
        setEnabled(false);
    }

    @Override
    public void onAction(Object event) {
        myCommand.doSomething();
    }

    @Override
    public void setActiveContext(Context activeContext) {
        this.activeContext = activeContext;
        this.activeContext.addContextListener(MyCommand.class, event -> contextChanged());
        contextChanged();
    }

    private void contextChanged() {
        myCommand = activeContext.find(MyCommand.class);
        setEnabled(myCommand != null);
    }

}

6.3. Application Context Sensitive Sample

The following sample shows a application context sensitive action implementation. It looks for all MyCommand instances in the application-wide context and listens for changes of this context. If it finds any MyCommand instance, the Action gets enabled, else it gets disabled. If the Action gets triggered (onAction-method), then a method of MyCommand gets called on all found instances.

package tutorial.action.sample.impl;

import tutorial.action.sample.MyCommand;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.drombler.acp.core.action.Action;
import org.drombler.acp.core.action.MenuEntry;
import org.drombler.acp.core.action.ToolBarEntry;
import org.drombler.commons.action.AbstractActionListener;
import org.drombler.commons.context.ApplicationContextSensitive;
import org.drombler.commons.context.Context;

@Action(id = "myaction", category = "mycategory", displayName = "%myaction.displayName",
        accelerator = "Shortcut+Shift+M", icon = "myaction.png")
@MenuEntry(path = "File", position = 3210)
@ToolBarEntry(toolBarId = "file", position = 72)
public class MyAction3 extends AbstractActionListener<Object> implements ApplicationContextSensitive {

    private Collection<? extends MyCommand> myCommands = Collections.emptyList();
    private Context applicationContext;

    public MyAction3() {
        setEnabled(false);
    }

    @Override
    public void onAction(Object event) {
        // protect against modification during iteration TODO: needed?
        List<MyCommand> currentMyCommands = new ArrayList<>(myCommands);
        currentMyCommands.forEach(myCommand -> myCommand.doSomething());
    }

    @Override
    public void setApplicationContext(Context applicationContext) {
        this.applicationContext = applicationContext;
        this.applicationContext.addContextListener(MyCommand.class,
                event -> contextChanged());
        contextChanged();
    }

    private void contextChanged() {
        myCommands = applicationContext.findAll(MyCommand.class);
        setEnabled(!myCommands.isEmpty());

6.4. Basic Toggle Sample

The following sample shows a simple toggle action implementation, which is registered as a toggle menu item in the menu with the ID "File" and as a toolbar toggle button in the toolbar with the ID "file". Both the menu item and the toolbar button participate in a toggle group.

package tutorial.action.sample.impl;

import org.drombler.acp.core.action.ToggleAction;
import org.drombler.acp.core.action.ToggleMenuEntry;
import org.drombler.acp.core.action.ToolBarToggleEntry;
import org.drombler.commons.action.AbstractToggleActionListener;

@ToggleAction(id = "test1", category = "test", displayName = "%test1.displayName",
        accelerator = "Shortcut+T", icon = "test1.png")
@ToggleMenuEntry(path = "Custom/Sub", position = 30, toggleGroupId = "test")
@ToolBarToggleEntry(toolBarId = "test", position = 30, toggleGroupId = "test")
public class MyToggleAction extends AbstractToggleActionListener<Object> {

    @Override
    public void onSelectionChanged(boolean oldValue, boolean newValue) {
        System.out.println("Test1 Toggle Action selection changed: " + newValue);
    }
}

6.5. More Samples

To see more samples have a look at the Sample Application described in the Getting Started section.