In my application, I have to build big panes with a lot of content. I will show a ProgressIndicator while the GUI is loading.
My first test, I will show a ProgressIndicator while I adding a lot of tabs into a TabPane.
That's my test Code:
public class SampleController implements Initializable {
private TabPane tabPane;
#FXML
private BorderPane borderPane;
ProgressIndicator myProgressIndicator;
Task<Void> myLongTask;
#Override
public void initialize(URL location, ResourceBundle resources)
{
myProgressIndicator = new ProgressIndicator();
Pane p1 = new Pane(myProgressIndicator);
tabPane = new TabPane();
Pane p2 = new Pane(tabPane);
myLongTask = new Task<Void>()
{
#Override
protected Void call() throws Exception
{
for (int i = 1; i < 1000; i++)
{
// Thread.sleep(10);
Tab newTab = new Tab("Number:" + i);
tabPane.getTabs().add(newTab);
}
return null;
}
};
borderPane.centerProperty().bind(Bindings.when(myLongTask.runningProperty()).then(p1).otherwise(p2));
new Thread(myLongTask).start();
}
}
But the application will show the window if the Task has finished. If I replace the lines inside the for-loop with Thread.sleep(10) the application show the Indicator and, after all, sleep, it shows the GUI.
How can I show an Indicator while the GUI is not loaded already?
You have a Task that creates a result (i.e. a TabPane). Therefore it's more convenient to use TabPane as type parameter instead of Void also you should call updateProgress to update the progress property and bind that property to the progress property of the ProgressIndicator.
The result can be added to the BorderPane in the onSucceded handler instead of creating a (more or less) complicated binding:
Task<TabPane> myLongTask;
#Override
public void initialize(URL url, ResourceBundle rb) {
myLongTask = new Task<TabPane>() {
#Override
protected TabPane call() throws Exception {
TabPane tabPane = new TabPane();
List<Tab> tabs = tabPane.getTabs();
final int count = 1000 - 1;
for (int i = 1; i <= count; i++) {
Thread.sleep(10);
Tab newTab = new Tab("Number:" + i);
tabs.add(newTab);
updateProgress(i, count);
}
return tabPane;
}
};
myLongTask.setOnSucceeded(evt -> {
// update ui with results
tabPane = myLongTask.getValue();
borderPane.setCenter(new Pane(tabPane));
});
// add progress indicator to show progress of myLongTask
myProgressIndicator = new ProgressIndicator();
myProgressIndicator.progressProperty().bind(myLongTask.progressProperty());
borderPane.setCenter(new Pane(myProgressIndicator));
new Thread(myLongTask).start();
}
Simply creating the tabs is fast however, and you won't see any progress indicator in the UI. Layouting a TabPane with 999 Tabs however is rather slow. The UI will most likely freeze for a short time. You can work around this by adding only a limited number of Tabs in each frame:
Return a List<Tab> from the task instead of a TabPane; these Tabs should not be added to the TabPane (yet). You can use a AnimationTimer to add a fixed number of tabs each frame:
final List<Tab> result = ...; // your tab list
// number of elements added each frame
final int step = 5;
final int size = result.size();
AnimationTimer timer = new AnimationTimer() {
int index = 0;
#Override
public void handle(long now) {
tabPane.getTabs().addAll(result.subList(index, Math.min(size, index+step)));
index += step;
if (index >= size) {
this.stop();
}
}
};
timer.start();
I have change the class like this:
public class SampleController implements Initializable {
#FXML
private BorderPane borderPane;
ProgressIndicator myProgressIndicator;
Task<List<Tab>> myLongTask;
TabPane tabPane = new TabPane();
#Override
public void initialize(URL location, ResourceBundle resources)
{
myLongTask = new Task<List<Tab>>()
{
#Override
protected List<Tab> call() throws Exception
{
List<Tab> newTabs = new ArrayList<Tab>();
final int count = 1000 - 1;
for (int i = 1; i <= count; i++)
{
Tab newTab = new Tab("Number:" + i);
newTabs.add(newTab);
}
return newTabs;
}
};
myProgressIndicator = new ProgressIndicator();
myProgressIndicator.progressProperty().bind(myLongTask.progressProperty());
borderPane.setCenter(new Pane(myProgressIndicator));
new Thread(myLongTask).start();
myLongTask.setOnSucceeded(evt -> {
final List<Tab> result = myLongTask.getValue();
final int step = 5;
final int size = result.size();
AnimationTimer timer = new AnimationTimer() {
int index = 0;
#Override
public void handle(long now) {
tabPane.getTabs().addAll(result.subList(index, Math.min(size, index+step)));
index += step;
if (index >= size) {
this.stop();
}
}
};
timer.start();
borderPane.setCenter(new Pane(tabPane));
});
}
}
Is it this what you mean?
Related
In my program, there are two Form and Checked classes.
In the Form class, there is a Label and a Button. At the click in the Button I create an instance of the class Checked and start its thread.
Now, what I'm having trouble with is that I need to pass the text from the Checked class and change the Label value, but I have not succeeded.
Here is my code:
public class MainForm extends Application {
protected static int intVerifiedNews = 0;
Button btnPlay = new Button("Button");
Label lbVerifiedNews = new Label("News: ");
#Override
public void start(Stage primaryStage) throws IOException {
final BorderPane border = new BorderPane();
final HBox hbox = addHBox();
Scene scene = new Scene(border, 850, 500, Color.BLACK);
btnPlay.setPrefSize(100, 24);
btnPlay.setMinSize(24, 24);
btnPlay.setOnAction((event) -> {
Checked ch = new Checked();
ch.start();
}
);
border.setTop(hbox);
hbox.getChildren().addAll(btnPlay, lbVerifiedNews);
primaryStage.setScene(scene);
primaryStage.show();
}
private HBox addHBox() {
HBox hbox = new HBox();
hbox.setPadding(new Insets(5, 0, 5, 5));
return hbox;
}
public static void main(String[] args) throws IOException {
launch(args);
}
}
Checked class:
public class Checked extends Thread {
public void run() {
for (int i = 0; i <= 5; i++) {
MainForm.intVerifiedNews ++;
//Here you need to pass the intVerifiedNews value to the Label
System.out.println(MainForm.intVerifiedNews);
}
}
}
Pass lbVerifiedNews into the constructor of the Checked class and store this reference in a field.
Checked ch = new Checked(lbVerifiedNews);
public class Checked extends Thread {
Label checkedLabelReference;
public Checked(Label label){
this.checkedLabelReference = label;
}
public void run() {
for (int i = 0; i <= 5; i++) {
MainForm.intVerifiedNews ++;
//Here you need to pass the intVerifiedNews value to the Label
Platform.runLater(new Runnable() {
#Override public void run() {
checkedLabelReference.setText(MainForm.intVerifiedNews);//update text
}});
System.out.println(MainForm.intVerifiedNews);
}
}
}
Generically you'll want to pass reference to the MainForm or form object that you want to update into your Checked class so that you have access to its update methods directly.
public class Checked implements Runnable {
public Checked(MainForm form1) {
// store form (or the object representing the text box directly) to update later
}
public void run() {
}
}
I have a method that read values from the the database and returns a Map<Integer,String>. This method takes some time to return the map.
Till the time values are getting read I want a progress indicator(only loading ring like indicator will be enough,no need for progress bar) to be displayed on screen and all other components should be disabled till the time progress bar is shown.
public void scanDevice() {
ObservableList<TextField> list = FXCollections.observableArrayList(vehicleId, vehicleName, deviceType,
offboardBroker1, offboardBroker2, postfixQueue, pKIServer);
editedValuesMap.clear();
// devicePlugged = true;
if (cbChooseProject.getSelectionModel().getSelectedItem() != null) {
try {
devicePlugged = dsAdapter.getAdapter();
if (devicePlugged) {
if (bScanDevice.isFocused()) {
readMap = new HashMap<Integer, String>();
//Process Start
readMap = dsAdapter.initScan();
//Process End
if (!readMap.isEmpty() && readMap != null) {
isWritten = true;
isDeviceSideEnabled();
editDeviceContents.setDisable(false);
vehicleId.setText(readMap.get(0));
vehicleName.setText(readMap.get(1));
deviceType.setText(readMap.get(2));
offboardBroker1.setText(readMap.get(3));
offboardBroker2.setText(readMap.get(4));
postfixQueue.setText(readMap.get(5));
pKIServer.setText(readMap.get(6));
lContentsSerialNo.setText(readMap.get(7));
}
}
}
You could disabled all nodes with a method like the following but if you are also wanting to wait while something is happening an overlay using StackPanes may be the preferred choice.
public void setNodesDiabled(boolean disable, Node... nodes) {
for(Node node : nodes) {
node.setDisable(disable);
}
}
With an arbitrary node count, you can disable and re-enable as many nodes that are relevant to the process. It also helps to clean up as you won't have several node.setDisable(true); node2.setDisable(true); and so on.
Here in this example you won't need setNodesDisabled() because the StackPane overlay prevents clicking anything other than what's inside it. The background color is gray with 70% alpha so that you can tell it's an overlay.
public class ProgressExample extends Application {
public StackPane layout, main, progress;
public StackPane createProgressPane() {
ProgressIndicator indicator = new ProgressIndicator();
indicator.setMaxHeight(50);
indicator.setMaxWidth(50);
StackPane pane = new StackPane();
pane.setAlignment(Pos.CENTER);
pane.setStyle("-fx-background-color: rgba(160,160,160,0.7)");
pane.getChildren().add(indicator);
Task<Void> task = new Task<Void>(){
protected Void call() throws Exception {
// Your process here.
// Any changes to UI components must be inside Platform.runLater() or else it will hang.
Thread.sleep(2000);
Platform.runLater(() -> {
layout.getChildren().remove(pane);
});
return null;
}
};
new Thread(task).start();
return pane;
}
public StackPane createMainPane() {
Label label = new Label("Hello World!");
label.setFont(Font.font("Tahoma", FontWeight.SEMI_BOLD, 16));
Button start = new Button("Start Process");
start.setOnAction(action -> {
progress = createProgressPane();
layout.getChildren().add(progress);
});
VBox vbox = new VBox(10);
vbox.setAlignment(Pos.CENTER);
vbox.getChildren().addAll(label, start);
vbox.setPadding(new Insets(10,10,10,10));
StackPane pane = new StackPane();
pane.getChildren().add(vbox);
return pane;
}
public void start(Stage stage) throws Exception {
main = createMainPane();
layout = new StackPane();
layout.getChildren().add(main);
Scene scene = new Scene(layout, 900, 550);
stage.setScene(scene);
stage.setTitle("Progress Example");
stage.setOnCloseRequest(e -> {
Platform.exit();
System.exit(0);
});
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I believe the problem is that you are trying to change the values of TextFields inside the Task which is not the FX application thread which is why you are getting Not on FX application thread. To fix this you need to put your lines that modify nodes inside a Platform.runLater() like the following to your if statement.
if (readMap != null && !readMap.isEmpty()) { // Swap the order, can't check empty if it's null.
isWritten = true;
isDeviceSideEnabled();
Platform.runLater(() -> {
editDeviceContents.setDisable(false);
vehicleId.setText(readMap.get(0));
vehicleName.setText(readMap.get(1));
deviceType.setText(readMap.get(2));
offboardBroker1.setText(readMap.get(3));
offboardBroker2.setText(readMap.get(4));
postfixQueue.setText(readMap.get(5));
pKIServer.setText(readMap.get(6));
lContentsSerialNo.setText(readMap.get(7));
});
}
Here is an SSCCE:
It uses a Service that can be started more than once. It is not completebut something to start with.
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
Scene scene = new Scene(root, 400, 400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
Service<Void> serv = new Service<Void>() {
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
int maxWork = 10;
for (int i = 0; i < maxWork; i++) {
Thread.sleep(1000);
updateProgress(i + 1, maxWork);
}
return null;
}
#Override
protected void succeeded() {
super.succeeded();
updateProgress(1, 1);
}
#Override
protected void cancelled() {
super.cancelled();
updateProgress(1, 1);
}
#Override
protected void failed() {
super.failed();
updateProgress(1, 1);
}
};
}
};
ProgressIndicator pi = new ProgressIndicator();
pi.progressProperty().bind(serv.progressProperty());
Button bStart = new Button("Start");
bStart.setOnAction(e -> {
serv.reset();
serv.start();
});
root.setCenter(bStart);
root.setBottom(pi);
primaryStage.setScene(scene);
primaryStage.show();
pi.getScene().getRoot().disableProperty().bind(serv.runningProperty());
}
public static void main(String[] args) {
launch(args);
}
}
In CSS I added:
.progress-indicator:disabled {
-fx-opacity: 1;
}
I have a grid pane which contains a number of buttons (normally something between 10 and 25) with five buttons per row (and however many are left in the last row). The number of buttons (and the buttons itself) might change during program execution. When that happens, the new buttons should be displayed. How can I achieve that? Here is a mini-example:
public class GridButtons extends Application {
List<String> buttonTexts = new ArrayList<>();
GridPane buttonGrid = new GridPane();
GridPane bgGrid = new GridPane();
#Override
public void start(Stage primaryStage) {
Button changeButtonsButton = new Button("Change BTNs");
changeButtonsButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
changeButtonTexts();
}
});
bgGrid.add(changeButtonsButton, 0, 0);
changeButtonTexts();
bgGrid.add(buttonGrid, 1, 0);
Scene scene = new Scene(bgGrid, 440, 140);
primaryStage.setScene(scene);
primaryStage.show();
}
public void updateButtonsGrid() {
buttonGrid = new GridPane();
for (int i = 0; i < buttonTexts.size(); i++) {
Button button = new Button(buttonTexts.get(i));
button.setMinWidth(70);
button.setMaxWidth(70);
buttonGrid.add(button, i % 5, i / 5);
System.out.println(buttonTexts.get(i));
}
// now the new GridPane should be displayed -> how?
}
public void changeButtonTexts() {
buttonTexts.clear();
Random random = new Random();
int buttonCount = random.nextInt(15) + 10;
for (int i = 0; i < buttonCount; i++) {
buttonTexts.add("Button " + i);
}
updateButtonsGrid();
}
public static void main(String[] args) {
launch(args);
}
}
Or are there better options than using a GridPane? Using a ListView<Button> and an ObservableList<Button> would work, but then the buttons are not displayed in a tabular form with five buttons in each row.
Creating a new GridPane is probably not what you want here. In the function updateButtonsGrid, change the line
buttonGrid = new GridPane();
to
buttonGrid.getChildren().clear();
If, for some reason, you are absolutely sure you need a new instance of GridPane, remove the old one from bgGrid, and add the new one. In your current code example the new instance of GridPane is never added to the scene graph.
I want to get the event of two buttons in my custom component.
the component is a imageview with two buttons to move between images, but I need to get the position of the image that is currently displayed, Im storing the key of the image, but I need to know when a button have been pressed outside the custom component, so I can change a Label outside the custom component.
public class TransitionSlider extends AnchorPane {
#FXML
private AnchorPane transitionSliderPane;
#FXML
private ImageView transitionSliderImageView;
#FXML
private Button prevButton;
#FXML
private Button nextButton;
private Map<Integer,Image> imageMap;
private Image currentImage;
private DropShadow imageViewDropShadow;
private int currentKey = 1;
private Image[] images;
public TransitionSlider() {
FXMLLoader loader = new FXMLLoader();
loader.setRoot(this);
loader.setController(this);
loader.setLocation(this.getClass().getResource("TransitionSlider.fxml"));
loader.setClassLoader(this.getClass().getClassLoader());
try {
loader.load();
} catch (IOException exception) {
throw new RuntimeException(exception);
}
prevButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
if(currentKey <= 1){
currentKey = currentKey + 1;
currentImage = imageMap.get(currentKey);
createTransition(transitionSliderImageView, currentImage);
}
}
});
nextButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
if(currentKey <= imageMap.size()){
currentKey = currentKey - 1;
currentImage = imageMap.get(currentKey);
createTransition(transitionSliderImageView, currentImage);
}
}
});
}
// more code here...
}
I want a way to capture the event and get variables inside the component and change a label outside the custom component...
for example:
public class Gallery extends Application {
#FXML
TransitionSlider ts;
Label label;
#Override
public void start(Stage stage) throws Exception {
label = new Label();
TransitionSlider ts = new TransitionSlider();
ts.captureButtonEvent(){ // need a way to capture this
label.setText(ts.getCurrentKey());
}
// more code here....
}
If I understood your question correctly, you want a binding.. Follow these steps:
1) Put bindable field and its getter/setter into TransitionSlider:
private IntegerProperty currentKey = new SimpleIntegerProperty(1);
public int getCurrentKey() {
return currentKey.get();
}
public void setCurrentKey(int val) {
return currentKey.set(val);
}
public IntegerProperty currentKeyProperty() {
return currentKey;
}
2) Bind this property to label's text in Gallery:
label = new Label();
TransitionSlider ts = new TransitionSlider();
label.textProperty.bind(ts.currentKeyProperty().asString());
Alternatively, if you want to do stuff more than just changing label's text, you can add a change listener to currentKeyProperty:
ts.currentKeyProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable,
Number oldValue, Number newValue) {
label.setText(newValue);
// do other stuff according to "oldValue" and "newValue".
}
});
I am trying to add a slider on my page like progress bar. But my code is not working well.
My task is when I am going to copy something from one location to another I want to display a progress bar on my page.
So in javaFx I wrote following task but it is not working well. That code runs but I want show the work in percentage like 30%, 50% and "finish". But my code fails to gives me like requirement so please help me.
My code is:
1.Declaration of progress bar and progress indicator
#FXML
final ProgressBar progressBar = new ProgressBar();
#FXML
final ProgressIndicator progressIndicator = new ProgressIndicator();
2.Assign values when I click on copy button.
#FXML
private void handleOnClickButtonAction(MouseEvent event) {
if (fromLabel.getText().isEmpty()
|| toLabel.getText().isEmpty()
|| fromLabel.getText().equalsIgnoreCase("No Directory Selected")
|| toLabel.getText().equalsIgnoreCase("No Directory Selected")) {
// Nothing
} else {
progressBar.setProgress(0.1f);
progressIndicator.setProgress(progressBar.getProgress());
this.directoryCount.setText("Please Wait !!!");
}
}
This code shows me only 10% completion an then directly shows "done", but I want whole process in percentage like 10,20,30,.. etc and then "done".
My copy code:
double i = 1;
while (rst.next()) {
File srcDirFile = new File(fromLabel.getText() + "/" + rst.getString("nugget_media_files"));
File dstDirFile = new File(toLabel.getText() + "/" + rst.getString("nugget_media_files"));
File dstDir = new File(toLabel.getText() + "/" + rst.getString("nugget_directory"));
if (srcDirFile.lastModified() > dstDirFile.lastModified()
|| srcDirFile.length() != dstDirFile.length()) {
copyDirectory(srcDirFile, dstDirFile, dstDir);
}
this.currentNuggetCount = i / this.nuggetFolderSize;
System.out.println("Nugget Count : " + this.currentNuggetCount);
Platform.runLater(new Runnable() {
#Override
public void run() {
progressBar.setProgress(1.0f);
progressIndicator.setProgress(progressBar.getProgress());
}
});
++i;
}
This is the copyDirectory method:
private static void copyDirectory(File srcDir, File dstDir,File destNugget) {
System.out.println(srcDir+" >> "+dstDir);
if(!destNugget.exists()) {
destNugget.mkdirs();
}
if (srcDir.isDirectory()) {
if (!dstDir.exists()) {
dstDir.mkdirs();
}
String[] children = srcDir.list();
for (int i=0; i<children.length; i++) {
copyDirectory(new File(srcDir, children[i]),
new File(dstDir, children[i]),
destNugget);
}
} else {
InputStream in = null;
try {
in = new FileInputStream(srcDir);
OutputStream out = new FileOutputStream(dstDir);
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
in.close();
out.close();
} catch (IOException ex) {
System.out.println("Exceptio "+ex);
} finally {
try {
in.close();
} catch (IOException ex) {
System.out.println("Exceptio "+ex);
}
}
}
}
Try this code. It will give you Progress bar with progress indicator which depends on the slider control.
public class Main extends Application {
#Override
public void start(Stage stage) {
Group root = new Group();
Scene scene = new Scene(root);
stage.setScene(scene);
stage.setTitle("Progress Controls");
final Slider slider = new Slider();
slider.setMin(0);
slider.setMax(50);
final ProgressBar pb = new ProgressBar(0);
final ProgressIndicator pi = new ProgressIndicator(0);
slider.valueProperty().addListener(new ChangeListener<Number>() {
public void changed(ObservableValue<? extends Number> ov,
Number old_val, Number new_val) {
pb.setProgress(new_val.doubleValue()/50);
pi.setProgress(new_val.doubleValue()/50);
}
});
final HBox hb = new HBox();
hb.setSpacing(5);
hb.setAlignment(Pos.CENTER);
hb.getChildren().addAll(slider, pb, pi);
scene.setRoot(hb);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
You must enter your code inside a Task and set within UpdateProgress method. Before you run the Task you have to set progressBar.progressProperty (). Bind (task.progressProperty ());
This is an example:
TaskTest