I am unable to change the text of my label from other class.
I need to constantly update date and time on the main screen, while I may be able to perform other functions too, simultaneously.
I have used a TimeSetting class, which extends Thread and in it's run() method, I've called the updation command in an infinite loop, using setText() and then slept the method for a second.
But on running this, nothing happens and on closing the output window, I get an error saying NullPointerExcpetion
Here's the code for the two classes :
FXMLDocumentController.java
package crt;
import java.io.IOException;
import java.net.URL;
import java.util.Date;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class FXMLDocumentController extends Thread implements Initializable
{
#FXML
protected Label check;
#FXML
**protected Label date;**
#FXML
protected Label time;
#FXML
protected Label RRRR;
#FXML
protected Label DDDD;
#FXML
protected Label SSSS;
#FXML
protected Label temp;
#FXML
protected Label maxtemp;
#FXML
protected Label mintemp;
#FXML
private void handleButtonAction(ActionEvent event) throws IOException {
//dc.setDate(date.textProperty().bind(valueproperty));
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("menu.fxml"));
Parent root1 = (Parent) fxmlLoader.load();
Stage stage = new Stage();
stage.initModality(Modality.APPLICATION_MODAL);
stage.initStyle(StageStyle.UNDECORATED);
stage.setTitle("MENU");
stage.setScene(new Scene(root1));
stage.show();
}
#Override
public void initialize(URL url, ResourceBundle rb) {
}
FXMLDocument.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ProgressBar?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Text?>
<AnchorPane id="AnchorPane" prefHeight="367.0" prefWidth="510.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="crt.FXMLDocumentController">
<children>
<Button fx:id="button" layoutX="387.0" layoutY="302.0" minHeight="25.0" minWidth="80.0" onAction="#handleButtonAction" onTouchPressed="#handleButtonAction" text="Menu" />
<Label fx:id="date" layoutX="56.0" layoutY="64.0" minHeight="25.0" minWidth="80.0" />
<Label fx:id="time" layoutX="361.0" layoutY="64.0" minHeight="25.0" minWidth="80.0" text="S" />
<Label fx:id="RRRR" layoutX="76.0" layoutY="100.0" minHeight="25.0" minWidth="70.0" />
<Label fx:id="DDDD" layoutX="195.0" layoutY="100.0" minHeight="25.0" minWidth="70.0" />
<Label fx:id="SSSS" layoutX="314.0" layoutY="100.0" minHeight="25.0" minWidth="70.0" />
<Text layoutX="136.0" layoutY="163.0" strokeType="OUTSIDE" strokeWidth="0.0" text="TEMP :-" />
<Label fx:id="temp" layoutX="275.0" layoutY="156.0" minHeight="25.0" minWidth="70.0" text="A" />
<Text layoutX="136.0" layoutY="203.0" strokeType="OUTSIDE" strokeWidth="0.0" text="MAX TEMP :-" />
<Label fx:id="maxtemp" layoutX="275.0" layoutY="188.0" minHeight="25.0" minWidth="70.0" text="B" />
<Text layoutX="136.0" layoutY="243.0" strokeType="OUTSIDE" strokeWidth="0.0" text="MIN TEMP :-" />
<Label fx:id="maxtemp" layoutX="275.0" layoutY="225.0" minHeight="25.0" minWidth="70.0" text="C" />
<ProgressBar layoutX="401.0" layoutY="21.0" prefHeight="18.0" prefWidth="70.0" progress="0.0" />
<Button fx:id="startbutton" layoutX="14.0" layoutY="18.0" mnemonicParsing="false" onAction="#startstart" text="START" />
<Label fx:id="check" layoutX="42.0" layoutY="306.0" />
</children>
</AnchorPane>
TimeSetting.java
package crt;
import java.util.Date;
public class TimeSetting extends Thread {
#Override
public void run()
{
FXMLDocumentController fdc = new FXMLDocumentController();
// fdc.load();
int i=0;
while(true)
{
Date d = new Date();
fdc.date.setText("fd" + i);
i++;
try
{
Thread.sleep(1000);
}
catch(InterruptedException e)
{
}
}
}
}
CRT.java
package crt;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class CRT extends Application {
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) throws InterruptedException {
launch(args);
TimeSetting ts = new TimeSetting();
ts.start();
}
}
launch() does not complete until the Application exits. You should start the thread in the start method of your application.
Furthermore new FXMLDocumentController() obviously creates a new instance of the controller class - one that isn't connected to any fxml, so none of the fields is injected. More info about communicating with a controller can be found here: Passing Parameters JavaFX FXML
Also if you get this to work, you're still using a thread different to the JavaFX application thread to modify the UI. This should not be done. Instead use Platform.runLater to update the UI:
while(true) {
Date d = new Date();
final String text = "fd" + i;
Platform.runLater(() -> {
fdc.date.setText(text);
});
i++;
try {
Thread.sleep(1000);
} catch(InterruptedException e) {
}
}
Related
I'm creating a simple login GUI form using eclipse, JavaFX and scene builder. I've coded my program so that every time I click the login button, it would switch to another window but it doesn't work. i'm a beginner in java but we've been tasked to create a system in my class so I've only relied on youtube tutorials, any help would be appreciated!
this is my main code
package application;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
Parent root = FXMLLoader.load(getClass().getResource("Main.fxml"));
Scene scene = new Scene(root);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
my login form
package application;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
public class Login {
#FXML
private Button button;
#FXML
private PasswordField password;
#FXML
private TextField username;
#FXML
private Label wrongLogin;
#FXML
public void Login(ActionEvent event) {
Stage primaryStage = new Stage();
if (username.getText().equals("admin") && password.getText().equals("admin")) {
try {
Parent root = FXMLLoader.load(getClass().getResource("BookRooms.fxml"));
Scene scene = new Scene(root);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
}
}
my fxml file
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane prefHeight="345.0" prefWidth="545.0" style="-fx-background-color: #fff2cc;" xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.Login">
<children>
<AnchorPane layoutX="132.0" layoutY="62.0" prefHeight="221.0" prefWidth="279.0" style="-fx-background-color: #545454; -fx-border-radius: 10;">
<children>
<Label layoutX="34.0" layoutY="60.0" prefHeight="25.0" prefWidth="56.0" text="Username:" textFill="WHITE" />
<Label layoutX="34.0" layoutY="98.0" prefHeight="25.0" prefWidth="56.0" text="Password:" textFill="WHITE" />
<TextField layoutX="106.0" layoutY="60.0" promptText="Enter Username ID:" />
<TextField layoutX="106.0" layoutY="98.0" promptText="Enter Password" />
<Button alignment="CENTER" layoutX="106.0" layoutY="137.0" mnemonicParsing="false" prefHeight="25.0" prefWidth="73.0" style="-fx-background-color: #fff2cc;" text="LOGIN" />
</children></AnchorPane>
<Label layoutX="225.0" layoutY="36.0" text="HOTEL DEL LUNA" />
</children>
</AnchorPane>
Your button in fxml can't call Login() in its controller class because there is no onAction attribute in fxml button tag .
So ,in <Button alignment="CENTER" layoutX="106.0" layoutY="137.0" mnemonicParsing="false" prefHeight="25.0" prefWidth="73.0" style="-fx-background-color: #fff2cc;" text="LOGIN" /> add onAction="#Login" inside Button Tag:
<Button alignment="CENTER" layoutX="106.0" layoutY="137.0" mnemonicParsing="false" onAction="#Login" prefHeight="25.0" prefWidth="73.0" style="-fx-background-color: #fff2cc;" text="LOGIN" />
And , naming a method in a class with exact the same name of that class is concidered a bad practice. Only constructors must match its name with class identifier
I currently have an application that utilizes multiple FXML controllers. In my root controller (CustomController.java) I have loaded an FXML file that calls MainMenuController. I have another controller/fxml-file that is another "view" of the application.
My question is: What would be the best approach to changing the current "view"? My current method is calling the CustomController.swapOut() when a button is clicked in the mainmenu.fxml. But this is causing an error, as it is creating a MainMenuController object and gets stuck in an infinite loop until memory runs out.
Note: I am currently trying to clear the viewableContent pane first, before attempting to load in a new "view"
Application structure => tester.java -> CustomController.java
|
V
customController.fxml -> MainMenuController.java
|
V
mainmenu.fxml
tester.java
package task01;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class tester extends Application
{
#Override
public void start(Stage stage) throws Exception
{
CustomController customController = new CustomController();
customController.getStylesheets().add("/task01/stylize.css");
stage.setScene(new Scene(customController,1920,1080));
stage.setTitle("Seneca ATM Program");
stage.setWidth(1920);
stage.setHeight(1080);
stage.show();
}
public static void main(String[] args)
{
Application.launch(args);
}
}
CustomController.java
package task01;
import javafx.event.ActionEvent;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.fxml.*;
import javafx.scene.layout.Pane;
import java.io.IOException;
public class CustomController extends GridPane
{
#FXML
private Pane viewableContent;
public CustomController()
{
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("customController.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try
{
fxmlLoader.load();
} catch (IOException exception)
{
throw new RuntimeException(exception);
}
}
public void swapOut()
{
viewableContent.getChildren().clear();
}
}
customController.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.Pane?>
<?import task01.MainMenuController?>
<fx:root type="javafx.scene.layout.GridPane" xmlns:fx="http://javafx.com/fxml" alignment="CENTER">
<ImageView fitWidth="229.67" fitHeight="149.67" GridPane.columnIndex="0" GridPane.rowIndex="0" GridPane.halignment="CENTER">
<Image url="/task01/logo.png"/>
</ImageView>
<Pane fx:id="viewableContent" GridPane.columnIndex="0" GridPane.rowIndex="1" GridPane.halignment="CENTER">
<MainMenuController/>
</Pane>
</fx:root>
MainMenuController.java
package task01;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import java.io.IOException;
public class MainMenuController extends GridPane
{
private CustomController customController = new CustomController();
#FXML
private VBox buttonSet;
#FXML
private HBox buttonSetOne;
#FXML
private HBox buttonSetTwo;
#FXML
private Button changePinButton;
#FXML
private Button accountInquiryButton;
#FXML
private Button withdrawMoneyButton;
#FXML
private Button depositMoneyButton;
#FXML
private Button balanceInquiryButton;
#FXML
private Button createAccountButton;
#FXML
private GridPane gridpane;
#FXML
public void initialize()
{
createAccountButton.setOnAction(event ->
{
customController.swapOut();
});
}
public MainMenuController()
{
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("mainmenu.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try
{
fxmlLoader.load();
} catch (IOException exception)
{
throw new RuntimeException(exception);
}
}
}
mainmenu.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.geometry.Insets?>
<fx:root type="javafx.scene.layout.GridPane" alignment="CENTER" vgap="50" hgap="50" xmlns:fx="http://javafx.com/fxml">
<padding><Insets top="10" bottom="10" left="10" right="10"/></padding>
<VBox fx:id="buttonSet" spacing="25" GridPane.columnIndex="0" GridPane.rowIndex="1">
<HBox fx:id="buttonSetOne" spacing="25">
<Button styleClass="menuButton" fx:id="createAccountButton">Create account</Button>
<Button styleClass="menuButton" fx:id="changePinButton">Change PIN</Button>
<Button styleClass="menuButton" fx:id="accountInquiryButton">Account Inquiry</Button>
</HBox>
<HBox fx:id="buttonSetTwo" spacing="25">
<Button styleClass="menuButton" fx:id="withdrawMoneyButton">Withdraw Money</Button>
<Button styleClass="menuButton" fx:id="depositMoneyButton">Deposit Money</Button>
<Button styleClass="menuButton" fx:id="balanceInquiryButton">Balance Inquiry</Button>
</HBox>
</VBox>
</fx:root>
Basically you need to pass the "parent" controller to the child controller instead of creating a instance in the child controller. (The instance you initialize your field with would be different to the parent instance anyways which would result in non-working code, even if you prevent the stackoverflow somehow.)
In this case you could use the FXMLLoader to pass the value like this:
<fx:root fx:id="rootController" type="javafx.scene.layout.GridPane" xmlns:fx="http://javafx.com/fxml" alignment="CENTER">
<ImageView fitWidth="229.67" fitHeight="149.67" GridPane.columnIndex="0" GridPane.rowIndex="0" GridPane.halignment="CENTER">
<Image url="/task01/logo.png"/>
</ImageView>
<Pane fx:id="viewableContent" GridPane.columnIndex="0" GridPane.rowIndex="1" GridPane.halignment="CENTER">
<MainMenuController customController="$rootController"/> <!-- pass object to setter using fx:id -->
</Pane>
</fx:root>
MainMenuController
private CustomController customController; //= new CustomController();
public void setCustomController(CustomController customController) {
this.customController = customController;
}
// required for fxmlloader to identify the type
public CustomController getCustomController() {
return customController;
}
I have a TableView with a single Column and I want to have a Button in the last row of that TableView/Column. The Button should be disabled until a row is selected and then it should be enabled. I've done numerous searches on this but everything I've found seems to be adding a button to every row within a multi-column table. Is there a quick way to do this?
Here are some sample images:
TableButtonView.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<AnchorPane maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="406.0" prefWidth="200.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="tablebutton.TableButtonController">
<children>
<VBox layoutX="60.0" layoutY="22.0" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="200.0" prefWidth="100.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Label alignment="CENTER" maxWidth="1.7976931348623157E308" prefHeight="51.0" prefWidth="200.0" text="Agency Customization">
<font>
<Font name="System Bold" size="13.0" />
</font>
</Label>
<HBox maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="40.0">
<children>
<Label alignment="CENTER_RIGHT" maxHeight="1.7976931348623157E308" prefHeight="30.0" prefWidth="70.0" text="Agency: " />
<TextField fx:id="agencyTextField" maxHeight="1.7976931348623157E308" prefHeight="30.0" prefWidth="45.0" />
<Button fx:id="addButton" maxHeight="1.7976931348623157E308" onAction="#onAdd" prefHeight="30.0" prefWidth="67.0" text="_Add">
<HBox.margin>
<Insets left="10.0" />
</HBox.margin>
</Button>
</children>
<padding>
<Insets bottom="10.0" />
</padding>
</HBox>
<TableView fx:id="agencyTableView" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="276.0">
<columns>
<TableColumn fx:id="agencyColumn" prefWidth="188.0" text="Agency" />
</columns>
<VBox.margin>
<Insets left="5.0" right="5.0" />
</VBox.margin>
</TableView>
<ButtonBar maxWidth="1.7976931348623157E308">
<buttons>
<Button fx:id="okAgencyButton" mnemonicParsing="false" onAction="#onOK" text="OK" />
<Button fx:id="cancelAgencyButton" mnemonicParsing="false" onAction="#onCancel" text="Cancel" />
</buttons>
<padding>
<Insets bottom="5.0" right="20.0" top="5.0" />
</padding>
</ButtonBar>
</children>
</VBox>
</children>
</AnchorPane>
TableButtonController.java:
package tablebutton;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class TableButtonController implements Initializable {
private WebView helpWebView;
#FXML
private TextField agencyTextField;
#FXML
private Button addButton;
#FXML
private TableView<String> agencyTableView;
#FXML
private TableColumn<String, String> agencyColumn;
#FXML
private Button okAgencyButton;
#FXML
private Button cancelAgencyButton;
#Override
public void initialize(URL url, ResourceBundle rb) {
agencyColumn.setCellValueFactory(cellData ->
new ReadOnlyStringWrapper(cellData.getValue())
);
agencyColumn.setStyle( "-fx-alignment: CENTER;");
ObservableList<String> agencies = FXCollections.observableArrayList(
"AA","DL","LH");
agencyTableView.getItems().addAll(agencies);
}
#FXML
private void onAdd(ActionEvent event) {
}
#FXML
private void onOK(ActionEvent event) {
// Just exit for now
Stage stage = (Stage) okAgencyButton.getScene().getWindow();
stage.close();
}
#FXML
private void onCancel(ActionEvent event) {
// Just exit for now
Stage stage = (Stage) cancelAgencyButton.getScene().getWindow();
stage.close();
}
}
TableButton.java:
package tablebutton;
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class TableButton extends Application {
#Override
public void start(Stage primaryStage) {
try
{
FXMLLoader loader = new FXMLLoader(getClass().getResource("TableButtonView.fxml"));
Scene scene = new Scene((Parent) loader.load());
primaryStage.setScene(scene);
primaryStage.setTitle("TableView Button Test");
primaryStage.show();
}
catch (IOException ignored)
{
}
}
public static void main(String[] args) {
launch(args);
}
}
Trying to put the button actually inside the TableView is problematic and not recommended. There is a technical difficulty in getting the button to render in the last row of the TableView (this is not trivial), and there are also potential usability issues (what if there are a lot of rows in the table and the table needs to be scrolled, does the remove button just scroll out of view? if so, then how would a user find it if they wanted to remove something).
Instead, don't put the button in the TableView:
Use a VBox which contains the the TableView and the Button.
Set appropriate constraints so that the Button butts right up under the TableView and is sized to the width of the TableView.
Bind the disable property of the Button to an empty selected items set.
Sample code:
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class Remover extends Application {
#Override
public void start(Stage stage) throws Exception {
ObservableList<NamedColor> colors = FXCollections.observableArrayList(
new NamedColor("red", Color.RED),
new NamedColor("green", Color.GREEN),
new NamedColor("blue", Color.BLUE),
new NamedColor("indigo", Color.INDIGO)
);
TableView<NamedColor> table = new TableView<>(colors);
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
TableColumn<NamedColor, String> colorNames = new TableColumn<>("Colors");
colorNames.setCellValueFactory(new PropertyValueFactory<>("name"));
table.getColumns().add(colorNames);
Button remove = new Button("Remove");
remove.disableProperty().bind(
Bindings.isEmpty(
table.getSelectionModel().getSelectedItems()
)
);
remove.setMaxWidth(Double.MAX_VALUE);
remove.setOnAction(event ->
table.getItems()
.removeAll(
table.getSelectionModel().getSelectedItems()
)
);
VBox layout = new VBox(table, remove);
layout.setPadding(new Insets(10));
stage.setScene(new Scene(layout));
stage.show();
}
public static void main(String[] args) {
launch(Remover.class);
}
public static class NamedColor {
private String name;
private Color color;
public NamedColor(String name, Color color) {
this.name = name;
this.color = color;
}
public String getName() {
return name;
}
public Color getColor() {
return color;
}
}
}
This is what my view looks like:
Explanation
The entire window itself runs on one controller, called the CartWindowController, and the product list itself is a JavaFX Custom Control called CartItemComponent, which has it's own controller. Each item in the list, therefore has it's own instance of the controller as I programmatically make an instance and populate a VBox.
Problem
I cannot figure out how to notify the CartWindowController when the user clicks on the "X" button, which is handled by the "CartItemComponent" Controller. I would highly appreciate it if anyone could give me a heads up on how to tackle this problem.
Here's what my FXML looks like for the entire Window:
CartWindow.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.VBox?>
<AnchorPane id="AnchorPane" fx:id="parent" prefHeight="400.0" prefWidth="600.0" styleClass="pane" stylesheets="#../assets/userwindow.css" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ordermanagementsystem.controllers.CartWindowController">
<children>
<Button layoutX="25.0" layoutY="25.0" mnemonicParsing="false" onAction="#exitWindow" prefHeight="35.0" prefWidth="20.0" styleClass="back-button" />
<Label layoutX="262.0" layoutY="17.0" styleClass="heading" text="Cart" />
<ScrollPane hbarPolicy="NEVER" layoutY="97.0" prefHeight="315.0" prefWidth="600.0" styleClass="no-padding">
<content>
<AnchorPane prefWidth="600.0" styleClass="no-padding">
<children>
<VBox fx:id="productList" layoutX="25.0" prefWidth="350.0" />
<AnchorPane layoutX="425.0" layoutY="25.0" prefWidth="150.0" styleClass="no-padding">
<children>
<Label layoutX="18.0" styleClass="heading-sub" text="Summary" />
<Label layoutX="1.0" layoutY="55.0" text="Gross:" />
<Label fx:id="grossTotal" alignment="CENTER_RIGHT" layoutX="47.0" layoutY="55.0" prefHeight="17.0" prefWidth="103.0" text="RM0.00" />
<Label layoutX="1.0" layoutY="75.0" text="Packaging:" />
<Label fx:id="packagingTotal" alignment="CENTER_RIGHT" layoutX="73.0" layoutY="75.0" prefHeight="17.0" prefWidth="77.0" text="RM0.00" />
<Label layoutX="1.0" layoutY="95.0" text="Total:" />
<Label fx:id="total" alignment="CENTER_RIGHT" layoutX="40.0" layoutY="95.0" prefHeight="17.0" prefWidth="110.0" styleClass="green-text" text="RM0.00" />
<Button layoutY="125.0" mnemonicParsing="false" onAction="#checkout" prefHeight="39.0" prefWidth="150.0" styleClass="action-button" text="Check Out" />
</children>
</AnchorPane>
</children>
</AnchorPane>
</content>
</ScrollPane>
</children>
</AnchorPane>
CartWindowController.java
package ordermanagementsystem.controllers;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Parent;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import ordermanagementsystem.cart.CartState;
import ordermanagementsystem.orders.models.OrderItem;
import ordermanagementsystem.viewcomponents.CartItemComponent;
public class CartWindowController extends ViewController implements Initializable {
#FXML
public Parent parent;
#FXML
private VBox productList;
#FXML
private Label grossTotal;
#FXML
private Label packagingTotal;
#FXML
private Label total;
private CartState cartState;
#FXML
private void exitWindow(ActionEvent event) {
try {
this.openPage(this.parent, "MainWindow.fxml");
} catch (IOException ex) {
ex.printStackTrace();
}
}
#FXML
private void checkout(ActionEvent event) {
}
#Override
public void initialize(URL url, ResourceBundle rb) {
this.cartState = CartState.getInstance();
for (OrderItem item : this.cartState.getItems()) {
this.productList.getChildren().add(new CartItemComponent(item));
}
}
}
CartItemComponent.java:
package ordermanagementsystem.viewcomponents;
import java.io.IOException;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import ordermanagementsystem.DialogBox;
import ordermanagementsystem.cart.CartState;
import ordermanagementsystem.orders.models.OrderItem;
public class CartItemComponent extends AnchorPane {
#FXML
private AnchorPane frame;
#FXML
private TextField quantity;
#FXML
private Label total;
#FXML
private Label productName;
private OrderItem item;
private CartState cartState;
public CartItemComponent(OrderItem item) {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/ordermanagementsystem/views/components/CartItemComponent.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try {
fxmlLoader.load();
} catch (IOException exception) {
exception.getStackTrace();
}
this.cartState = CartState.getInstance();
this.item = item;
this.setQuantity(1);
this.quantity.setEditable(false);
this.productName.setText(this.item.getProduct().getName());
}
private void setQuantity(int quantity) {
this.quantity.setText(String.valueOf(quantity));
this.item.setQuantity(quantity);
this.cartState.updateItem(this.item);
this.total.setText("RM" + String.format("%.2f", item.getProduct().getPrice() * quantity));
}
private int getQuantity() {
return Integer.parseInt(this.quantity.getText());
}
private void updateSummary() {
}
#FXML
private void add(ActionEvent event) {
int quantity = this.getQuantity();
if (quantity == 99) {
DialogBox.showValidationDialog("The quantity cannot be over 99.");
} else {
this.setQuantity(quantity + 1);
}
}
#FXML
private void substract(ActionEvent event) {
int quantity = this.getQuantity();
if (quantity == 1) {
DialogBox.showValidationDialog("The quantity cannot be below 1.");
} else {
this.setQuantity(quantity - 1);
}
}
}
Something you could try is to pass the CartWindowController into the CartItemComponent constructor, then when you need to notify the CartWindowController, you call a method or set a flag or trigger an event, your choice! All you would need to do is add a parameter of type CartWindowController to you CartItemComponent and just save the reference.
I been searching in YouTube, Stack-Overflow and fxexperience, even oracle documentation but I still don't get it.
There's not similar example :(
The problem is how to do a stack and queue simulator.
Generate 10 random numbers. Done.
Show the numbers in a table. Done.
Use the 10 random numbers to simulate a stack and a queue. I don't now how to comunicate the service with the TextField.
Pause the simulation. or Stop.
-The program needs a pause method. I don't know how to pause a thead. Perhaps with wait() and notify(). I don't know.
I have used label.textProperty.bind(service.progressProperty()). this works but when i try to bind a variable instead
the method updateProgress(i,n) throws a exception.
Maybe I need to use 2 Tasks.
Main class:
package simulation;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("/simulation/simulation.fxml"));
primaryStage.setTitle("JavaFX and concurrency, Stack and Queue");
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Controller Class:
package simulation;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Worker;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import java.net.URL;
import java.util.ResourceBundle;
public class SimulationCt implements Initializable {
#FXML private TableView table;
#FXML private TableColumn i_tc;
#FXML private TableColumn random_tc;
#FXML private TextField stack_start;
#FXML private TextField stack_1;
#FXML private TextField stack_2;
#FXML private TextField stack_3;
#FXML private TextField stack_4;
#FXML private TextField stack_5;
#FXML private TextField stack_final;
#FXML private TextField queue_start;
#FXML private TextField queue_1;
#FXML private TextField queue_2;
#FXML private TextField queue_3;
#FXML private TextField queue_4;
#FXML private TextField queue_5;
#FXML private TextField queue_final;
#FXML private Button new_b;
#FXML private Button play_pause_b;
#FXML private Button stop_b;
#FXML private ProgressBar progress_bar;
private ObservableList<RandomNumber> numberList = FXCollections.observableArrayList();
private CalculateService backProcess;
#FXML
private void createNew () {
disableNew(true);
generateRandom();
backProcess = new CalculateService();
progress_bar.progressProperty().bind(backProcess.progressProperty());
Platform.runLater(() -> {
backProcess.start();
});
}
#FXML
private void playPause () {
if(backProcess.getState().equals(Worker.State.RUNNING)) {
System.out.println("stoping...");
backProcess.cancel();
} else if (backProcess.getState().equals(Worker.State.CANCELLED)) {
System.out.println("restarting...");
backProcess.restart();
}
}
#FXML
private void stop () {
if(backProcess.getState().equals(Worker.State.RUNNING)) {
System.out.println("stoping...");
backProcess.cancel();
} else if (backProcess.getState().equals(Worker.State.CANCELLED)) {
System.out.println("already stoped...");
}
clearItems();
disableNew(false);
}
// cleans the list and the progress bar.
private void clearItems () {
progress_bar.progressProperty().unbind();
progress_bar.progressProperty().set(0.0);
numberList.clear();
}
private void disableNew (boolean b) {
new_b.setDisable(b);
play_pause_b.setDisable(!b);
stop_b.setDisable(!b);
}
// generates random numbers to fill the table, these numbers are the ones for the stack and the queue.
private void generateRandom () {
for (int i = 1; i < 11; i++) {
int rnd = (int)(Math.random() * (200 - 0 + 1)) + 0;
numberList.add(new RandomNumber(i, rnd ));
}
}
private void startTable () {
i_tc.setCellValueFactory( new PropertyValueFactory("i"));
random_tc.setCellValueFactory( new PropertyValueFactory("number"));
table.setItems(numberList);
}
#Override
public void initialize(URL url, ResourceBundle rb) {
disableNew(false);
startTable();
}
}
FXML:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ProgressBar?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<AnchorPane id="AnchorPane" prefHeight="300.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="simulation.SimulationCt">
<children>
<TableView fx:id="table" layoutX="8.0" layoutY="10.0" prefHeight="282.0" prefWidth="162.0">
<columns>
<TableColumn fx:id="i_tc" prefWidth="28.0" text="i" />
<TableColumn fx:id="random_tc" prefWidth="122.0" text="Random Number" />
</columns>
</TableView>
<GridPane layoutX="303.0">
<columnConstraints>
<ColumnConstraints fillWidth="false" minWidth="10.0" prefWidth="50.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
<ColumnConstraints fillWidth="false" halignment="RIGHT" hgrow="SOMETIMES" minWidth="10.0" prefWidth="50.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label text="Stack" />
<VBox GridPane.rowIndex="1" GridPane.valignment="TOP">
<children>
<TextField fx:id="stack_start" prefHeight="25.0" prefWidth="25.0" />
<Label text="new" />
</children>
</VBox>
<VBox GridPane.columnIndex="1" GridPane.rowIndex="1">
<children>
<TextField fx:id="stack_1" prefHeight="25.0" prefWidth="25.0" />
<TextField fx:id="stack_2" prefHeight="25.0" prefWidth="25.0" />
<TextField fx:id="stack_3" prefHeight="25.0" prefWidth="25.0" />
<TextField fx:id="stack_4" prefHeight="25.0" prefWidth="25.0" />
<TextField fx:id="stack_5" prefHeight="25.0" prefWidth="25.0" />
</children>
</VBox>
<VBox GridPane.columnIndex="2" GridPane.rowIndex="1" GridPane.valignment="TOP">
<children>
<TextField fx:id="stack_final" prefHeight="25.0" prefWidth="25.0" />
<Label text="last" />
</children>
</VBox>
</children>
</GridPane>
<GridPane layoutX="193.0" layoutY="155.0">
<columnConstraints>
<ColumnConstraints fillWidth="false" hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
<ColumnConstraints fillWidth="false" hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label text="Queue" GridPane.columnIndex="1" />
<VBox GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="TOP">
<children>
<TextField fx:id="queue_start" prefHeight="25.0" prefWidth="25.0" />
<Label text="new" />
</children>
</VBox>
<HBox spacing="5.0" GridPane.columnIndex="1" GridPane.rowIndex="1">
<children>
<TextField fx:id="queue_1" prefHeight="25.0" prefWidth="25.0" />
<TextField fx:id="queue_2" prefHeight="25.0" prefWidth="25.0" />
<TextField fx:id="queue_3" prefHeight="25.0" prefWidth="25.0" />
<TextField fx:id="queue_4" prefHeight="25.0" prefWidth="25.0" />
<TextField fx:id="queue_5" prefHeight="25.0" prefWidth="25.0" />
</children>
</HBox>
<VBox GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="TOP">
<children>
<TextField fx:id="queue_final" prefHeight="25.0" prefWidth="25.0" />
<Label text="last" />
</children>
</VBox>
</children>
</GridPane>
<Button fx:id="new_b" onAction="#createNew" layoutX="266.0" layoutY="243.0" mnemonicParsing="false" text="New" />
<Button fx:id="play_pause_b" onAction="#playPause" layoutX="326.0" layoutY="243.0" mnemonicParsing="false" text="Play / Pause" />
<Button fx:id="stop_b" onAction="#stop" layoutX="428.0" layoutY="243.0" mnemonicParsing="false" text="Stop" />
<ProgressBar fx:id="progress_bar" layoutX="266.0" layoutY="277.0" prefWidth="200.0" progress="0.0" />
</children>
</AnchorPane>
DataHelper:
package simulation;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
public class RandomNumber {
private IntegerProperty i;
private IntegerProperty number;
public RandomNumber(int i, int number) {
this.i = new SimpleIntegerProperty(i);
this.number = new SimpleIntegerProperty(number);
}
public int getI() {
return i.get();
}
public IntegerProperty iProperty() {
return i;
}
public void setI(int i) {
this.i.set(i);
}
public int getNumber() {
return number.get();
}
public IntegerProperty numberProperty() {
return number;
}
public void setNumber(int number) {
this.number.set(number);
}
}
Service class:
package simulation;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
public class CalculateService extends Service {
int n = 20; // this does the trick to simulate the pause.
int j = 0; // even if the task is canceled the last value is saved here.
#Override
protected Task createTask() {
return new Task() {
#Override protected Void call() throws Exception {
int a;
int b;
int iterations;
for (iterations = j; iterations <= n; iterations++) {
j = iterations;
if (isCancelled()) {
updateMessage("Cancelled");
break;
}
updateProgress(iterations, n);
System.out.println("number: " + j);
//Block the thread for a short time, but be sure
//to check the InterruptedException for cancellation
try {
Thread.sleep(100);
} catch (InterruptedException interrupted) {
if (isCancelled()) {
updateMessage("Cancelled");
break;
}
}
}
return null;
}
};
}
}
There's a lot of code in the question, and I don't think you need it all to address the concepts you're actually asking about. So I'll just give a high-level answer here. If you want to edit your question to something much simpler that addresses the actual issue, then I can make this specific to that example.
I would probably try to do this without threads at all, but using the animation API. For example, you could use a Timeline, with the following basic outline:
public class Controller {
// #FXML-annotated UI elements...
// Other state....
private Timeline timeline ;
#FXML
public void initialize() {
timeline = new Timeline(new KeyFrame(Duration.seconds(100)), e -> {
if (moreStepsToDo()) {
doNextStep();
} else {
stopSimulation();
}
});
timeline.setCycleCount(Animation.INDEFINITE);
}
private boolean moreStepsToDo() {
// return true if there are more steps in the simulation,
// false otherwise
}
private void doNextStep() {
// do next step in the simulation
}
#FXML
private void stopSimulation() {
timeline.stop();
}
#FXML
private void pauseSimulation() {
timeline.pause();
}
#FXML
private void playSimulation() {
timeline.play();
}
#FXML
private void resetSimulation() {
timeline.jumpTo(Duration.ZERO);
}
}
The nice thing about this solution is that everything is single-threaded: the event handler for the key frame is executed on the FX Application Thread, which is the same thread that executes the event handlers. This means there is no need to worry about synchronizing data across threads. The predefined methods in the animation API pause(), play(), and stop() provide exactly the functionality you're looking for; you just have to update the application state appropriately.
Here's a simple complete example that uses this approach (it just moves a bunch of rectangles, one at a time, from one vbox to another).
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class SimplePausableAnimation extends Application {
private VBox left;
private VBox right;
private Timeline timeline;
private Button pausePlay;
#Override
public void start(Stage primaryStage) {
left = new VBox(10);
left.setMinWidth(200);
right = new VBox(10);
right.setMinWidth(200);
HBox hbox = new HBox(10, left, right);
pausePlay = new Button();
Button reset = new Button("Reset");
reset.setOnAction(e -> reset());
reset();
BorderPane root = new BorderPane(hbox);
HBox buttons = new HBox(5, pausePlay, reset);
buttons.setAlignment(Pos.CENTER);
root.setBottom(buttons);
Scene scene = new Scene(root, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
private void reset() {
if (timeline != null) {
timeline.stop();
}
left.getChildren().clear();
right.getChildren().clear();
for (int i = 0; i < 5; i++) {
left.getChildren().add(new Rectangle(100, 100, Color.CORNFLOWERBLUE));
}
timeline = new Timeline(new KeyFrame(Duration.seconds(1), e -> {
if (moreStepsToDo()) {
doNextStep();
} else {
timeline.stop();
}
}));
timeline.setCycleCount(Animation.INDEFINITE);
pausePlay.disableProperty().bind(Bindings.createBooleanBinding(() -> {
if (left.getChildren().isEmpty()) {
return true;
}
return false;
}, left.getChildren()));
pausePlay.textProperty().bind(Bindings.createStringBinding(() -> {
if (timeline.getStatus() == Animation.Status.RUNNING) {
return "Pause";
}
return "Play";
}, timeline.statusProperty()));
pausePlay.setOnAction(e -> {
if (timeline.getStatus() == Animation.Status.RUNNING) {
timeline.pause();
} else {
timeline.play();
}
});
}
private boolean moreStepsToDo() {
return !left.getChildren().isEmpty();
}
private void doNextStep() {
int n = left.getChildren().size();
Node node = left.getChildren().remove(n - 1);
right.getChildren().add(node);
}
public static void main(String[] args) {
launch(args);
}
}
If you do want to do this with threads, one approach to pausing a thread is to use a Semaphore with a single permit. This generally looks something like this:
Semaphore pauser = new Semaphore(1);
Thread simulationThread = new Thread(() -> {
try {
while (! Thread.currentThread().isInterrupted()) {
pauser.acquire();
// do simulation step
pauser.release();
Thread.sleep(100);
}
} catch (InterruptedException exc) {
// ignore and exit thread...
}
});
(Obviously the same idiom will work in your Task, which is executed on a background thread.)
Then calling pauser.acquire(); from the controller will pause the simulation (because the simulation thread will not be able to acquire the permit), and calling pauser.release() while it is paused will let it run again.