JavaFx TabPane One view for each tab but different data - javafx

I'm new to the TabPanes, didn't really worked with them until now, and I need a TabPane with many Tabs but the same view(same look) for each Tab, what changes it is just the data in the tabs.
I have read JavaFX TabPane - One controller for each tab
but this is not what I want, to assign a controller foreach tab. I want to do not duplicate my code, so I wold need to initialize the .fxml which contains my view only once and reuse it every time I swich between the tabs and load the appropriate data to it.
How can I achieve it?
My solution was:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.control.Tab?>
<AnchorPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="tabpane.Controller">
<TabPane>
<Tab>
<fx:include source="CommonUI.fxml" fx:id="first"/>
</Tab>
<Tab>
<fx:include source="CommonUI.fxml" fx:id="second"/>
</Tab>
<Tab>
<fx:include source="CommonUI.fxml" fx:id="third"/>
</Tab>
<Tab>
<fx:include source="CommonUI.fxml" fx:id="forth"/>
</Tab>
<Tab>
<fx:include source="CommonUI.fxml" fx:id="fifth"/>
</Tab>
</TabPane>
</AnchorPane>
public class Controller implements Initializable {
MyService setrvice = MyServiceProvider.getService(MyService.class);
#FXML
private CommonUIController firstController;
#FXML
private CommonUIController secondController;
#FXML
private CommonUIController thirdController;
#FXML
private CommonUIController forthController;
#FXML
private CommonUIController fifthController;
#Override
public void initialize(URL location, ResourceBundle resources) {
// some code.
}
private void load() {
firstController.load(service.firstData());
secondController.load(service.secondData());
thirdController.load(service.thirdData());
forthController.load(service.forthData());
fifthController.load(service.fifthData());
}
}

Create instance of your controller and get view component. Then replace view and update data on tab change.
//pseudoCode
Node controllerView = controller.getView();
TabPane tabPane = new TabPane();
tabPane.getSelectionModel().selectedItemProperty().addListener((observable, oldTab, tab) -> {
if(oldTab != null) {
oldTab.setContent(null);
}
if(tab != null) {
tab.setContent(controllerView);
//pseudoCode
controller.updateData();
}
});

Related

JavaFx: Context menu dynamic style

I have a problem with context menus. The items in the context menu can be styled, but it doesn't work dynamic. I mean for example at the initialize I add a styleclass to the MenuItem and by an event I remove it, but the style still remains there. How can it be solved?
Here is a simple example to play with it:
Controller.java:
public class Controller implements Initializable {
private static final String STYLED = "styled";
#FXML
private ListView<String> listView;
#FXML
private Button change;
#Override
public void initialize(URL location, ResourceBundle resources) {
ContextMenu cm = new ContextMenu();
MenuItem miAdd = new MenuItem("Add");
miAdd.setOnAction(event -> listView.getItems().add("Apple"));
MenuItem miRemove = new MenuItem("Remove");
miRemove.disableProperty().bind(listView.getSelectionModel().selectedItemProperty().isNull());
miRemove.setOnAction(event -> listView.getItems().remove(listView.getSelectionModel().getSelectedItem()));
cm.getItems().addAll(miAdd, miRemove);
listView.setContextMenu(cm);
miRemove.getStyleClass().add(STYLED);
change.setOnAction(event -> {
if (!miRemove.getStyleClass().contains(STYLED)) {
miRemove.getStyleClass().add(STYLED);
} else {
miRemove.getStyleClass().remove(STYLED);
}
});
}
}
View.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.VBox?>
<AnchorPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="textField.Controller"
stylesheets="css/layout.css">
<VBox>
<Button fx:id="change" text="ChangeStyle"/>
<ListView fx:id="listView"/>
</VBox>
</AnchorPane>
layout.css:
.styled .text {
-fx-strikethrough: true;
}
.styled {
-fx-background-color: gray;
}
How can I manage to add/remove style to my MenuItem ?

GridPane acts as mouseTransparent although not set

What do I want to do?
I am facing a problem with a fxml based javafx application. I want to create a simple form sheet to enter some numerical values that are supposed to be stored in a file uppon confirmation.
What is the problem?
The form does perfectly what it is supposed to do, except that I cannot mouse click the elements within the two GridPanes. I can enter the values via switching TextFields by pressing the tab button, but that is of course not the ideal way. I have already added TextFields and CheckBoxes within the AnchorPane directly, and they are clickable, but within the GridPanes this does not work. The FlowPane within the same AnchorPane also works perfectly fine.
To me it seems that the GridPanes act as if it was set to mouseTransparent="true", but this is not the case and I already tried explicitly setting it to false as well. I cannot find the reason for this behavior so perhaps somebody of you has an idea.
EDIT: After playing around with an addtional GridPane in the same tab I found out that it is not a general GridPane issue in this file. For a GridPane with only checkboxes and only one Anchor the element can be accessed. Is there some overlap preventing accessability via mouse?
Within an initializer function of a separate class the scene is set:
BorderPane root = FXMLLoader.load(getClass().getResource("..\\initializeSeason\\InitializeSeason.fxml"));
Scene scene = new Scene(root,600,900);
stage.setScene(scene);
stage.show();
The xfml file is build as follows (I'm sorry for the German names):
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.layout.FlowPane?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.text.Text?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.control.RadioButton?>
<?import javafx.scene.control.CheckBox?>
<BorderPane xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.LeagueRulesController" >
<center>
<TabPane tabClosingPolicy="UNAVAILABLE" >
<tabs>
<Tab style="-fx-font-weight:bold" text="Liga Regeln" >
<AnchorPane >
<children>
<GridPane AnchorPane.topAnchor="10.0" AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" hgap="15" vgap="15" >
<columnConstraints>
<ColumnConstraints hgrow="ALWAYS" />
</columnConstraints>
<children>
<Label text="Anzahl der Zeichen im Teamnamen: " GridPane.columnIndex="0" GridPane.rowIndex="0"/>
<TextField fx:id="numberOfShortNameChars" alignment="BASELINE_RIGHT" prefWidth="100" GridPane.columnIndex="1" GridPane.rowIndex="0"/>
<Label text="Anzahl der Touchdown Differenz Einträge: " GridPane.columnIndex="0" GridPane.rowIndex="1"/>
<TextField fx:id="numberOfTDDiffEntries" alignment="BASELINE_RIGHT" prefWidth="100" GridPane.columnIndex="1" GridPane.rowIndex="1"/>
<GridPane fx:id="tdDiffEntrys" hgap="15" vgap="15" GridPane.columnIndex="1" GridPane.rowIndex="2" />
<Label text="Erhält Gewinnerteam bei Spielaufgabe maximale Punktzahl?:" GridPane.columnIndex="0" GridPane.rowIndex="3"/>
<CheckBox fx:id="isConcedeMaxWin" GridPane.columnIndex="1" GridPane.rowIndex="3"></CheckBox>
</children>
</GridPane>
<FlowPane orientation="HORIZONTAL" alignment="BOTTOM_RIGHT" hgap="5" AnchorPane.topAnchor="10.0" AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0">
<Button fx:id="okButton" text="OK" onAction="#handleOK"> </Button>
<Button fx:id="abbrechenButton" text="Abbrechen" onAction="#handleAbbrechen"> </Button>
</FlowPane>
</children>
</AnchorPane>
</Tab>
<Tab style="-fx-font-weight:bold" text="Teams verwalten">
</Tab>
</tabs>
</TabPane>
</center>
</BorderPane>
Although I am sure the problem can be found within the .fxml file I also add the LeagueRulesController class.
package application;
import java.net.URL;
import java.util.ArrayList;
import java.util.ResourceBundle;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class LeagueRulesController implements Initializable{
private ArrayList<TextField> tdDiffs;
private ArrayList<TextField> points;
#FXML
private CheckBox isConcedeMaxWin;
#FXML
private Button okButton;
#FXML
private Button abbrechenButton;
#FXML
private void handleAbbrechen(ActionEvent actionEvet) {
Stage stage = (Stage) abbrechenButton.getScene().getWindow();
// do what you have to do
stage.close();
}
#FXML
private void handleOK(ActionEvent actionEvet) {
Stage stage = (Stage) abbrechenButton.getScene().getWindow();
stage.close();
}
#FXML
private TextField numberOfShortNameChars;
#FXML
private TextField numberOfTDDiffEntries;
#FXML
private GridPane tdDiffEntrys;
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
numberOfShortNameChars.setText("25");
numberOfShortNameChars.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
numberFieldListener(numberOfShortNameChars, oldValue,newValue);
}
});
numberOfTDDiffEntries.setText("5");
initializeTDDiffArray();
numberOfTDDiffEntries.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
numberFieldListener(numberOfTDDiffEntries, oldValue,newValue);
initializeTDDiffArray();
}
});
}
private void initializeTDDiffArray() {
tdDiffEntrys.getChildren().clear();
tdDiffs = new ArrayList<>();
points = new ArrayList<>();
tdDiffEntrys.add(new Label("TD Diff"), 0, 0);
tdDiffEntrys.add(new Label("Punkte"), 1, 0);
int numberOfTDDiffEntriesInt;
if(numberOfTDDiffEntries.getText().equals(""))
numberOfTDDiffEntriesInt = 1;
else
numberOfTDDiffEntriesInt =Integer.parseInt(numberOfTDDiffEntries.getText());
for (int i = 0; i < numberOfTDDiffEntriesInt; i++) {
tdDiffs.add(new TextField());
points.add(new TextField());
tdDiffs.get(tdDiffs.size()-1).setText(Integer.toString(i-numberOfTDDiffEntriesInt/2));
points.get(points.size()-1).setText(Integer.toString(i+1));
tdDiffs.get(tdDiffs.size()-1).setAlignment(Pos.BASELINE_RIGHT);
points.get(points.size()-1).setAlignment(Pos.BASELINE_RIGHT);
tdDiffs.get(tdDiffs.size()-1).setPrefWidth(1);
points.get(points.size()-1).setPrefWidth(1);
tdDiffs.get(tdDiffs.size()-1).textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
numberFieldListener(tdDiffs.get(tdDiffs.size()-1), oldValue,newValue);
}
});
points.get(points.size()-1).textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
numberFieldListener(points.get(points.size()-1), oldValue,newValue);
}
});
tdDiffEntrys.add(tdDiffs.get(tdDiffs.size()-1), 0, i+1);
tdDiffEntrys.add(points.get(points.size()-1), 1, i+1);
}
}
private void numberFieldListener(TextField textField, String oldValue,String newValue) {
if(!newValue.matches("\\d*")) {
textField.setText(newValue.replaceAll("[^\\d]", ""));
}
}
}
Thanks in advance, for taking the time.
Your FlowPane is on top of your GridPane which stops mouse events from reaching the GridPane. In your FXML file you have the AnchorPane constraints for both your GridPane and FlowPane set to 10 (for top, left, bottom, and right). This means both nodes are taking up the same space in the AnchorPane. And since you add your FlowPane second the GridPane gets covered. Note that adding the GridPane second will most likely allow you to click the TextFields but will stop you from being able to click the Buttons.
Instead of an AnchorPane why don't you use something like a VBox? A VBox is designed for laying out nodes vertically. You could also replace the FlowPane with an HBox since that's the behavior you seem to be after. You'd end up with something like:
<Tab>
<VBox>
<GridPane>
<!-- your GirdPane children -->
</GridPane>
<HBox>
<!-- your buttons -->
</HBox>
</VBox>
</Tab>
I omitted attributes for brevity. You'd obviously set them to your desired values.
If you go this route you'll need to move your Label - the one with its text set to "Erhält Gewinnerteam bei Spielaufgabe maximale Punktzahl?:" - into the new HBox as the first child. That way it stays in line with the buttons.
Of course, you don't have to replace your AnchorPane. If you want to keep it, you'll have to figure out how to have the FlowPane's top anchor always be equal to how far the bottom of the GridPane is from the top. Since you currently have the GridPane's top anchor at 10 the FlowPane's top anchor would have to be something like 10 + gridPaneHeight. I'm not sure you can do this inside the FXML file which means you'd have to do it in your controller's initialize method. If your app is going to be resizable you may also have to update the top anchor whenever a resize occurs.

Unable to get textfield input [JavaFX]

I am unable to extract user input from textfield in javafx.
When I click the button, I want the text from the textfield should be printed in console but it displays nothing. Here is a screenshot: 1
Controller Class:
public class FXMLDocumentController implements Initializable {
#FXML
private Label label;
#FXML
private TextField textField;
#FXML
private Button button;
#FXML
private void handleButtonAction(ActionEvent event) {
textField = new TextField();
System.out.println("You clicked me!" + textField.getText());
label.setText("Hello World!");
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
}
FXML:
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane id="AnchorPane" prefHeight="200" prefWidth="320"
xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="javafxapplication4.FXMLDocumentController">
<children>
<Button fx:id="button" layoutX="126" layoutY="90"
onAction="#handleButtonAction" text="Click Me!" />
<Label fx:id="label" layoutX="126" layoutY="120" minHeight="16"
minWidth="69" />
<TextField fx:id="textField" layoutX="86.0" layoutY="137.0" />
</children>
</AnchorPane>
What is the mistake, and can you provide an explanation if possible?
Thank You.
The problem is here textField = new TextField(); you are redefining the textField instance that's why you're getting empty value, but you're already instanciating the TextField with the #FXML annotation so to retrieve the text inside that textField (already defined before) change your method to this :
#FXML
private void handleButtonAction(ActionEvent event) {
System.out.println("You clicked me!" + textField.getText());
label.setText("Hello World!");
}

ListSelectionView not displaying contents of ObservableList

I have a ListSelectionView (from the ControlsFX library) in JavaFX and I need it to display a set of values when a particular button is clicked. It displays the values when I place the add the contents into it in the initialize method. But when I place the same code in the event action method of a button it doesn't display. I have another method which gets the Source items and Target items from the ListSelectionView and displays them. Even though the ListSelectionView isn't displaying anything, the getSourceItems() method returns a set of values which are then displayed.
public class FXMLDocumentController implements Initializable {
#FXML
private Label label;
#FXML
private Button button;
#FXML
private JFXListView<Label> listv;
#FXML
private ListSelectionView<Label> abc;
#FXML
private void handleButtonAction(ActionEvent event) {
System.out.println("You clicked me!");
label.setText("Hello World!");
}
#Override
public void initialize(URL url, ResourceBundle rb) {
}
#FXML
private void handleToggle(MouseEvent event) {
ObservableList<Label> users = FXCollections.observableArrayList();
abc.setSourceHeader(new Label("Present"));
abc.setTargetHeader(new Label("Absent"));
for (int i=0; i<4;i++)
{
users.add(new Label("Name "+i));
}
abc.setSourceItems(users);
abc.setDisable(false);
for (int i=0; i<4;i++)
{
Label k = new Label("Hello"+i);
listv.getItems().add(k);
}
}
#FXML
private void handlenewNOde(MouseEvent event) {
ObservableList<Label> users1 =abc.getSourceItems();
System.out.println("Source items");
for(int i =0; i<users1.size(); i++)
{
Label k=users1.get(i);
System.out.println(k.getText());
}
System.out.println("Target items");
ObservableList<Label> users12 =abc.getTargetItems();
for(int i =0; i<users12.size(); i++)
{
Label k=users12.get(i);
System.out.println(k.getText());
}
}
}
.fxml :
<?xml version="1.0" encoding="UTF-8"?>
<?import com.jfoenix.controls.JFXButton?>
<?import com.jfoenix.controls.JFXListView?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<?import org.controlsfx.control.ListSelectionView?>
<AnchorPane id="AnchorPane" prefHeight="680.0" prefWidth="914.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.141" fx:controller="listselectionviewtryout.FXMLDocumentController">
<children>
<Button fx:id="button" layoutX="14.0" layoutY="21.0" onAction="#handleButtonAction" text="Click Me!" />
<Label fx:id="label" layoutX="17.0" layoutY="54.0" minHeight="16" minWidth="69" />
<JFXListView fx:id="listv" layoutX="131.0" layoutY="35.0" prefHeight="237.0" prefWidth="75.0" />
<JFXButton layoutX="413.0" layoutY="58.0" onMouseClicked="#handleToggle" text="Toggle Extension" />
<JFXButton layoutX="445.0" layoutY="134.0" onMouseClicked="#handlenewNOde" text="new Node" />
<ListSelectionView fx:id="abc" layoutX="226.0" layoutY="262.0" />
</children>
</AnchorPane>
Ignore the JFXListView.
Output:
Source items
Name 0
Name 1
Name 2
Name 3
Target items
Should
abc.setSourceItems(users);
be
abc.getSourceItems().addAll(users);

Why isnt my JavaFX Progress Indicator not updating?

I have the following layout in my JavaFX application,
StackPane
- BorderPane
- Text (on Top)
- Vbox (on Center)
- ProgressIndicator
- Label
- BorderPane
- Scroll Pane (with Tile Pane inside) (Center of Border Pane)
- Button (Bottom of Border)
- BorderPane
- Vbox (on Center)
- Progress Indicator
- Label
- Button ( Bottom)
When showing the first Border Pane, the Progress Indicator works perfectly. I set it up using the following code,
progress.progressProperty().bind(iconLoadTask.progressProperty());
progresspane.visibleProperty().bind(iconLoadTask.runningProperty());
When the iconLoadTask completes, the progress pane becomes invisible and the next pane is shown correctly.
From the next BorderPane (which has the Scroll pane), I show the last border pane (with a progress indicator - indeterminate). This progress indicator doesnt animate at all.
Edit:
As suggested, I have tried reproducing this issue,
I have the following controller file. Here you can see two progress indicators (progress and stopProfInd). progress is updated from the task appLoadTask. This is a finite task and I can update progress from the task. This works fine.
I have another indicator called stopProfInd. This is set indeterminate from Scene Builder. Here you can see that I make that pane visible. I expect the indicator to animate, but it doesn't.
public class AppController
implements Initializable {
#FXML
private StackPane stackpaneapps;
#FXML
private BorderPane progresspane;
#FXML
private ProgressIndicator progress;
#FXML
private ProgressIndicator stopProfInd;
#FXML
private BorderPane profilingpane;
#FXML
private Label devicename;
private ObservableValue<Number> profProperty;
// Reference to the main application.
private Main mainApp;
private AppIconsTask appLoadTask;
private ProfilingTask prTask;
private Queue<MyVbox> clickedAppQueue;
#Override // This method is called by the FXMLLoader when initialization is complete
public void initialize(URL fxmlFileLocation, ResourceBundle resources) {
clickedAppQueue = new LinkedList<MyVbox>();
}
/**
* Is called by the main application to give a reference back to itself.
*
* #param mainApp
*/
public void setMainApp(Main mainApp) {
this.mainApp = mainApp;
}
/**
* Is called by the main application to give a reference back to itself.
*
* #param task
*
*/
public void setAppLoadTask(AppIconsTask task) {
this.appLoadTask = task;
progress.progressProperty().bind(appLoadTask.progressProperty());
progresspane.visibleProperty().bind(appLoadTask.runningProperty());
appLoadTask.setOnSucceeded(t -> drawAppIcons(appLoadTask.getApps()));
profilingpane.setVisible(false);
}
void drawAppIcons(ObservableList<App> apps){
progress.setVisible(false);
profilingpane.setVisible(true);
prTask = new ProfilingTask();
stopProfInd.progressProperty().bind(prTask.progressProperty());
new Thread(prTask).start();
}
}
Any clues?
FXML:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.paint.*?>
<?import javafx.scene.effect.*?>
<?import javafx.scene.*?>
<?import javafx.scene.image.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<AnchorPane prefHeight="263.0" prefWidth="304.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.gamebench.ios.controller.AppController">
<children>
<StackPane fx:id="stackpaneapps" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="10.0">
<children>
<BorderPane fx:id="profilingpane" prefHeight="200.0" prefWidth="200.0">
<center>
<VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0" BorderPane.alignment="CENTER">
<children>
<ProgressIndicator fx:id="stopProfInd" />
</children>
</VBox>
</center>
</BorderPane>
<BorderPane fx:id="progresspane" prefHeight="200.0" prefWidth="200.0">
<center>
<VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0" BorderPane.alignment="CENTER">
<children>
<ProgressIndicator fx:id="progress" maxHeight="100.0" maxWidth="100.0" prefHeight="68.0" prefWidth="77.0" />
</children>
</VBox>
</center>
</BorderPane>
</children>
</StackPane>
</children>
</AnchorPane>
Adding Task implementation as requested. The following is for appIconsTask which works well.
protected ObservableList<App> call() throws Exception {
String deviceId = "blah";
/* We will start before we get the list of installed apps */
updateProgress(0.0f , 100);
// Get list of installed apps
App[] installedApps = getAppsList(deviceId , false);
appList = FXCollections.observableList(new ArrayList<App>());
int numApps = installedApps.length;
double progress = 1.0f;
for(App app: installedApps){
byte[] pngData;
pngData = comm.getAppIcon(deviceId, app.getBundleId());
app.setAppIcon(pngData);
appList.add(app);
updateProgress((progress/numApps)*100, 100);
progress += 1.0f;
}
return appList;
}
Task implementation for the next progress indicator,
protected Void call() throws Exception {
double f = 1.0;
while(!getStopProf()){
Thread.sleep(30);
}
return null;
}
Reproduction of the issue here,
https://github.com/h-karthik/BugReproStackOverflow
I think you are running into this bug which, at the time of writing, is fixed in the latest GA release (JDK 8u20). The easiest fix, if it's possible for you to do, is to upgrade to and require that version.
If you can't do that, the next most intuitive option is to avoid use of setVisible(...) and just add the progress indicator to the scene graph when you need it. You can define elements in FXML which are not contained in the scene graph by using <fx:define>. Inject this as usual, and then just add the element to the scene graph as you need it. (You can remove it in similar fashion, if you need.)
Your FXML file becomes:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.paint.*?>
<?import javafx.scene.effect.*?>
<?import javafx.scene.*?>
<?import javafx.scene.image.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<AnchorPane prefHeight="263.0" prefWidth="304.0"
xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.stackoverflow.repro.controller.AppController">
<children>
<StackPane fx:id="stackpaneapps" AnchorPane.bottomAnchor="0.0"
AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0"
AnchorPane.topAnchor="10.0">
<children>
<BorderPane fx:id="progresspane" prefHeight="200.0"
prefWidth="200.0">
<center>
<VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0"
BorderPane.alignment="CENTER">
<children>
<ProgressIndicator fx:id="progress" maxHeight="100.0"
maxWidth="100.0" prefHeight="68.0" prefWidth="77.0" />
</children>
</VBox>
</center>
</BorderPane>
</children>
</StackPane>
</children>
<fx:define>
<BorderPane prefHeight="200.0" prefWidth="200.0" fx:id="profilingpane">
<center>
<VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0"
BorderPane.alignment="CENTER">
<children>
<ProgressIndicator fx:id="stopProfInd" />
</children>
</VBox>
</center>
</BorderPane>
</fx:define>
</AnchorPane>
and the controller becomes
package com.stackoverflow.repro.controller;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import com.stackoverflow.repro.Main;
import com.stackoverflow.repro.tasks.AppIconsTask;
import com.stackoverflow.repro.tasks.ProfilingTask;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.*;
public class AppController
implements Initializable {
#FXML
private StackPane stackpaneapps;
#FXML
private BorderPane progresspane;
#FXML
private ProgressIndicator progress;
#FXML
private ProgressIndicator stopProfInd;
#FXML
private BorderPane profilingpane;
// Reference to the main application.
private Main mainApp;
private AppIconsTask appLoadTask;
private ProfilingTask prTask;
#Override // This method is called by the FXMLLoader when initialization is complete
public void initialize(URL fxmlFileLocation, ResourceBundle resources) {
}
/**
* Is called by the main application to give a reference back to itself.
*
* #param mainApp
*/
public void setMainApp(Main mainApp) {
this.mainApp = mainApp;
}
/**
* Is called by the main application to give a reference back to itself.
*
* #param task
*
*/
public void setAppLoadTask(AppIconsTask task) {
this.appLoadTask = task;
progress.progressProperty().bind(appLoadTask.progressProperty());
progresspane.visibleProperty().bind(appLoadTask.runningProperty());
// profilingpane.setVisible(false);
appLoadTask.setOnSucceeded(t -> drawAppIcons());
}
void drawAppIcons(){
// profilingpane.setVisible(true);
stackpaneapps.getChildren().add(profilingpane);
prTask = new ProfilingTask();
new Thread(prTask).start();
}
}
Your workaround with the opacity works as well, but this just feels like a more natural approach. There is a slight difference in functionality, which is not evident from everything being in a StackPane. With both setVisible (when it works correctly) and the setOpacity(...) workaround, the progress indicator will take up space in the layout. With adding and removing it when it is required, it will not take up space in the layout when it is not present. So the choice of approach probably depends on how you want it to behave in terms of layout.
Just a couple of other comments about your code:
If you make the thread running the prTask a daemon thread, it will not prevent the JVM from exiting when you close the last window. Obviously you may have other mechanisms for shutting this down in your real app, but it can be a useful trick (and is less annoying if you're testing this from Eclipse...):
Thread prThread = new Thread(prTask);
prThread.setDaemon(true);
prThread.start();
Also, your threading code in the profiling task looks like it is not quite correct. The stopProf field is almost certainly changed on a different thread to the one on which it is read. This means there is no guarantee of liveness of the field (there can be an arbitrary delay, possible indefinite, between it being changed in one thread and that change being visible in another). You should do one of the following:
Mark the field as volatile, or
Mark the getStopProf() and setStopProf(...) methods as synchronized, or
Replace the boolean field with an AtomicBoolean.
This third option is the one I prefer (favor high-level APIs over low-level primitives):
package com.stackoverflow.repro.tasks;
import java.util.concurrent.atomic.AtomicBoolean;
import javafx.concurrent.Task;
/**
* Created by karthik on 17/02/15.
*/
public class ProfilingTask extends Task<Void> {
AtomicBoolean stopProf;
#Override
protected Void call() throws Exception {
double f = 1.0;
while(!getStopProf()){
Thread.sleep(30);
}
return null;
}
public boolean getStopProf(){
return stopProf.get();
}
public void setStopProf(boolean stop){
stopProf.set(stop);
}
}
So, it turns out that it might be my ignorance or possibly a bug.
In my code instead of the following line
profilingpane.setVisible(true);
I use the following and the problem goes away. This is more of a hack to solve the problem.
profilingpane.setOpacity(0.0);
And then when I want the pane to show up,
profilingpane.setOpacity(1.0);

Resources