JavaFX FXML communication between Application and Controller classes - javafx-2

I am trying to get and destroy an external process I've created via ProcessBuilder in my FXML application close, but it's not working. This is based on the helpful advice Sergey Grinev gave me here.
I have tried running with/without the "// myController.setApp(this);" and with "// super.stop();" at top of subclass and at bottom (see commented out/in for that line in MyApp), but no combination works.
This probably isn't related to FXML or JavaFX, though I imagine this is a common pattern for developing apps on JavaFX. I suppose I'm asking for a Java best practice for closing dependent processes in a UI-based app like this one (in this case: FXML / JavaFX based), where there is a controller class and an application class.
Can you explain what I'm doing wrong? Or better: advise what I should be doing instead? Thanks.
In my Application I do this:
public class MyApp extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader fxmlLoader = new FXMLLoader();
Scene scene = (Scene)FXMLLoader.load(getClass().getResource("MyApp.fxml"));
MyAppController myController = (MyAppController)fxmlLoader.getController();
primaryStage.setScene(scene);
primaryStage.show();
// myController.setApp(this);
}
#Override
public void stop() throws Exception {
// super.stop();
// this is called on fx app close, you may call it in an action handler too
if (MyAppController.getScriptProcess() != null) {
MyAppController.getScriptProcess().destroy();
}
super.stop();
}
public static void main(String[] args) {
launch(args);
}
}
In my Controller I do this:
public class MyAppController implements Initializable {
private Application app;
private static Process scriptProcess;
public void setApp(Application a) {
app = a;
}
public static Process getScriptProcess() {
return scriptProcess;
}
}
The result when I run with the "commented-out setApp()" not commented out (that is, left in the start method), is the following, immediately upon launch (the main Scene flashes, then disappears, then this dialog appears:
"JavaFX Launcher Error:
Exception while running Application"
And it gives an, "Exception in Application start method" in the console as well.
The result when I leave out the "commented-out code" in my MyApp above (that is, remove the "setApp()" from the start method), is that my app does indeed close, but gives this error when it closes:
Exception in thread "JavaFX Application Thread" java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
at javafx.fxml.FXMLLoader$ControllerMethodEventHandler.handle(FXMLLoader.java:1440)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:69)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:217)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:170)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:38)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:37)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:92)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:35)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:92)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:35)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:92)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:53)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:28)
at javafx.event.Event.fireEvent(Event.java:171)
at javafx.scene.Node.fireEvent(Node.java:6863)
at javafx.scene.control.Button.fire(Button.java:179)
at com.sun.javafx.scene.control.behavior.ButtonBehavior.mouseReleased(ButtonBehavior.java:193)
at com.sun.javafx.scene.control.skin.SkinBase$4.handle(SkinBase.java:336)
at com.sun.javafx.scene.control.skin.SkinBase$4.handle(SkinBase.java:329)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:64)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:217)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:170)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:38)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:37)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:92)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:35)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:92)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:35)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:92)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:35)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:92)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:53)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:33)
at javafx.event.Event.fireEvent(Event.java:171)
at javafx.scene.Scene$MouseHandler.process(Scene.java:3324)
at javafx.scene.Scene$MouseHandler.process(Scene.java:3164)
at javafx.scene.Scene$MouseHandler.access$1900(Scene.java:3119)
at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1559)
at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2261)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:228)
at com.sun.glass.ui.View.handleMouseEvent(View.java:528)
at com.sun.glass.ui.View.notifyMouse(View.java:922)
at com.sun.glass.ui.gtk.GtkApplication._runLoop(Native Method)
at com.sun.glass.ui.gtk.GtkApplication$3$1.run(GtkApplication.java:82)
at java.lang.Thread.run(Thread.java:722)
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at javafx.fxml.FXMLLoader$ControllerMethodEventHandler.handle(FXMLLoader.java:1435)
... 44 more
Caused by: java.lang.NullPointerException
at mypackage.MyController.handleCancel(MyController.java:300)
... 49 more
Clean up...

The approach is right, but there are few problems.
You've created the FXMLLoader named fxmlLoader but then you call FXMLLoader.load() which is static method and is not connected with instance you've created before.
Also using static method to intercommunication is not very good (imagine you'll want to have several processes). Better store myController to a field and call it in the stop() method.
Even better would be to add corresponding utility method to Controller and call it from main app, because main app doesn't seem to use Process itself.
Here goes short app to demonstrate all described:
public class DoTextAreaLog extends Application {
private LoggController controller;
#Override
public void start(Stage stage) throws IOException{
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("logg.fxml"));
VBox root = (VBox)fxmlLoader.load();
controller = (LoggController) fxmlLoader.getController();
stage.setScene(new Scene(root, 400, 300));
stage.show();
}
#Override
public void stop() throws Exception {
super.stop();
controller.destroy();
}
public static void main(String[] args) { launch(); }
}
Controller:
public class LoggController implements Initializable {
#FXML private TextArea textarea;
#FXML private void onAction(ActionEvent event) {
destroy();
}
private Process p;
public void destroy() {
if (p != null) {
p.destroy();
}
}
#Override
public void initialize(URL url, ResourceBundle rb) {
try {
p = new ProcessBuilder("ping", "stackoverflow.com", "-n", "100").start();
new Thread(new Runnable() {
#Override
public void run() {
try {
try (BufferedReader bri = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
String line;
while ((line = bri.readLine()) != null) {
log(line);
}
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}).start();
} catch (IOException ex) {
ex.printStackTrace();
}
}
private void log(final String st) {
Platform.runLater(new Runnable() {
#Override
public void run() {
textarea.setText(st + "\n" + textarea.getText());
}
});
}
}
logg.fxml:
<VBox id="root" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml" fx:controller="fxml.LoggController">
<TextArea fx:id="textarea"/>
<Button text="Stop The Madness!" onAction="#onAction"/>
</VBox>

Related

java.lang.IllegalStateException: Not on FX application thread Calling Function

Something strange is happening.. Untill 10 minutes ago I had no problem with this code. But now I have a problem updating JUST my VBOX from an external thread.
These are my three classes:
Controller Class:
public class Controller implements Initializable{
#FXML
private VBox slaveVbox;
private ButtonBar newNode = new ButtonBar();
private Circle c= new Circle();
private Button b= new Button();
private Label lname = new Label();
private Label lIMEI = new Label();
private Label lroot = new Label();
#Override
public void initialize(URL location, ResourceBundle resources) {
}
public void create(String imei, String permission,boolean isOnline) throws IOException{
if(!alreadyExist(imei)){
newNode = new ButtonBar();
b = setButtonSpec(imei + "btnHavefun");
c = setCircleSpec(imei + "statuOnline", isOnline);
lname= setLNameSpec(imei + "name");
lIMEI = setLIMEISpec(imei + "Imei");
lroot = setLrootSpec(imei + "root", permission);
newNode.getButtons().addAll(lname,lIMEI,lroot,b,c);
slaveVbox.getChildren().addAll(newNode);
}
}
}
Main Class:
public class MainApp extends Application {
FXMLLoader loader2;
private Stage primaryStage;
private BorderPane rootLayout;
#Override
public void start(Stage primaryStage) throws IOException {
this.primaryStage = primaryStage;
this.primaryStage.setTitle("Thypheon Application");
initRootLayout();
Controller controller2 = initDesign();
Connection con = new Connection(controller2);
Thread t = new Thread(con);
t.start();
primaryStage.setOnCloseRequest(new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent e) {
Platform.exit();
System.exit(0);
}
});
}
public static void main(String[] args) {
launch(args);
}
public void initRootLayout(){
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("RootLayout.fxml"));
rootLayout = (BorderPane) loader.load();
Scene scene = new Scene(rootLayout);
primaryStage.setScene(scene);
primaryStage.show();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public Controller initDesign(){
try {
FXMLLoader loader2= new FXMLLoader(getClass().getResource("Design.fxml"));
AnchorPane anchor = (AnchorPane) loader2.load();
rootLayout.setCenter(anchor);
Controller controller = loader2.getController();
return controller;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
public Stage getPrimaryStage(){
return primaryStage;
}
}
Connection THREAD:
public class Connection implements Runnable {
String result;
Controller controller;
public Connection(Controller controller) {
this.controller = controller;
}
#Override
public void run() {
try {
controller.create("jhgjhgjh", "dssf", true);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Debugging the Application Everything works perfectly untill I reach slaveVbox.getChildren().addAll(newNode); Here comes the exception..
After some attempt to solve the problem I figured out that if I create a ButtonBar and I insert it in the slaveVbox from Main (inside start()) it works fine.. So I ve tied to add controller2.create("FIRST", "FIRST", true); in my start() function like this:
#Override
public void start(Stage primaryStage) throws IOException {
this.primaryStage = primaryStage;
this.primaryStage.setTitle("Thypheon Application");
initRootLayout();
Controller controller2 = initDesign();
controller2.create("FIRST", "FIRST", true);
Connection con = new Connection(controller2);
Thread t = new Thread(con);
t.start();
primaryStage.setOnCloseRequest(new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent e) {
Platform.exit();
System.exit(0);
}
});
}
But obviously my application shows two ButtonBars... One created in the start() function and one created inside the Connection Thread.. How Can I avoid this?? Why I can't directly add item inside my VBox directly from my Connecton thread??
You cannot update the UI from a thread other than the FX Application Thread. See, for example, the "Threading" section in the Application documentation.
It's not at all clear why you are using a background thread at all here: there doesn't seem to be any long-running code in the method you are calling. In general, if you have long-running code to call, you can call it in a background thread and then update the UI by wrapping UI update in a Platform.runLater(...).
public class Connection implements Runnable {
String result;
Controller controller;
public Connection(Controller controller) {
this.controller = controller;
}
#Override
public void run() {
try {
// execute long-running code here...
// perform any updates to the UI on the FX Application Thread:
Platform.runLater(() -> {
// code that updates UI
});
// more long-running code can go here...
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

JavaFX: How to make an looping thread without freezing application?

How do I go about making a thread within JavaFX? I've looked around and no answers are clear/want I need, which is basicly the same as Java's Thread Runnable, which is not conpatible with JavaFx unless it's for a background task.
The basic start application class I have:
public class Main extends Application {
private Stage stage;
private AnchorPane rootLayout;
#Override
public void start(Stage stage) {
this.stage = stage;
this.stage.setTitle("Main");
setLayout();
}
private void setLayout() {
try {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(Main.class.getResource("view/View.fxml"));
rootLayout = (AnchorPane) loader.load();
Scene scene = new Scene(rootLayout);
stage.setScene(scene);
stage.show();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
I need a JavaFX Thread that'll keep changing the visibility of a button without freezing/pausing the application while it's running, how do I go about doing this?
Edit: I need this but for a JavaFX app:
public void run(){
while (true){
if (button.isVisible)
button.setVisibility(false);
else
button.setVisibility(true);
Thread.sleep(1000);
}
}

Exception in thread "JavaFX Application Thread" java.lang.NullPointerException

I'm writing a program on the desktop that takes an array of COM port and displays it on the chart dynamically. the program runs without failures for a while, but then fails with the following exception:
Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
at com.sun.scenario.animation.AbstractMasterTimer.timePulseImpl(AbstractMasterTimer.java:344)
at com.sun.scenario.animation.AbstractMasterTimer$MainLoop.run(AbstractMasterTimer.java:267)
at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:521)
at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:505)
at com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$399(QuantumToolkit.java:334)
at com.sun.javafx.tk.quantum.QuantumToolkit$$Lambda$40/432581434.run(Unknown Source)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$144(WinApplication.java:101)
at com.sun.glass.ui.win.WinApplication$$Lambda$36/1963387170.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
the application continues to run. continue to saved data to a text file, but the window is not active any button except the close button.
if anyone has any ideas please help.
here is some code
public class FXMLController implements Initializable {
public static XYChart.Series<Number, Number> hourDataSeries;
#FXML
public Label label;
#FXML
public Label statusbar;
#FXML
public LineChart lineChart;
#FXML
public NumberAxis xAxis;
#FXML
NumberAxis yAxis;
#FXML
MenuItem close;
#FXML
MenuItem Options;
#FXML
MenuItem about;
#FXML
Button connect;
#FXML
public Label hexString;
#FXML
private void close(ActionEvent event) throws SerialPortException {
//COMConnect.serialPort.closePort();
System.exit(0);
}
#FXML
private void connect(ActionEvent event) {
OpenScene openScene = new OpenScene("/SceneCOM.fxml", "Options");
}
#FXML
private void showStatistics(ActionEvent event) {
OpenScene openScene = new OpenScene("/SceneStatistics.fxml", "Statistics");
}
#FXML
private void aboutit(ActionEvent event) {
OpenScene openScene = new OpenScene("/SceneAbout.fxml", "About");
}
#FXML
private void getconnect(ActionEvent event) {
if (COMConnect.b == true) {
COMConnect.b = false;
try {
COMConnect.serialPort.closePort();
} catch (SerialPortException ex) {
Logger.getLogger(FXMLController.class.getName()).log(Level.SEVERE, null, ex);
}
connect.setText("Connect");
} else {
COMConnect.b = true;
COMConnect.comConnect();
}
}
#Override
public void initialize(URL url, ResourceBundle rb) {
hourDataSeries = new XYChart.Series<Number, Number>();
label.textProperty().bind(COMConnect.EventListener.valueProperty);
COMConnect.connectProperty.setValue("Connect");
connect.textProperty().bindBidirectional(COMConnect.connectProperty);
statusbar.textProperty().bind(Typewriter.status);
hexString.textProperty().bind(TypewriterHexString.hexStr);
xAxis.setLabel("Time");
xAxis.setTickUnit(1);
xAxis.setTickLabelFormatter(new NSC());
yAxis.setLabel("density," + superscript(" kg/m3"));
xAxis.setForceZeroInRange(false);
lineChart.setLegendVisible(false);
lineChart.setCursor(Cursor.CROSSHAIR);
lineChart.getData().add(hourDataSeries);
}
}
and add data to LineChart from other class
javafx.application.Platform.runLater(new Runnable() {
#Override
public void run() {
valueProperty.setValue(formatt(second) + superscript(" kg/m3"));
if (y > 10) {
hourDataSeries.getData().remove(0);
}
data = new XYChart.Data<Number, Number>(y, second);
data.setNode(new HoveredThresholdNode(0, second, ""));
hourDataSeries.getData().add(data);
System.out.println(hourDataSeries.getData().toString());
y++;
}
});
}
Thus, the solution was very simple:
lineChart.setAnimated(false);
the application works correctly for about 25 hours.
Thanks everyone!

How to open dialog box in android as soon as game ends from surfaceview class

I have made a SurfaceView subclass in MainActivity which runs some animation/game/thread via canvas draw and I want a dialog box to appear as soon as game ends. I have made a function called openNewGameDialog where I call new AlertDialog.Builder(MainActivity.this) however since I am calling this from a thread it gives error. Please help!! Following is the code:
public class MainActivity extends Activity implements OnTouchListener{
GameSurface dSurface;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dSurface=new GameSurface(this);
dSurface.setOnTouchListener(this);
initialize();
setContentView(dSurface);
}
private void initialize(){
}
#Override
protected void onPause() {
// TODO Auto-generated method stub
super.onPause();
dSurface.pause();
}
#Override
protected void onResume() {
// TODO Auto-generated method stub
super.onResume();
dSurface.resume();
}
private void openNewGameDialog(){
new AlertDialog.Builder(MainActivity.this)
.setTitle("hhhhhh")
.setItems(R.array.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// TODO Auto-generated method stub
}
})
.show();
}
public class GameSurface extends SurfaceView implements Runnable{
SurfaceHolder gameHolder;
Thread gameThread = null;
public GameSurface(Context context) {
super(context);
// TODO Auto-generated constructor stub
gameHolder = getHolder();
}
public void pause(){
isRunning = false;
while(true)
{
try {
gameThread.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
break;
}
gameThread=null;
}
public void resume(){
isRunning = true;
gameThread = new Thread(this);
gameThread.start();
}
#Override
public void run() {
// TODO Auto-generated method stub
while(isRunning){
if(!gameHolder.getSurface().isValid())
continue;
Canvas canvas = gameHolder.lockCanvas();
canvas.draw something
if game==ends
openNewGameDialog();
gameHolder.unlockCanvasAndPost(canvas);
}
}
}
}
You must put the contents of openNewGameDialog() in a Runnable instead of a method and then call the runOnUiThread(Runnable r) method of the Activity class using that Runnable.
Any kind of UI manipulation can only be done by the UI thread. TherunOnUiThread(Runnable r) method queues the Runnable to the UI thread event queue.

NPE in initialize

After reading introduction_to_fxml I got an impression that an initialize method can be used as spring's afterPropertiesSet or EJB's a #PostConstruct method - that is expected all member variables set when it is invoked. But when I tried I got NPE. The code I tried looks like following.
Main app:
public class MyApp extends Application {
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("/myapp.fxml"));///MAIN LOAD
Scene scene = new Scene(root, 320, 240);
scene.getStylesheets().add("/myapp.css");
stage.setScene(scene);
stage.setTitle("my app");
stage.show();
}
public static void main(String[] args) { launch(); }
}
myapp.fxml:
...
<VBox fx:id="root" xmlns:fx="http://javafx.com/fxml" >
<ControlA>
<SomeClass>
</SomeClass>
</ControlA>
</VBox>
ControlA.java:
#DefaultProperty("aproperty")
public class ControlA extends StackPane {
private SomeClass aproperty;
public ContentPane(){
try {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/controls/ControlA.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
fxmlLoader.load();//ControlA LOAD
} catch (IOException exception) {
throw new RuntimeException(exception);
}
}
public void initialize() {
//aproperty is null here, called from ControlA LOAD
}
//aproperty get/set
public void setAproperty(SomeClass p){//it is called from MAIN LOAD
....
}
The component's initialize method is called from its load method and its property is being set from parent's load method which is called later. And it looks understandable, a component's property values can't be constructed until parent fxml is read. But if so, what is best practice to init a component before it will be used and after all the props were initialized?
Best regards, Eugene.
You need to implement the Initializable interface in a controller. I was under the impression that this was for controllers only.

Resources