Mar 31

Swing and JavaFX: working with JFXPanel

I soon will have to deal with JavaFX in a Swing based fat client
- oh sorry, of course I meant “multi-layered rich-client” ;-)!

So this brings me to have a look at the JFXPanel.
The JFXPanel is a javax.swing.JComponent to embed JavaFX content into Swing-UIs. A JFXPanel can be used similar to a JPanel and can be accessed via the EDT as a generic Swing component except dealing with JavaFX component must be done via the JavaFX application thread.

To play around with that stuff I created two similar Panels (Swing + JavaFX) each with a Button, a TextField and a Label and placed them in a JSplitPane and a JFrame:

A closer look inside

To try Swing <-> JavaFX interoperability the buttons actions are setting the text from TextField to JLabel and vice versa.
There is nothing special with the JPanel as its handles generic Swing stuff, but with the JFXPanel containing the JavaFX controls:

public class SwingFXPanel extends JFXPanel {

    private Button testButton;
    private TextField testTextField;
    private Label testLabel;
    private VBox pane;

    public SwingFXPanel() {
        init();
    }

    private void init() {
        testButton = new Button("I am a JavaFX Button");
        testTextField = new TextField();
        testLabel = new Label("empty");
        pane = new VBox();
        pane.setAlignment(Pos.CENTER);
        pane.getChildren().addAll(testTextField, testButton, testLabel);
        Platform.runLater(this::createScene);
    }

    private void createScene() {
        Scene scene = new Scene(pane);
        setScene(scene);
    }

    public Button getTestButton() {
        return testButton;
    }

    public TextField getTestTextField() {
        return testTextField;
    }

    public Label getTestLabel() {
        return testLabel;
    }
}

Important here: to add the Scene to the JFXPanel within the JavaFX Application Thread:

Platform.runLater(this::createScene);

If you call

createScene()

from another thread you get a Runtime-Exception:

java.lang.IllegalStateException: Not on FX application thread; currentThread = AWT-EventQueue-0

Also each interaction with JavaFX related stuff must be placed on the JavaFX Application Thread:

E.g.:

Platform.runLater(() -> {
   swingFXPanel.getTestLabel().setText(swingPanel.getTestTextField().getText());
});
public class InteropFrame extends JFrame {

    private JSplitPane centralSplitPane;
    private SwingPanel swingPanel;
    private SwingFXPanel swingFXPanel;

    public InteropFrame(){
        init();
    }

    private void init() {
        setTitle("Swing <-> JavaFX Interoperatbiliy");
        setSize(500, 500);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLayout(new BorderLayout());

        centralSplitPane = new JSplitPane();
        centralSplitPane.setDividerLocation(0.5);
        centralSplitPane.setResizeWeight(0.3);
        
        swingPanel = new SwingPanel();
        swingFXPanel = new SwingFXPanel();

        swingPanel.getTestButton().addActionListener((ActionEvent e) -> {
            Platform.runLater(() -> {
                swingFXPanel.getTestLabel().setText(swingPanel.getTestTextField().getText());
            });
        });


        swingFXPanel.getTestButton().setOnAction((javafx.event.ActionEvent t) -> {
            swingPanel.getTestLabel().setText(swingFXPanel.getTestTextField().getText());
        });

        centralSplitPane.setLeftComponent(swingPanel);
        centralSplitPane.setRightComponent(swingFXPanel);

        add(centralSplitPane, BorderLayout.CENTER);
    }
}

Alternatively also dealing with FXML is quite simple:

public class SwingFXMLPanel extends JFXPanel {

    @FXML
    private Button testButton;
    @FXML
    private TextField testTextField;
    @FXML
    private Label testLabel;

    private VBox rootPane;

    private URL fxmlResource;
    
    public SwingFXMLPanel(URL fxmlResource){
        this.fxmlResource = fxmlResource;
        init();
    }

    private void init(){
        rootPane = new VBox();
        FXMLLoader loader = new FXMLLoader(fxmlResource);
        loader.setController(this);
        loader.setRoot(rootPane);
        try {
            loader.load();
        } catch (IOException ex) {
            Logger.getLogger(SwingFXMLPanel.class.getName()).log(Level.SEVERE, null, ex);
        }

        testButton.setText("I am a JavaFX Button");
        testLabel.setText("empty");

        Platform.runLater(this::createScene);
    }

    private void createScene() {
        Scene scene = new Scene(rootPane);
        setScene(scene);
    }

    public Button getTestButton() {
        return testButton;
    }

    public TextField getTestTextField() {
        return testTextField;
    }

    public Label getTestLabel() {
        return testLabel;
    }

}

For me its very important to get the most possible acceptance of my co-workers to use JavaFX in Swing.
Therefore I want to simplify the specific FX application thread handling.
So maybe this can be achieved if the major difference to using a JPanel is just to add

    private void createScene() {
        Scene scene = new Scene(rootPane);
        setScene(scene);
    }

and to call

  Platform.runLater(this::createScene);

inside the JFXPanel.

You can find the complete example code here.

Mar 23

Call to verify responsiveness of your JavaFX UIs

Recently Jim Weaver installed my little picture indexing app “picmodo” for a demo on his Surface Pro and the GUI became a pice of junk.

Obviously the basic font size of JavaFX on a Windows Tablet is to high:

picmodo-main-crap

user-editor-crap

picmodo-options-crap

I assume, too absolute sizes and positions are not always the best idea, even if the resize behaviour works like expected and the UI it looks quite ok on the developers machine… ;-)

So I suppose to frequently give it a try by simply adding this to your CSS to increase default font size a bit and change the button sizes to verify the responsiveness of your layouts:


.root{
-fx-font-size: 16pt;
}

.button, .toggle-button, .choice-box {
-fx-padding: 10px 20px 10px 20px;
}

Somehow that forced me to modify some things. So basically I now prefer to use a combination of VBox, HBox and Region(s) (as a kind of spring-delimiter) for the major layout and to give a bit more respect to “USE_COMPUTED_SIZE” of controls and panes …

picmodo-main-clean

user-editor-clean

picmodo-options-clean

Feb 03

SplitPane.Divider Transition

Wrote a small utility class to control animated sliding of a SplitPane.Divider.

You can find the complete code in my Bitbucket repo here.

I intend to make it as simple as possible to attach a slide-animation to a standard SplitPane:

splitPane          The SplitPane to be animated.
dividerIndex     Index of the divider to be controlled.
direction           Direction to slide out the layout container
(horizontal SplitPane: LEFT/RIGHT, vertical SplitPane: UP/DOWN)
cycleDuration   Duration/Speed of the slide animation.

SplitPaneDividerSlider( SplitPane splitPane,
                                int dividerIndex,
                                Direction direction,
                                Duration cycleDuration )

SplitPaneTransitionDemo1

E.g. create a divider controller for divider at index 0:

SplitPaneDividerSlider leftSplitPaneDividerSlider = new SplitPaneDividerSlider(centerSplitPane, 0, SplitPaneDividerSlider.Direction.LEFT);

This guy can be triggered by a ToggleButton:

leftToggleButton.selectedProperty().addListener((ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) -> {
leftSplitPaneDividerSlider.setAimContentVisible(t1);
});

Set a font-icon on the ToogleButton:

AwesomeDude.setIcon(leftToggleButton, AwesomeIcon.TOGGLE_LEFT, "2em");

leftToggleButton.selectedProperty().addListener((ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) -> {
      if (t1) {
          AwesomeDude.setIcon(leftToggleButton, AwesomeIcon.TOGGLE_LEFT, "2em"
      } else {
           AwesomeDude.setIcon(leftToggleButton, AwesomeIcon.TOGGLE_RIGHT, "2.5em")          
      }
});

SplitPanes in JavaFX do respect the min/max sizes of layout containers.

Movement of the divider is stopped when reaching these values.
To overrule this constraint when animation is started to slide out the content, min size is set to 0.0 and the original size will be restored when sliding in the content is finished. The right pane is figured out based on the divider index and the direction to side out:

        switch (direction) {
            case LEFT:
            case UP:
                content = (Region) splitPane.getItems().get(dividerIndex);
                break;
            case RIGHT:
            case DOWN:
                content = (Region) splitPane.getItems().get(dividerIndex + 1);
                break;
        }

Nov 17

Introducing ‘adm4EE’: Manage Java EE Application Servers

Today I would like to announce a new project I have started at bitbucketadm4EE

I want to create a JavaFX  based management tool to ease the daily business of Java EE Application Server admins.

“A tool to manage App Servers? What about the already existing admin consoles?”

The aim is to keep the UI as simple as possible and hide complexity, even if the backend functions are very powerful and to provide easy access to:  monitoring the state of domains and applications and basic configurations like JDBC-pools, service ports, change admin password, etc.:

MainView

Currently implemented features:

Domain

  • Setup new domain according to domain template folder with support for JavaScript based setup script (Nashorn) via command line
  • Configure the domain
  • Start the domain
  • Stop the domain
  • Check if a domain is up and running
  • Restart the domain automatically in case of failure

EE Applications

  • Check application version against remote update server
  • Update application with new version from remote update server (support for update script)

JDBC Pool

  • Reconfigure JDBC Pool settings: Username, Password, URL, Ping JDBC Pools

Misc

  • Configure Service Ports (e.g. for HTTP listener, admin listener,…) with PortBase/PortOffset support
  • Command Line Interface for usage during scripted installation procedure
  • Settings are stored in a config-File (e.g. URL for Online-Updates, preferred Ports, …)
  • Change admin password
  • Show and save server log file
  • By now only GlassFish is supported, but prepared for other JavaEE-Servers (e.g. WildFly)

Some :

ConfigurationViewJDBCPoolViewLogViewServicePortsViewChangeAdminPasswordView

Have I already mentioned how much  I like the native packaging target? 

OSXdmg

 

Download: adm4ee_1_0_0.dmg

 

Nov 04

FontAwesomeFX: 4.0.1 support

Hi,

I just have updated FontAwesomeFX to support currently released FontAwesome 4.0.1 with all of it’s 369 Icons.

Please note that some enum values have been changed, e.g. former AwesomeIcon.SIGNOUT is now AwesomeIcon.SIGN_OUT.

Also I have added some convenience methods to the helper class:

AwesomeDude.setIcon(TreeItem treeItem, AwesomeIcon icon) {
AwesomeDude.setIcon(TreeItem treeItem, AwesomeIcon icon, String iconSize)

Tip: Navigation Buttons

NavigationButtons

Add CSS and assign the class to a Button or ToggleButton:

.navigation-button{
    -fx-font-size: 0.9em;
    -fx-background-color: transparent;
    -fx-graphic-text-gap: 0px;
    -icons-color: gray;
    -fx-text-fill: gray;
    -fx-effect: innershadow( three-pass-box , rgba(0,0,0,0.4) , 6, 0.0 , 0 , 2 );
}

.navigation-button:pressed:hover{
    -icons-color: black;
    -fx-text-fill: black;
    -fx-effect: innershadow( three-pass-box , rgba(0,0,0,0.7) , 6, 0.0 , 0 , 2 );
}

.navigation-button:hover{
    -fx-effect: innershadow( three-pass-box , rgba(0,0,0,0.7) , 6, 0.0 , 0 , 2 );
}

Set FontAwesome-Icons:

AwesomeDude.setIcon(configurationButton, AwesomeIcon.GEAR, viewContext.getUiConfiguration().getIconEmSize(), ContentDisplay.TOP);
AwesomeDude.setIcon(aboutButton, AwesomeIcon.INFO, viewContext.getUiConfiguration().getIconEmSize(), ContentDisplay.TOP);
AwesomeDude.setIcon(exitButton, AwesomeIcon.SIGN_OUT, viewContext.getUiConfiguration().getIconEmSize(), ContentDisplay.TOP);

Source
@ Bitbucket

Maven

<dependency>
  <groupId>de.jensd</groupId>
  <artifactId>fontawesomefx</artifactId>
  <version>8.0.5</version>
</dependency>

Support for glyph font packs in ControlsFX 8.0.2
Since version 8.0.2 the ControlsFX-Project has support for glyph font packs.

Oct 04

Solved: eGalax Touch Screen issues with JavaFX (on Raspberry Pi)

In a former post I wrote about my trouble getting touch support up an running with my eGalax Touch Display, JavaFX and the Raspberry Pi.
Today Simon gave me a hint how he solved that touch event issues with JavaFX.

Many Kudos to Simon Ritter (@speakjava)!

The Clue

It turns out that the screen driver actually creates two event devices in /dev/input: event0 and event1. JavaFX sees /dev/input/event0 and assumes that events come from that, but for some unknown reason no events are actually sent: they all go to /dev/input/event1.

The Cure

Recreate /dev/input/event0 with the same device as /dev/input/event1, so JavaFX gets the information it needs:

eGalax_patch1

Make it suitability for daily usage

Create an init-script to apply the patch at start-up:
Required-Start: $all‘ tells insserv to start the script after all the other scripts, at the end of the boot sequence:

#! /bin/sh
### BEGIN INIT INFO
# Provides:          egalax-patch
# Required-Start:    $all
# Required-Stop:     
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description:
# Description:       Recreates event0 with the same device as event1, so JavaFX gets the information it needs.
### END INIT INFO

. /lib/lsb/init-functions

rm -f /dev/input/event0
mknod /dev/input/event0 c 13 65

log_success_msg "eGalax Touch Screen patch for JavaFX applied."

You can load it here.

  • copy the script to /etc/init.d/
  • make sure it’s executable: sudo chmod 755 egalax_patch
  • then enable the script: sudo insserv egalax-patch

One last thing

The event devices are re-created by the driver when the USB connection was interrupted.
In this case you will have to reboot the Raspi or (much better) execute the script manually:

eGalax_patch2

Sep 09

RasPi does the Home Automation (Part V): Just around the corner

Looking for a lightweight Java-based solution to run REST services on embedded systems I already had an eye on the Vert.x project. I was excited to see there is a talk about it on the agenda of the Herbstcampus in Nuremberg last week (I talked about my RasPi projects).

It was a pleasure to attend the talk with Eberhard Wolff about Vert.x!

Inspired by Eberhards presentation I was thrilled to give it a try and I created my first “Vertical” as a REST service for my little home automation project (as an alternative to my custom HttpServer).
Clearly I will only need a very small fraction of the provided power!

Setup Vert.x on RasPi

  • Download and Install Vert.x
    e.g. extract the package to /opt
  • put ‘vert.x-2.0.1-final/bin‘ on path:
    e.g. add 'export PATH=/opt/vert.x-2.0.1-final/bin:$PATH' at the and of ~/.bashrc.

Check installation

vertx-version

Implementing the service

I added vertx-core & vertx-platform as maven dependency to my Sweethome project.
Note the ‘provided’ scope: the dependencies are just used at compile time. At runtime the needed libs will be provided by the vertx environment.

<dependency>
    <groupId>io.vertx</groupId>
    <artifactId>vertx-core</artifactId>
    <version>2.0.1-final</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>io.vertx</groupId>
    <artifactId>vertx-platform</artifactId>
    <version>2.0.1-final</version>
    <scope>provided</scope>
</dependency>

Now I am prepared to give it a try with a very simple so called ‘Verticle‘ implementing a request handler which is called in case the REST call signature is matching.

Basically the URL contains the parameter for my Send-command mentioned in a previous post:

http://IP:PORT/intertechno/send/HOUSE_CODE/GROUP/DEVICE>/COMMAND

public class IntertechnoVerticle extends Verticle {

    @Override
    public void start() {
        RouteMatcher routeMatcher = new RouteMatcher();
        routeMatcher.get("/intertechno/send/:houseCode/:group/:device/:command", new Handler<HttpServerRequest>() {
            @Override
            public void handle(HttpServerRequest req) {
                container.logger().info("received request from: " + req.remoteAddress());
                
                String deviceCode = String.format("%s %s %s", req.params().get("houseCode"), req.params().get("group"), req.params().get("device"));
                String commandParam = (String) req.params().get("command");
                Send.Command command = "0".equals(commandParam) ? Send.Command.TURN_OFF : Send.Command.TURN_ON;
                
                container.logger().info("Send: " + deviceCode + " " + command.name());
                new Send().send(deviceCode, command);
                container.logger().info("OK");
                
                req.response().setStatusCode(200);
                req.response().end("<b>Send: " + deviceCode + " " + command.name() + "</b>");
            }
        });

        int port = 14880;
        HttpServer server = vertx.createHttpServer();
        server.requestHandler(routeMatcher)
                .listen(port);

        container.logger().info("---------------------------------------");
        container.logger().info("-                                     -");
        container.logger().info("-      S W E E T H O M E  SERVER      -");
        container.logger().info("-                                     -");
        container.logger().info("---------------------------------------");
        container.logger().info("Server started.");
        container.logger().info("Listening on port " + port);
        container.logger().info("Waiting for incoming requests...");
    }
}

Deploy and run vertx

Start the server…

vertx run de.jensd.sweethome.server.vertx.IntertechnoVerticle -cp /opt/lib/sweethome-1.0.4-SNAPSHOT.jar

vertx-intertechno-verticle

… and call the service:

vertx-intertechno-url

You can get the code here.

[EOM]

Aug 31

JavaFX on Raspberry Pi: Some fun with stepper motor

Recently had some fun when controlling my stepper motor with my Raspberry Pi.

The motor: a 28BYJ-48 with ULN2003 driver unit:

DSC_5687

Cool: Pi4J has a ready to use GpioStepperMotorComponent.

Let’s assume this architecture:

StepperMotorControl2

Thus we start with the StepperMotorAdapter using this component.

First some helpfull defines:

private final int oneRevolution = 2038;
private final int quarterRevolution = oneRevolution / 4;
private final int halfRevolution = oneRevolution / 2;
private final int oneDegreeRevolution = oneRevolution / 360;

then we need to get the GpioController and provide 4 GPIO pins set to digital output mode

gpio = GpioFactory.getInstance();

final GpioPinDigitalOutput[] pins = {
    gpio.provisionDigitalOutputPin(RaspiPin.GPIO_00, PinState.LOW),
    gpio.provisionDigitalOutputPin(RaspiPin.GPIO_01, PinState.LOW),
    gpio.provisionDigitalOutputPin(RaspiPin.GPIO_02, PinState.LOW),
    gpio.provisionDigitalOutputPin(RaspiPin.GPIO_03, PinState.LOW)};

and we need a byte array to define a step sequence e.g. a single motor step sequence (every coil is turned on once per step):

singleStepSequence = new byte[4];
    singleStepSequence[0] = (byte) 0b0001;
    singleStepSequence[1] = (byte) 0b0010;
    singleStepSequence[2] = (byte) 0b0100;
    singleStepSequence[3] = (byte) 0b1000;

Now we can create the motor:

motor = new GpioStepperMotorComponent(pins);
        motor.setStepSequence(currentStepSequence);
        motor.setStepsPerRevolution(oneRevolution);

and some methods to control the motor:

public void stop() {
    motor.stop();
}

public void forward() {
    motor.reverse();
}

public void backward() {
    motor.forward();
}

public void oneStepBackward() {
    motor.step(oneDegreeRevolution);
}

public void oneStepForward() {
    motor.step(-oneDegreeRevolution);
}

public void halfRevolutionBackward() {
    motor.step(halfRevolution);
}

public void halfRevolutionForward() {
    motor.step(-halfRevolution);
}

public void quarterRevolutionBackward() {
    motor.step(quarterRevolution);
}

public void quarterRevolutionForward() {
    motor.step(-quarterRevolution);
}

This adapter is used by the StepperMotorControl which implements handlers like:

adjustBackwardButton.setOnMousePressed(new EventHandler<MouseEvent>() {
    @Override
    public void handle(MouseEvent t) {
        stepperMotorAdapter.backward();
    }
});
adjustBackwardButton.setOnMouseReleased(new EventHandler<MouseEvent>() {
    @Override
    public void handle(MouseEvent t) {
        stepperMotorAdapter.stop();
    }
});
adjustForwardButton.setOnMousePressed(new EventHandler<MouseEvent>() {
    @Override
    public void handle(MouseEvent t) {
        stepperMotorAdapter.forward();
    }
});
adjustForwardButton.setOnMouseReleased(new EventHandler<MouseEvent>() {
    @Override
    public void handle(MouseEvent t) {
        stepperMotorAdapter.stop();
    }
});

and provides FXML usable methods like:

@FXML
public void backward() {
    Platform.runLater(new Runnable() {
        @Override
        public void run() {
            stepperMotorAdapter.backward();
        }
    });
}

@FXML
public void forward() {
    Platform.runLater(new Runnable() {
        @Override
        public void run() {
            stepperMotorAdapter.forward();
        }
    });
}

@FXML
public void stop() {
    Platform.runLater(new Runnable() {
        @Override
        public void run() {
            stepperMotorAdapter.stop();
        }
    });
}

Finally this is the JavaFX UI running on the RasPi:
StepperMotorControl

Get the complete code here:

https://bitbucket.org/Jerady/raspberry-cpio-control-fx

Icons on the buttons are provided by:
FontAwesomeFX

Jul 31

‘Font Awesome’ Icons with JavaFX: minor updates

Hi,

some minor FontAwesomeFX updates:

The AwesomeDude now provides convenience methods to add an AwesomeFont-Icon to existing Labeled-controls and MenuItems by setting the graphic node:

labeled.setGraphic(createIconLabel(icon,iconSize));

Recall that direct subclasses of Labeled are ButtonBase, Cell, Label, TitledPane and their friends (by now I have not tested all combination).

setIcon(Labeled labeled, AwesomeIcon icon)
setIcon(Labeled labeled, AwesomeIcon icon, ContentDisplay contentDisplay)
setIcon(Labeled labeled, AwesomeIcon icon, String iconSize)
setIcon(Labeled labeled, AwesomeIcon icon, String iconSize, ContentDisplay contentDisplay)
setIcon(MenuItem menuItem, AwesomeIcon icon)
setIcon(MenuItem menuItem, AwesomeIcon icon, String iconSize)
setIcon(MenuItem menuItem, AwesomeIcon icon, String fontSize, String iconSize)

Usage

For example, I use the new methods for the UI of my RaspberryPi Home project:

AwesomeDude.setIcon(onButton, AwesomeIcon.LIGHTBULB, "72px");
AwesomeDude.setIcon(offButton, AwesomeIcon.LIGHTBULB, "72px");
AwesomeDude.setIcon(exitButton, AwesomeIcon.POWER_OFF, "48px");

Looks like this:

sweethomefx_1_0_1

Source
@ Bitbucket

Maven

<dependency>
  <groupId>de.jensd</groupId>
  <artifactId>fontawesomefx</artifactId>
  <version>8.0.1</version>
</dependency>