Kensuke Kousaka's Blog

Notes for Developing Software, Service.

Communicate UI thread from other thread in JavaFX

This article describes how to modify UI from other threads in JavaFX.

Server Side (Python)

First, the following code is server-side program which written in Python. It connects client program, send user input value, and receive command from client program.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import socket


def main():
    host_ip = '127.0.0.1'
    host_port = 51001
    buffer_size = 1024

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind((host_ip, host_port))
    sock.listen(1)

    while True:
        print 'Waiting for connection...'
        conn, addr = sock.accept()
        print '...connected from: ', addr

        # Sending data must add line break strings(\r\n)
        # Wait for connecting from client. Close connection if received `close` or empty data
        while True:
            # Get user input strings
            send_data = raw_input('> ')

            # Send user input strings to client
            conn.send(send_data + "\r\n")

            # Client must send `receive`, `close`, or empty string to server
            received_data = conn.recv(buffer_size)

            if not received_data or received_data == "close":
                # Close server connection
                break

        conn.close()
    sock.close() # Never executed
    
    
if __name__ == '__main__':
    main()

Operation flow is listed below.

  • Specify IP address and port number to wait connection from client
    • When connected, send data to client which input by user
    • Wait to receive data from client
    • Close server connection if received data is empty or close string, otherwise continue waiting for user input.

By the way, this program is server-side program, so sock.close() is never executed because program has to wait to connect from client.

Client-side (JavaFX)

Second, the following code is client-side GUI program which written in JavaFX.

package sample;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;

import java.io.IOException;

public class Main extends Application {
    @Override
    public void start (Stage primaryStage) throws IOException {
        // Load fxml
        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("sample.fxml"));
        primaryStage.setTitle("Sample");
        primaryStage.setScene(new Scene(fxmlLoader.load(), 600, 400));

        Controller controller = fxmlLoader.getController();

        primaryStage.setOnCloseRequest(e -> {
            controller.stageClose();
            primaryStage.close();
        });
        primaryStage.show();
    }

    public static void main (String[] args) {
        launch(args);
    }

This code(Main.java) is start point of program. Inflate layout from sample.fxml and show window by primaryStage.show().

The following code is GUI layout file, sample.fxml.

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<VBox fx:controller="sample.Controller" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
  <children>
    <Pane prefHeight="300.0" prefWidth="600.0">
      <children>
        <Label fx:id="label" alignment="CENTER" contentDisplay="CENTER" layoutX="284.0" layoutY="134.0" text="Label" />
      </children>
    </Panel>
    <Panel prefHeight="100.0" prefWidth="600.0">
      <children>
        <Button fx:id="button" onAction="#buttonClick" layoutX="266.0" layoutY="37.0" mnemonicParsing="false" text="Connect" />
      </children>
    </Panel>
  </children>
</VBox>

Put GUI screenshot which inflate by above fxml code.

f:id:k3n:20180609234955p:plain

Finally, the following code is controller class which manage GUI events.

package sample;

import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Controller {
    // bind to fxml fx:id
    public Label label;
    public Button button;

    // set this flag to false when you want to close socket connection
    public static volatile boolean connectStatus = false;
    SocketTask socketTask = null;

    // thread pool
    private ExecutorService service = Executors.newSingleThreadExecutor();

    @FXML
    public void buttonClick () throws InterruptedException {
        // bind with fxml onAction
        if (!connectStatus) {
            socketTask = new SocketTask();

            // sync socketTask updateMessage with label
            label.textProperty().bind(socketTask.messageProperty());

            // tasks after finished socketTask
            socketTask.setOnSucceeded(e -> label.textProperty().unbind());
            socketTask.setOnCancelled(e -> label.textProperty().unbind());
            socketTask.setOnFailed(e -> label.textProperty().unbind());

            // execute socketTask
            service.submit(socketTask);
        }
        else {
            connectStatus = false;
            socketTask = null;
        }
    }

    public void stageClose() {
        // Shutdown thread pool when close stage
        service.shutdown();
    }
}


class SocketTask extends Task {

    @Override
    protected Object call () throws Exception {
        String dstIP = "127.0.0.1";
        int dstPORT = 51001;

        Socket socket = new Socket(dstIP, dstPORT);
        Controller.connectStatus = true;

        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));

        while (true) {
            String receivedText = bufferedReader.readLine();
            updateMessage(receivedText);

            if (Controller.connectStatus) {
                bufferedWriter.write("received");
                bufferedWriter.flush();
            }
            else {
                bufferedWriter.write("close");
                bufferedWriter.flush();

                bufferedReader.close();
                bufferedWriter.close();
                socket.close();
                break;
            }
        }
        return null;
    }
}

This class instantiate when loading FXML. Execute operations after clicking Connect button. SocketTask, which manages socket communication, extends Task class. Before execute socket communication, bind SocketTask's messageProperty with label's textProperty.

Task class has updateMessage(String message) which update messageProperty. It can dynamically update label text by passing string data received from server.

Use Task's method can communicate UI from other thread.