Обновлять пользовательский интерфейс JavaFX не через действие одного из его элементов - PullRequest
0 голосов
/ 11 июня 2019

Я пытаюсь создать приложение с графическим интерфейсом в JavaFX и этот графический интерфейс должен обновляться с помощью триггера, который представляет собой новую запись, полученную потребителем Amazon kinesis.Поэтому я немного поиграл с ним и попытался закрасить кружок в цвет по своему выбору (золото), когда получаю новую запись, но, к моему удивлению, GUI не обновлялся правильно.Даже после того, как я попытался запустить его с Platform.runlater, как предложили некоторые вопросы.Когда я отладил код, к своему удивлению, я также обнаружил, что значение FXML свойства fill действительно изменяется, но это происходит до того, как моя точка останова находится на дескрипторе функции, которая должна его изменить (что само по себе странно).Но по какой-то причине мой графический интерфейс по-прежнему отказывается обновляться.

Если я создам какую-то кнопку и запускаю с ней весь процесс, она все же изменит цвет круга.

Справка будеточень ценится.

Вот моя функция записи процесса (мониторингLogic.updateUI обновляет пользовательский интерфейс):

public void processRecords(List<Record> records, IRecordProcessorCheckpointer checkpointer) {
    long timestamp = 0;
    List<Long> seqNos = new ArrayList<>();

    for (Record r : records) {

        timestamp = Math.max(timestamp, Long.parseLong(r.getPartitionKey()));

        try {
            byte[] b = new byte[r.getData().remaining()];
            r.getData().get(b);
            seqNos.add(Long.parseLong(new String(b, "UTF-8").split("#")[0]));
            //this thread adds the transaction to the DB
            Thread addTransactionToDBThread = new Thread() {
                public void run() {
                    try {
                        JSONObject jsonObj = new JSONObject(new String(b, "UTF-8").split("#")[1]);
                        Transaction transaction = Transaction.convertJsonToTransaction(jsonObj);
                        //add the transaction to the database
                        dataBase.addTransactionToDB(transaction);
                        //update the user-interface about the last transaction in the system
                        DATA_STATUS transactionStatus = monitoringLogic.getStatus(transaction);
                        monitoringLogic.updateUI(transaction.getUuid(), transaction.getSender(), transaction.getReceiver(), transactionStatus);
                        Thread.sleep(1000);
                    } catch(InterruptedException e) {
                        e.printStackTrace();
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                    }
                }
            };
            addTransactionToDBThread.start();
        } catch (Exception e) {
            log.error("Error parsing record", e);
            System.exit(1);
        }
    }

    synchronized (lock) {
        if (largestTimestamp.get() < timestamp) {
            log.info(String.format(
                    "Found new larger timestamp: %d (was %d), clearing state",
                    timestamp, largestTimestamp.get()));
            largestTimestamp.set(timestamp);
            sequenceNumbers.clear();
        }

        // Only add to the shared list if our data is from the latest run.
        if (largestTimestamp.get() == timestamp) {
            sequenceNumbers.addAll(seqNos);
            Collections.sort(sequenceNumbers);
        }
    }
    try {
        checkpointer.checkpoint();
    } catch (Exception e) {
        log.error("Error while trying to checkpoint during ProcessRecords", e);
    }
}

Вот мой контроллер пользовательского интерфейса:

package com.userInterface;

import com.DATA_STATUS;
import com.Main;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import org.apache.commons.io.FileUtils;
import java.io.*;
import java.util.*;

public class UIController implements Observer {
    @FXML
    private TableView<Record> table = new TableView<Record>();
    @FXML
    public TableColumn<Record, String> legalFlow1 = new TableColumn<Record, String>();
    @FXML
    public TableColumn<Record, String> legalFlow2 = new TableColumn<Record, String>();
    @FXML
    public TableColumn<Record, String> legalFlow3 = new TableColumn<Record, String>();
    @FXML
    public TableColumn<Record, String> legalFlow4 = new TableColumn<Record, String>();
    @FXML
    public TableColumn<Record, String> legalFlow5 = new TableColumn<Record, String>();
    @FXML
    public TableColumn<Record, String> legalFlow6 = new TableColumn<Record, String>();
    @FXML
    public Button exitButton = new Button();
    @FXML
    public Circle legalFlow1circle1 = new Circle();


    public Map<String, String> circles = new HashMap<String, String>();
    //last changes
    public UIController() {
        fillMap();
    }

    @FXML
    /**
     * The function exits from the game
     */
    public void pressExitButton() {
        Main.dropDBSchema();
        System.exit(0);
    }


    public void actionPerformed() {
        Platform.runLater(new Runnable() {
            @Override public void run() {
                handle();
            }
        });
    }

    public void handle() {
        legalFlow1circle1.setFill(Color.GOLD);
    }


        public void changeCircleColor(String key, DATA_STATUS status) {
        exitButton.fire();

    }

    private void fillMap() {
        this.circles.put("3866f99b-c412-4ce7-89dc-a53a06fa0fbc_ms1_ms2", "legalFlow1circle1");
        this.circles.put("3866f99b-c412-4ce7-89dc-a53a06fa0fbc_ms2_ms3", "legalFlow1circle2");
        this.circles.put("3866f99b-c412-4ce7-89dc-a53a06fa0fbc_ms3_ms4", "legalFlow1circle3");
        this.circles.put("a24854d9-1417-4468-852b-2fd442c844ce_ms3_ms1", "legalFlow2circle1");
        this.circles.put("a24854d9-1417-4468-852b-2fd442c844ce_ms1_ms2", "legalFlow2circle2");
        this.circles.put("332c464c-1b73-455e-800b-285683892285_ms4_ms2", "legalFlow3circle1");
        this.circles.put("332c464c-1b73-455e-800b-285683892285_ms2_ms3", "legalFlow3circle2");
        this.circles.put("332c464c-1b73-455e-800b-285683892285_ms3_ms1", "legalFlow3circle3");
        this.circles.put("ba3ef2e3-356e-4951-9854-f1803bb91653_ms2_ms1", "legalFlow4circle1");
        this.circles.put("ba3ef2e3-356e-4951-9854-f1803bb91653_ms1_ms4", "legalFlow4circle2");
        this.circles.put("ba3ef2e3-356e-4951-9854-f1803bb91653_ms4_ms3", "legalFlow4circle3");
        this.circles.put("b4fea051-d49c-46b9-a544-8cda3d4a8701_ms1_ms3", "legalFlow5circle1");
        this.circles.put("b4fea051-d49c-46b9-a544-8cda3d4a8701_ms3_ms2", "legalFlow5circle2");
        this.circles.put("02f77f86-0370-49a5-a26d-e3cfc2921d6c_ms3_ms2", "legalFlow6circle1");
        this.circles.put("02f77f86-0370-49a5-a26d-e3cfc2921d6c_ms2_ms4", "legalFlow6circle2");
        this.circles.put("02f77f86-0370-49a5-a26d-e3cfc2921d6c_ms4_ms1", "legalFlow6circle3");
    }

    @Override
    public void update(Observable o, Object arg) {
        actionPerformed();
    }
}

Вотмой файл FXML:

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

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.shape.Circle?>

<AnchorPane prefHeight="563.0" prefWidth="780.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.userInterface.UIController">
   <children>
      <Button id="exitButton" fx:id="exitButton" layoutX="787.0" layoutY="488.0" mnemonicParsing="false" onAction="#pressExitButton" text="Exit" />
      <TableView id="table" fx:id="table" layoutX="120.0" layoutY="199.0" prefHeight="200.0" prefWidth="730.0">
        <columns>
          <TableColumn id="legalFlow1" fx:id="legalFlow1" prefWidth="124.0" text="legalFlow1" />
            <TableColumn id="legalFlow2" fx:id="legalFlow2" prefWidth="121.0" text="legalFlow2" />
            <TableColumn id="legalFlow3" fx:id="legalFlow3" prefWidth="116.0" text="legalFlow3" />
            <TableColumn id="legalFlow4" fx:id="legalFlow4" prefWidth="120.0" text="legalFlow4" />
            <TableColumn id="legalFlow5" fx:id="legalFlow5" prefWidth="124.0" text="legalFlow5" />
            <TableColumn id="legalFlow6" fx:id="legalFlow6" prefWidth="124.0" text="legalFlow6" />
        </columns>
      </TableView>
      <ListView id="legalFlow4Content" fx:id="legalFlow4Content" layoutX="479.0" layoutY="225.0" prefHeight="173.0" prefWidth="123.0" />
      <ListView id="legalFlow5Content" fx:id="legalFlow5Content" layoutX="602.0" layoutY="225.0" prefHeight="173.0" prefWidth="123.0" />
      <ListView id="legalFlow6Content" fx:id="legalFlow6Content" layoutX="725.0" layoutY="225.0" prefHeight="173.0" prefWidth="123.0" />
      <ListView id="legalFlow3Content" fx:id="legalFlow3Content" layoutX="364.0" layoutY="225.0" prefHeight="173.0" prefWidth="115.0" />
      <ListView id="legalFlow2Content" fx:id="legalFlow2Content" layoutX="241.0" layoutY="225.0" prefHeight="173.0" prefWidth="123.0" />
      <ListView id="legalFlow1Content" fx:id="legalFlow1Content" layoutX="118.0" layoutY="225.0" prefHeight="173.0" prefWidth="123.0" />
      <Circle id="legalFlow1circle3" fx:id="legalFlow1circle3" fill="#d6d8da" layoutX="135.0" layoutY="363.0" radius="12.0" stroke="BLACK" strokeType="INSIDE" />
      <Circle id="legalFlow1circle2" fx:id="legalFlow1circle2" fill="#d6d8da" layoutX="135.0" layoutY="311.0" radius="12.0" stroke="BLACK" strokeType="INSIDE" />
      <Circle id="legalFlow1circle1" fx:id="legalFlow1circle1" fill="#d6d8da" layoutX="135.0" layoutY="258.0" radius="12.0" stroke="BLACK" strokeType="INSIDE" />
      <Circle id="legalFlow2circle2" fx:id="legalFlow2circle2" fill="#d6d8da" layoutX="257.0" layoutY="342.0" radius="12.0" stroke="BLACK" strokeType="INSIDE" />
      <Circle id="legalFlow2circle1" fx:id="legalFlow2circle1" fill="#d6d8da" layoutX="256.0" layoutY="281.0" radius="12.0" stroke="BLACK" strokeType="INSIDE" />
      <Circle id="legalFlow3circle3" fx:id="legalFlow3circle3" fill="#d6d8da" layoutX="380.0" layoutY="364.0" radius="12.0" stroke="BLACK" strokeType="INSIDE" />
      <Circle id="legalFlow3circle2" fx:id="legalFlow3circle2" fill="#d6d8da" layoutX="380.0" layoutY="309.0" radius="12.0" stroke="BLACK" strokeType="INSIDE" />
      <Circle id="legalFlow3circle1" fx:id="legalFlow3circle1" fill="#d6d8da" layoutX="380.0" layoutY="254.0" radius="12.0" stroke="BLACK" strokeType="INSIDE" />
      <Circle id="legalFlow4circle3" fx:id="legalFlow4circle3" fill="#d6d8da" layoutX="494.0" layoutY="363.0" radius="12.0" stroke="BLACK" strokeType="INSIDE" />
      <Circle id="legalFlow4circle2" fx:id="legalFlow4circle2" fill="#d6d8da" layoutX="494.0" layoutY="308.0" radius="12.0" stroke="BLACK" strokeType="INSIDE" />
      <Circle id="legalFlow4circle1" fx:id="legalFlow4circle1" fill="#d6d8da" layoutX="495.0" layoutY="254.0" radius="12.0" stroke="BLACK" strokeType="INSIDE" />
      <Circle id="legalFlow5circle2" fx:id="legalFlow5circle2" fill="#d6d8da" layoutX="618.0" layoutY="340.0" radius="12.0" stroke="BLACK" strokeType="INSIDE" />
      <Circle id="legalFlow5circle1" fx:id="legalFlow5circle1" fill="#d6d8da" layoutX="618.0" layoutY="278.0" radius="12.0" stroke="BLACK" strokeType="INSIDE" />
      <Circle id="legalFlow6circle3" fx:id="legalFlow6circle3" fill="#d6d8da" layoutX="740.0" layoutY="363.0" radius="12.0" stroke="BLACK" strokeType="INSIDE" />
      <Circle id="legalFlow6circle2" fx:id="legalFlow6circle2" fill="#d6d8da" layoutX="740.0" layoutY="310.0" radius="12.0" stroke="BLACK" strokeType="INSIDE" />
      <Circle id="legalFlow6circle1" fx:id="legalFlow6circle1" fill="#d6d8da" layoutX="740.0" layoutY="257.0" radius="12.0" stroke="BLACK" strokeType="INSIDE" />
      <Label layoutX="151.0" layoutY="250.0" text="MS1 to MS2" />
      <Label layoutX="150.0" layoutY="302.0" text="MS2 to MS3" />
      <Label layoutX="151.0" layoutY="355.0" text="MS3 to MS4" />
      <Label layoutX="270.0" layoutY="273.0" text="MS3 to MS1" />
      <Label layoutX="270.0" layoutY="334.0" text="MS1 to MS2" />
      <Label layoutX="396.0" layoutY="246.0" text="MS4 to MS2" />
      <Label layoutX="395.0" layoutY="301.0" text="MS2 to MS3" />
      <Label layoutX="396.0" layoutY="355.0" text="MS3 to MS1" />
      <Label layoutX="510.0" layoutY="246.0" text="MS2 to MS1" />
      <Label layoutX="508.0" layoutY="300.0" text="MS1 to MS4" />
      <Label layoutX="508.0" layoutY="355.0" text="MS4 to MS3" />
      <Label layoutX="634.0" layoutY="269.0" text="MS1 to MS3" />
      <Label layoutX="635.0" layoutY="331.0" text="MS3 to MS2" />
      <Label layoutX="756.0" layoutY="250.0" text="MS3 to MS2" />
      <Label layoutX="756.0" layoutY="300.0" text="MS2 to MS4" />
      <Label layoutX="756.0" layoutY="355.0" text="MS4 to MS1" />
   </children>
</AnchorPane>

И, наконец, вот мой основной:

package com;

import com.kinesisdataconsumer.Consumer;
import com.kinesisdataproducer.Producer;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.io.IOException;
import java.util.ArrayList;

@SpringBootApplication
public class Main extends Application {
    public static Stage stage = new Stage();
    public static Parent root = new Parent() {
    };
    public static DataBase dataBase;

    /**
     * the main method of the program
     */
    public static void main(String args[]) {
        String legalFlowsFileName = "src/main/resources/legalFlows.json";
        String transactionFileName = "src/main/resources/transaction.json";

        //create the data base and add the tables
        dataBase = new DataBase();

        //run the spring boot application
        SpringApplication springApplication = new SpringApplication(Main.class);
        springApplication.run(args);

        //parse all the legal flows from 'legalFlows.json'
        LegalFlowsFileParser legalFlowsFileParser = new LegalFlowsFileParser(legalFlowsFileName);
        ArrayList<LegalFlow> legalFlows = legalFlowsFileParser.parseFile();
        dataBase.addAllLegalFlowsToDB(legalFlows);

        //parse all the transactions from 'transactions.json'
        TransactionsFileParser transactionsFileParser = new TransactionsFileParser(transactionFileName);
        ArrayList<Transaction> transactions = transactionsFileParser.parseFile();

        //Kinesis Producer
        Producer producer = new Producer(transactions);
        try {
            producer.produceData();
        } catch (Exception e) {
            e.printStackTrace();
        }

        //create the monitoring logic of the whole system
        MonitoringLogicImpl monitoringLogic = new MonitoringLogicImpl(dataBase, legalFlows);

        //Kinesis consumer
        Thread thread = new Thread(){
            public void run(){
                Consumer consumer = new Consumer(dataBase, transactions, monitoringLogic);
                consumer.consumeData();
            }
        };
        thread.start();

        Application.launch(Main.class, args);
    }

    public void start(Stage stage){
        this.stage = stage;
        FXMLLoader loader = new FXMLLoader();
        try {
            loader.setLocation(getClass().getResource("/UserInterface.fxml"));
            this.root = loader.load();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e){
            e.printStackTrace();
        }
        stage.setTitle("Troubleshooting project");
        stage.setScene(new Scene(root, 900, 700));
        stage.show();
    }

    public static void dropDBSchema(){
        dataBase.dropSchema();
    }
}

Вот класс логики мониторинга:

package com;

import com.userInterface.UIController;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;

import java.util.UUID;

public class MonitoringLogicImpl extends Observable implements MonitoringLogic {
    private DataBase database ;
    private ArrayList<LegalFlow> legalFlows;
    private final int MAX_DELAY = 20;
    private UIController uiController;
    List<Observer> observers = new ArrayList<Observer>();

    public MonitoringLogicImpl(DataBase database, ArrayList<LegalFlow> legalFlows){
        this.database = database;
        this.legalFlows = legalFlows;
        this.uiController = new UIController();
        attach(this.uiController);
    }

    public DATA_STATUS getStatus(Transaction transaction){
        LegalFlow legalFlow = getLegalFlowAccordingToLegalFlowUUID(transaction.getUuid());
        if(legalFlow.isTransactionExistInLegalFlow(transaction)){
            if(!isTransactionArrivedInDelay(transaction.getTimeSent(), transaction.getTimeReceived())){
                //first case : status COMPLETE - the transaction exist in the DB and it is part of a legal flow
                return DATA_STATUS.COMPLETE;
            } else {
                //third case : status DELAY - the time from the transaction sent until it's received is above the threshold value(MAX_DELAY)
                return DATA_STATUS.DELAY;
            }
        } else{
            //third case : status ERROR - the transaction is not exist in the DB
            return DATA_STATUS.ERROR;
        }
    }

    public void updateUI(UUID flowUUID, String sender, String receiver, DATA_STATUS status){
       // String key = flowUUID.toString()+"_"+sender+"_"+receiver;
        //this.uiController.changeCircleColor(key, status);
        notifyAllObservers();
    }

    public LegalFlow getLegalFlowAccordingToLegalFlowUUID(UUID legalFlowUUID){
        for (LegalFlow lf:this.legalFlows){
            if(lf.getUUID().toString().equals(legalFlowUUID.toString())){
                return lf;
            }
        }
        return new LegalFlow(UUID.fromString("00000000-0000-0000-0000-000000000000"),"ms0");
    }

    public boolean isTransactionArrivedInDelay(String timeSent, String timeReceived){
        DateTime dateTimeSent= null, dateTimeReceived = null;
        try {
            DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-mm-dd HH:mm:ss");
            dateTimeSent = formatter.parseDateTime(timeSent);
            dateTimeReceived = formatter.parseDateTime(timeReceived);
        }
        catch (Exception e){
            e.printStackTrace();
        }
        long seconds = dateTimeReceived.getMillis() - dateTimeSent.getMillis();
         if((seconds/1000) > MAX_DELAY){
             return true;
         }
         return false;
    }
    //last changes
    public void attach(Observer observer){
        observers.add(observer);
    }

    public void notifyAllObservers(){
        Object obj = null;
        for (Observer observer : observers) {
            observer.update(this,obj);
        }
    }

}

1 Ответ

0 голосов
/ 12 июня 2019

Итак, вы создаете добавленный экземпляр UIController, поэтому вы не получаете исключение NullPointerException, а также почему вы не обновляете фактическое UIController, используемое на экране.

Что вы, по сути, делаете:

- new MonitoringLogicImpl
  - new UIController
  - Add to Observers
- Start FX
  - FXML Loads new UIController

UIController, который вы отлаживаете, не тот экземпляр, который находится на экране. Я опубликовал в одном из сообщений ваших коллег, что вам нужно каким-то образом передать созданный FXML UIController в MonitoringLogicImpl:

- make `MonitoringLogicImpl monitoringLogic` a Main instance variable so that in `start` you can call something like `monitoringLogic.addListener(loader.getController())`

Однако это будет иметь ограничения, если вы начнете добавлять другие экраны FXML, которые необходимо обновить. Возможно, вы захотите сделать что-то более общее, когда ваше приложение является наблюдателем и распределяет сообщения в почтовый ящик, шину сообщений или что-то еще.

Что касается приложения, что произойдет, если кто-то закроет его? Нет способа повторно подключить графический интерфейс без перезапуска всей службы?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...