Dec 19

MQTT.fx 0.0.12 released

Download latest binaries at mqttfx.org.
Bugs and feature requests can be reported via the issue tracker.

New in this version:

MQTT version support for 3.1 and 3.1.1
The MQTT version can be set via ConnectonProfile (default: 3.1.1, if not supported by broker: 3.1)
mqttfx-mqtt-version

New: “Log”-tab
MQTT.fx 0.0.12 introduces a new tab to capture/show the logging stream:
mqttfx-log-tab

Changed rendering of messages
mqttfx-message-tile

Wildcard Topics
Improved handling of topics containing wildcards.

MQTT.fx is now using paho 1.0.1

Fixed bugs and issues
Fixed some issues when connect/disconnect/connection lost

Nov 20

ShichimiFX 1.0.1 released

I have added extracted some more classes from my application projects (e.g. from MQTT.fx) and added them as new features to ShichimiFX (and released 1.0.1).

Get the code from my ShichimiFX Bitbucket Repo

Maven

<dependency>
  <groupId>de.jensd</groupId>
  <artifactId>shichimifx</artifactId>
  <version>1.0.1</version>
</dependency>

AnimationDude
Provides support for Fade-in/Fade-out-Transition of controls:
AnimationDude.addInOutFadeTransition(Duration duration, Node node)
AnimationDude.addInOutFadeTransition(Duration duration, Node nodeToFade, Node trigger)

Make a ListCell (this) a trigger to to fade in a button bar on mouse hover:
AnimationDude.addInOutFadeTransition(Duration.millis(200), buttonsBox, this);

public class AnimationDude {
    
    public final static void  addInOutFadeTransition(Duration duration, Node node) {
        addInOutFadeTransition(duration, node, node);
    }

    public final static void  addInOutFadeTransition(Duration duration, Node nodeToFade, Node trigger) {
        nodeToFade.setOpacity(0.0);
        final FadeTransition fadeOutTransition = new FadeTransition(duration, nodeToFade);
        fadeOutTransition.setFromValue(1.0);
        fadeOutTransition.setToValue(0.0);
        final FadeTransition fadeInTransition = new FadeTransition(duration, nodeToFade);
        fadeInTransition.setFromValue(0.0);
        fadeInTransition.setToValue(1.0);
        trigger.setOnMouseEntered(e->{
                    fadeOutTransition.stop();
                    fadeInTransition.play();
        });
        trigger.setOnMouseExited(e->{
                    fadeInTransition.stop();
                    fadeOutTransition.play();
        });
    }
}

ConsoleDude
Provides support for switching System.out/Sytem.err (ConsoleStream)or bypassing a logging output (ConsoleStreamHandler) a JavaFX text components.

Usage

Use “stdOutMessageArea” for System.out and System.err output:
hook StdOut + StdErr: ConsoleDude.hookStdOutTo(stdOutMessageArea);
restore StdOut + StdErr: ConsoleDude.restoreStdOut();

Also you can observe the status, e. g.:
hookButton.disableProperty().bind(ConsoleDude.stdOutHookedProperty());
releaseButton.disableProperty().bind(ConsoleDude.stdOutHookedProperty().not());

Use “logMessageArea” for logging output:
LOGGER.addHandler(ConsoleDude.createConsoleStreamHandler(logMessageArea));

public class ConsoleStream extends OutputStream {

    private final TextInputControl output;

    public ConsoleStream(TextInputControl ta) {
        this.output = ta;
    }

    @Override
    public void write(int i) throws IOException {
        Platform.runLater(() -> {
            output.appendText(String.valueOf((char) i));
        });
    }
}
public class ConsoleStreamHandler extends StreamHandler{

    public ConsoleStreamHandler(OutputStream out, Formatter formatter) {
        super(out, formatter);
    }

    @Override
    public synchronized void publish(LogRecord record) {
        super.publish(record);
        flush();
    }
}
public class ConsoleDude {

    public final static PrintStream STD_OUT = System.out;
    public final static PrintStream STD_ERR = System.err;
    private static BooleanProperty stdOutHooked = new SimpleBooleanProperty();

    public static void hookStdOutTo(TextInputControl outputArea) {
        PrintStream ps = new PrintStream(new ConsoleStream(outputArea), true);
        System.setOut(ps);
        System.setErr(ps);
        stdOutHookedProperty().set(true);
    }

    public static void restoreStdOut() {
        System.setOut(STD_OUT);
        System.setErr(STD_ERR);
        stdOutHookedProperty().set(false);
    }

    public static BooleanProperty stdOutHookedProperty() {
        if (stdOutHooked == null) {
            stdOutHooked = new SimpleBooleanProperty();
        }
        return stdOutHooked;
    }

    public boolean isHooked() {
        return stdOutHookedProperty().get();
    }

    public static ConsoleStreamHandler createConsoleStreamHandler(TextInputControl outputArea) {
        return createConsoleStreamHandler(outputArea,new SimpleFormatter());
    }

    public static ConsoleStreamHandler createConsoleStreamHandler(TextInputControl outputArea, Formatter formatter) {
        return new ConsoleStreamHandler(new ConsoleStream(outputArea), formatter);
    }

}
Nov 11

MQTT.fx 0.0.11 released

Download latest binaries in all flavours at mqttfx.org.
Bugs and feature requests can be reported via the issue tracker.

UPDATE: Now available: MQTT.fx 0.0.11-1 containing a fix for wildcard handling!

New in this version:

Extended option menu
You can now subscribe to all recent topics with one click and also unsubscrive from all topics is supported:
messages_subscribe_all

Improved messages rendering
– the payload of received messages is now collapsible (Issue #15):
collabsible
– topics can be unsubscribed by one click on the trash-icon:
unsubscribe
– the selection of a subscribed topic sets the value of the topics choice boxfor e.g. un-subscrition (Issue #11)
– fixed issues with topics / messages colors

New Filter-Option
– you can now set a filter to show only the latest recevied messages (Issue #8)
onlylatest
– “show notification” property and “show only latest” property are stored per connection profile

Always on Top
Added a new option to set whether detached tabs should be opened always on top:
alwaysontop

Bundled with Java 8u25

Nov 04

Properties Extractor: Best way to get the ListView instantly updating its elements

This post is about how to deal with JavaFX ListViews and TableViews and how these controls are getting informed about changed content of the contained elements.
I wonder why I didn’t find anything about the following pattern in the relevant books as this is a really crucial mechanism.
Many posts out there suggest to force triggering a ChangeEvent getting a ListView to refresh by calling

list.remove(POJO);
list.add(index,POJO);

after each commited change! Brrr ;-)!

But there is a much better way:
Enable your list to report changes on the element by providing an properties extractor.

The Demo App
I have created a small demo app to play with for giving it a try.
Basically two TableViews and one ListView all sharing the same data.
To change properties of the elements one TableView is editable:
PropertiesExtractor Demo1

The DataModel

The compulsive PersonBean folling the JavaFX Bean Pattern/Convention

public class PersonBean {

    private StringProperty firstName;
    private StringProperty lastName;
    private ObjectProperty<LocalDate> birthday;
    private ObjectBinding<Long> age;

    public PersonBean() {
    }

    public PersonBean(String firstName, String lastName, LocalDate birthday) {
        setFirstName(firstName);
        setLastName(lastName);
        setBirthday(birthday);
    }

    public final StringProperty firstNameProperty() {
        if (firstName == null) {
            firstName = new SimpleStringProperty();
        }
        return firstName;
    }

    public final String getFirstName() {
        return firstNameProperty().get();
    }

    public final void setFirstName(final java.lang.String firstName) {
        firstNameProperty().set(firstName);
    }

    public final StringProperty lastNameProperty() {
        if (lastName == null) {
            lastName = new SimpleStringProperty();
        }
        return lastName;
    }

    public final java.lang.String getLastName() {
        return lastNameProperty().get();
    }

    public final void setLastName(final java.lang.String lastName) {
        lastNameProperty().set(lastName);
    }

    public final ObjectProperty<LocalDate> birthdayProperty() {
        if (birthday == null) {
            birthday = new SimpleObjectProperty<>();
        }
        return birthday;
    }

    public final LocalDate getBirthday() {
        return birthdayProperty().get();
    }

    public final void setBirthday(final java.time.LocalDate birthday) {
        birthdayProperty().set(birthday);

    }

    public String stringValue() {
        return String.format("%s %s %s", getFirstName(), getLastName(), getBirthday().format(DateTimeFormatter.ISO_LOCAL_DATE));
    }

    public final ObjectBinding<Long> ageBinding() {
        if (age == null) {
            age = new ObjectBinding<Long>() {
                {
                    bind(birthdayProperty());
                }

                @Override
                protected Long computeValue() {
                    if (getBirthday() == null) {
                        return null;
                    }
                    return getBirthday().until(LocalDate.now(), ChronoUnit.YEARS);
                }
            };
        }
        return age;
    }

    public static Callback<PersonBean, Observable[]> extractor() {
        return (PersonBean p) -> new Observable[]{p.lastNameProperty(), p.firstNameProperty(), p.birthdayProperty(), p.ageBinding()};
    }
}

DataModel containing a List of randomly created PersonBeans:

public class DataModel {

    private ObservableList<PersonBean> personFXBeans;

    public DataModel() {
        init();
    }

    private void init() {
        personFXBeans = DataSource.getRandomPersonBeansList(100);
    }

    public ObservableList<PersonBean> getPersonFXBeans() {
        return personFXBeans;
    }
}

As you may know to assign a DataModel e.g. to a TableView or a ListView in JavaFX you just have to use the setItems(ObvervableList) method.

@FXML
public void onFillWithDemoDataFXBeans() {
  readOnlyListView.setItems(model.getPersonFXBeans());
  readOnlyTableView.setItems(model.getPersonFXBeans());
  editableTableView.setItems(model.getPersonFXBeans());
}

Now getting a TableView informed about property changes of contained elements is already done you for by the binding either in two ways:
via a PropertyValueFactory and by more or less direct property binding:

readOnlyFirstNameColumn.setCellValueFactory(new PropertyValueFactory<>("firstName"));
readOnlyLastNameColumn.setCellValueFactory(new PropertyValueFactory<>("lastName"));
readOnlyBirthdayColumn.setCellValueFactory(new PropertyValueFactory<>("birthday"));
readOnlyAgeColumn.setCellValueFactory(i -> i.getValue().ageBinding());

editableFirstNameColumn.setCellValueFactory(i -> i.getValue().firstNameProperty());
editableLastNameColumn.setCellValueFactory(i -> i.getValue().lastNameProperty());
editableBirthdayColumn.setCellValueFactory(i -> i.getValue().birthdayProperty());
ageColumn.setCellValueFactory(i -> i.getValue().ageBinding());

But the ListView basically only observes the list and not the properties of each element of that list.

When using a ObservableList created by FXCollections.observableArrayList() the ListView will only refresh on ListChange Events like remove() an add() of elements. Therefore

list.remove(POJO);
list.add(index,POJO);

after each commited change.

But there is a much better way:
Enable your list to report changes on the element by providing an properties extractor.
You don’t have to care about refreshing then!

ObservableList persons = FXCollections.observableArrayList(PersonBean.extractor());

See DataSource.getRandomPersonBeansList(int length):

public static ObservableList<PersonBean> getRandomPersonBeansList(int length) {
        ObservableList<PersonBean> persons = FXCollections.observableArrayList(PersonBean.extractor());
        for (int i = 0; i < length; i++) {
            persons.add(new PersonBean(getRandomName(), getRandomLastname(), getRandomLocalDate()));
        }
        return persons;
    }

This Extrator is basically a Callback containing an array of Obvervables which are then observed by the Obervablelist (more precicely: ObservableListWrapper):

My PersonBean already provides it’s extrator callback:

public static Callback<PersonBean, Observable[]> extractor() {
   return (PersonBean p) -> new Observable[]{p.lastNameProperty(), p.firstNameProperty(), p.birthdayProperty(), p.ageBinding()};
}

Following this pattern all controls are updated instantly after applying the change.

Edit data…
PropertiesExtractor Demo3

and commit:
PropertiesExtractor Demo4

THE CODE PLEASE!
You can find the complete code at my BitBucket Repo

Oct 16

MQTT.fx 0.0.10 released

MQTT.fx 0.0.10 was released.

Download latest binaries in all flavours at mqttfx.org.
Bugs and feature requests can be reported via the issue tracker.

New in this version:
MQTT-Message Clipboard

Option to copy the message payload to clipboard
copypayload
Service menus at publish and subscription panel: now you can clear received messages buffer and the recent topics lists
publish_service
subscribe_service

Extended logging to mqttfx.log
Added Application Window Icon (for Windows)
Now running/bundled with Java 8u20
Updated 3rd-party libs (e.g. ControlsFX 8.20.7)

Oct 15

ShichimiFX – Yet another JavaFX utils lib

shichimi

ShichimiFX is a small lib containing helper/util classes extracted from my other projects which I commonly need. The lib will be extended if it turns out that I need a util class in more than one project…
(Shichimi is my favorite Japanese spice mixture)

And yes it also yet another utils lib containing (among others):

For further details refer to the ShichimiFX Bitbucket Repo

 

Maven

<dependency>
  <groupId>de.jensd</groupId>
  <artifactId>shichimifx</artifactId>
  <version>1.0.0</version>
</dependency>
Oct 09

The JDK 8u20 “Tap trap”

When switching MQTT.fx to JDK 8u20 I was nicked by the fix of RT-24658.
The fix definitely makes sense but may clash with my TabPaneDetacher approach.

In MQTT.fx the content of the Tabs is disabled as long the MQTT-Broker is not connected.
And I am using Property Bindings for this. E.g. in the PublishController “publishBox” is the root of the Publish-Tab content and it is bound to the brokerConnectedProperty():

publishBox.disableProperty().bind(model.brokerConnectedProperty().not());

This worked well until JDK 8u20 as now this leads into:

Exception in thread "JavaFX Application Thread" java.lang.RuntimeException: A bound value cannot be set.
at javafx.beans.property.BooleanPropertyBase.set(BooleanPropertyBase.java:139)
at javafx.scene.Node.setDisable(Node.java:1539)
[...]

This is because each time when e.g. adding a Tab to a TabPane updateDisabled() is called to explicitly SET the disabled value of the content also:

 private void updateDisabled() {
        boolean disabled = isDisable() || (getTabPane() != null && getTabPane().isDisabled());
        setDisabled(disabled);

        // Fix for RT-24658 - content should be disabled if the tab is disabled
        Node content = getContent();
        if (content != null) {
            content.setDisable(disabled);
        }
    }

Options to deal with this:
Quit using Binding here and use ChangeListener instead.
Or simply wrap the content root in another Pane.

Sep 19

FontAwesomeFX 8.0.10 released

FontAwesomeFX 8.0.10 introduces Stacked Icons and FXML support

Binaries: Download
Sources: Bitbucket
Maven:

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

Stacked Icons

FontAwesome is featuring Stacked Icons which is clearly also for JavaFX Applications this is a useful feature.

To support Stackend Icons FontAwesomeFX 8.0.10 comes with two new classes: AwesomeIconsStack and Icon.

AwesomeIconsStack
is basically a StackPane providing convent methods to add and remove Icons.

Icon
is a Node which is able to display a styleable FontAwesome icon. Icons can be styled in two ways:

by setting a style class:

Region stackedIcon2 = AwesomeIconsStack.create()
                .add(Icon.create()
                        .icon(AwesomeIcon.SQUARE)
                        .size("3em")
                        .styleClass("stack-base"))
                .add(Icon.create()
                        .icon(AwesomeIcon.STAR)
                        .size("2em")
                        .styleClass("stack-top")
                );

or by setting the style:

Region stackedIcon3 = AwesomeIconsStack.create()
                .add(Icon.create()
                        .icon(AwesomeIcon.SQUARE)
                        .style("-fx-font-size: 4em; -fx-text-fill: yellowgreen;"))
                .add(Icon.create()
                        .icon(AwesomeIcon.APPLE)
                        .style("-fx-font-size: 3em; -fx-text-fill: white;")
                );

fontawesomefx_8010_1

Thats the code to create Icons like above:

Region stackedIcon1 = AwesomeIconsStack.create().add(Icon.create());

Region stackedIcon2 = AwesomeIconsStack.create()
        .add(Icon.create()
                .icon(AwesomeIcon.SQUARE)
                .size("3em")
                .styleClass("stack-base"))
        .add(Icon.create()
                .icon(AwesomeIcon.STAR)
                .size("2em")
                .styleClass("stack-top")
        );

Region stackedIcon3 = AwesomeIconsStack.create()
        .add(Icon.create()
                .icon(AwesomeIcon.SQUARE)
                .style("-fx-font-size: 4em; -fx-text-fill: yellowgreen;"))
        .add(Icon.create()
                .icon(AwesomeIcon.APPLE)
                .style("-fx-font-size: 3em; -fx-text-fill: white;")
        );

Region stackedIcon4 = AwesomeIconsStack.create()
        .add(Icon.create()
                .icon(AwesomeIcon.SQUARE)
                .style("-fx-font-size: 4em; -fx-text-fill: yellowgreen;"))
        .add(Icon.create()
                .icon(AwesomeIcon.APPLE)
                .style("-fx-font-size: 3em; -fx-text-fill: black;")
        );

Region stackedIcon5 = AwesomeIconsStack.create()
        .add(Icon.create()
                .icon(AwesomeIcon.BUG)
                .style("-fx-font-size: 2em; -fx-text-fill: black;"))
        .add(Icon.create()
                .icon(AwesomeIcon.BAN)
                .style("-fx-font-size: 4em; -fx-text-fill: red; -fx-opacity: 0.5;")
        );

Region stackedIcon6 = AwesomeIconsStack.create()
        .add(Icon.create()
                .icon(AwesomeIcon.CIRCLE)
                .style("-fx-font-size: 8em; -fx-text-fill: linear-gradient(#70b4e5 0%, #247cbc 70%, #2c85c1 85%);"))
        .add(Icon.create()
                .icon(AwesomeIcon.TWITTER)
                .style("-fx-font-size: 4em; -fx-text-fill: white;")
        );

Region stackedIcon7 = AwesomeIconsStack.create()
        .add(Icon.create()
                .icon(AwesomeIcon.CIRCLE)
                .style("-fx-font-size: 12em; -fx-text-fill: linear-gradient(#70b4e5 0%, #247cbc 70%, #2c85c1 85%);"))
        .add(Icon.create()
                .icon(AwesomeIcon.BITBUCKET)
                .style("-fx-font-size: 6em; "
                        + "-fx-text-fill: linear-gradient(#ffffff, #d2d2d2); "
                        + "-fx-effect: dropshadow( one-pass-box , rgba(0,0,0,0.8) , 4 , 0.0 , 1 , 1 );")
        );

HBox stackIconBox = new HBox();
stackIconBox.setSpacing(5.0);
stackIconBox.getChildren().addAll(stackedIcon1, stackedIcon2, stackedIcon3, stackedIcon4, stackedIcon5, stackedIcon6, stackedIcon7);

Region iconStack1 = AwesomeIconsStack.create()
        .add(Icon.create()
                .icon(AwesomeIcon.CIRCLE)
                .style("-fx-font-size: 12em; -fx-text-fill: linear-gradient(#70b4e5 0%, #247cbc 70%, #2c85c1 85%);"))
        .add(Icon.create()
                .icon(AwesomeIcon.BITBUCKET)
                .style("-fx-font-size: 6em; "
                        + "-fx-text-fill: linear-gradient(#ffffff 0%, #d2d2d2); "
                        + "-fx-effect: dropshadow( one-pass-box , rgba(0,0,0,0.8) , 4 , 0.0 , 1 , 1 );"))
        .add(new Icon(AwesomeIcon.BAN, null, "-fx-font-size: 12em; -fx-text-fill: red; -fx-opacity: 0.5;", null));

HBox stackIconBox2 = new HBox();
stackIconBox2.setSpacing(5.0);
stackIconBox2.getChildren().addAll(iconStack1);

Have a look at “iconStack1” in the code above:
the number of stacked Icons is not limited to only two.

Support for FXML

In respect to the issue opened by Manuel Maukys (@manuel_mauky) I played with the new @NamedArg annotation. It’s also possible to use the Icon in FXML (even stacked):

fontawesomefx_8010_2

<VBox alignment="CENTER" spacing="10.0">
  <children>
    <Icon awesomeIcon="STAR" size="4em" style="-fx-font-size: 6em; -fx-text-fill: linear-gradient(#ffffff, #d2d2d2); -fx-effect: dropshadow( one-pass-box , rgba(0,0,0,0.5) , 8 , 0.0 , 0 , 0 );" styleClass="" />
    <StackPane>
      <children>
        <Icon awesomeIcon="CIRCLE" style="-fx-font-size: 24em; -fx-text-fill: linear-gradient(#70b4e5 0%, #247cbc 70%, #2c85c1 85%);" styleClass="" size=""/>
        <Icon awesomeIcon="BITBUCKET" style="-fx-font-size: 12em; -fx-text-fill: linear-gradient(#ffffff, #d2d2d2); -fx-effect: dropshadow( one-pass-box , rgba(0,0,0,0.8) , 4 , 0.0 , 1 , 1 );" styleClass="" size=""/>
      </children>
    </StackPane>
  </children>
</VBox>

I am not a Ninja master yet, but somehow I imported a custom component to get it also supported in SceneBuilder 2.0:

fontawesomefx_8010_3

Sep 13

MQTT.fx 0.0.9 released

A new version of MQTT.fx is available (probably the last release before JavaOne)

Download latest binaries in all flavours: mqttfx.org.
Bugs and feature requests can be reported via the issue tracker.

This release comes with some new features:

Message Font Size

(in respect to Gerrit Grunwald @hansolo_)
To improve ledibility of large message bodies now the font size of messages can be set to a fixed value (0.6em – 1.5em) or a dynamic size depending on message size (1.0em – 0.6em).
You can open the new Settings Dialog via Menu Extras / Settings:

settings

Dynamic message font size:

fonts

Clear History

(also in respect to Gerrit Grunwald)
The Connection Profile Dialog (Section “General”) and the Publish/Subscribe panels now the history of last topics can be cleared.

clear_publish

clear_sub

clear_profile

“Never again drive me crazy with silly questions”

(in respect to David Janes @dpjanes)
The Exit Dialog now offers an option “Do not ask me again!” to get not asked again when closing the app.

exit

Bugfixes

Also I again have fixed some bugs (typos, usability, layout + style issues).