Я пытаюсь создать приложение с графическим интерфейсом в 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);
}
}
}