xtracold
xtracold

Reputation: 23

Binding JavaFX Label to StringProperty

In my JavaFX application I have a label that I want to update with the StringProperty of another class that defines server functionality.

In the server class I have a StringProperty defined as shown below:

public class eol_server {

     private StringProperty serverMessagesProperty;
     etc..

I have a method to return the StringProperty on request:

    public StringProperty serverMessagesProperty() {
    if (serverMessagesProperty == null) {
        serverMessagesProperty = new SimpleStringProperty();
    }
    return serverMessagesProperty;
}

In the main gui class, Start() method I build the Scene, then instantiate a new server object. After this I update one of the labels in my Scene graph by binding it to the StringProperty of the server object:

public void start(Stage primaryStage) {
    ...set up all the gui components
     primaryStage.show();
     dss = new eol_server();
     lbl_dssMessage.textProperty().bind(dss.getServerMessagesProperty());
}

When I run the application, the scene is rendered as I expect, and the lbl_dssMessage text is set to value that is set up in the constructor of eol_server. But, from that point on the binding is not working, although I have actions that would update the StringProperty of the dss object they are not updating the label in the GUI.

Here is the complete file that generates a stripped down version of the scene:

public class eol_gui extends Application {

private static eol_server dss = null;
private static Stage primaryStage;


/**
 * Application Main Function
 * @param args
 */
public static void main(String[] args) {
    launch(args);
}

/**
 * @param stage
 */
private void setPrimaryStage(Stage stage) {
    eol_gui.primaryStage = stage;
}

/**
 * @return Stage for GUI
 */
static public Stage getPrimaryStage() {
    return eol_gui.primaryStage;
}

/* (non-Javadoc)
 * @see javafx.application.Application#start(javafx.stage.Stage)
 */
@Override
public void start(Stage primaryStage) {

    setPrimaryStage(primaryStage);

    BorderPane root = new BorderPane();
    Label lbl_dssMessage = new Label("initialized");
    HBox topMenu = new HBox();
    topMenu.getChildren().add(lbl_dssMessage);

    eol_supervisor_I config1 = new eol_supervisor_I();

    // Build Scene
    root.setStyle("-fx-background-color: #FFFFFF;");
    root.setTop(topMenu);
    primaryStage.setScene(new Scene(root, 300, 50));
    primaryStage.show();

    dss = new eol_server();
    dss.getServerMessagesProperty().addListener(new ChangeListener<Object>() {
                        @Override 
                        public void changed(ObservableValue<?> o,Object oldVal,Object newVal)
                        {
                            //Option 1: lbl_dssMessage.setText(newVal.toString());
                        }
    });
    //Option 2
    lbl_dssMessage.textProperty().bind(dss.serverMessagesProperty);
}

}

As you can see I have tried the bind method and a change listener. It looks like bind was working, as was the listener, in all cases except those that run in the server service threads. These throw the IllegalStateException due to not being on the main JavaFX application thread. How do I safely and correctly exchange messages from the service to the main thread?

The server is defined in the following class, which is intended to run services independent of the main JavaFX thread. But I would like to exchange info between the threads to show status. Ii'm trying to avoid the GUI hanging while the server connections and data exchanges are made.

public class eol_server {

public StringProperty serverMessagesProperty;

public eol_server() {
    /* Create Scripting Environment */

    serverMessagesProperty().set("Establishing Debug Server Environment");
    connect();

}
public boolean connect() {  
    serverMessagesProperty().set("Creating Server");
    ConnectService connectService = new ConnectService();
    connectService.start();
    return false;
}

public StringProperty serverMessagesProperty() {
    if (serverMessagesProperty == null) {
        serverMessagesProperty = new SimpleStringProperty();
    }
    return serverMessagesProperty;
}

public StringProperty getServerMessagesProperty() {
    return serverMessagesProperty;
}

private class ConnectService extends Service<Void> {

    @Override
    protected void succeeded() {

    }

    @Override
    protected void failed() {

    }

    @Override
    protected void cancelled() {

    }

    @Override
    protected Task<Void> createTask() {
        return new Task<Void>() {
            @Override
            protected Void call() throws Exception {
                serverMessagesProperty().set("Connecting....");
                Thread.sleep(5000);
                // DEMO: uncomment to provoke "Not on FX application thread"-Exception:
                // connectButton.setVisible(false);
                serverMessagesProperty().set("Waiting for server feedback");
                Thread.sleep(5000);
                return null;
            }
        };
    }
}

private class DisconnectService extends Service<Void> {

    @Override
    protected void succeeded() {

    }

    @Override
    protected void cancelled() {

    }

    @Override
    protected Task<Void> createTask() {
        return new Task<Void>() {
            @Override
            protected Void call() throws Exception {
                updateMessage("Disconnecting....");
                serverMessagesProperty().set("Disconnecting....");
                Thread.sleep(5000);
                updateMessage("Waiting for server feedback.");
                serverMessagesProperty().set("Waiting for server feedback.");
                Thread.sleep(5000);

                return null;
            }
        };
    }

}

}

Thanks in advance

JW

Upvotes: 1

Views: 1708

Answers (2)

onurozcelik
onurozcelik

Reputation: 1214

Read the below paragraph from JavaFX Node documentation.

Node objects may be constructed and modified on any thread as long they are not yet attached to a Scene in a Window that is showing. An application must attach nodes to such a Scene or modify them on the JavaFX Application Thread.

The correct and more functional way to solve your problem is

dss.serverMessagesProperty.addListener((observable, oldValue, newValue) -> Platform.runLater(() -> lbl_dssMessage.setText(newValue));

Upvotes: 0

xtracold
xtracold

Reputation: 23

I seem to have a solution, derived from what I read on the runlater() Platform method. Also see discussion here

Multi-Threading error when binding a StringProperty

changing my listener to invoke the message update via runLater seems to break the multi-threading problem. Yes I expect it is not immediate but it will be very very close to immediate and good enough. I appreciate that JavaFX requires you to not mess with the Application thread values / nodes etc etc of the scene graph but it is quite a complicated area of the JavaFX library.

Here is the listener that worked for me

// Get new DSS session active
    dss = new eol_server();

    dss.serverMessagesProperty.addListener(new ChangeListener<Object>() {
        @Override
        public void changed (ObservableValue<?> observable, Object oldValue, Object newValue) {
            Platform.runLater(() -> lbl_dssMessage.setText(newValue.toString()));
        }
    });
}

Happy for further discussion as to why this works when the other options did not, also open to any other more elegant suggestions.

Upvotes: 0

Related Questions