javafx button positoning in scene - javafx

how can I set a button's position in the middle for all size of anchorpane in javafx scenebuilder.....
like this code...
the button should be in the middle even if i set the scene into the middle
i have tried so many times but couldent get it...
thanks in advance
if you dont understand try to run the code in IDE and then maximize it and again minimize it...
observe the position of the button
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.StackPane;
import javafx.stage.*;
import javafx.stage.WindowEvent;
import java.util.Optional;
public class Javafxpopupmessage extends Application {
private Stage mainStage;
#Override
public void start(Stage stage) throws Exception {
this.mainStage = stage;
stage.setOnCloseRequest(confirmCloseEventHandler);
Button closeButton = new Button("Close Application");
closeButton.setOnAction(event ->
stage.fireEvent(
new WindowEvent(
stage,
WindowEvent.WINDOW_CLOSE_REQUEST
)
)
);
StackPane layout = new StackPane(closeButton);
layout.setPadding(new Insets(100));
stage.setScene(new Scene(layout));
stage.show();
}
private EventHandler<WindowEvent> confirmCloseEventHandler = event -> {
Alert closeConfirmation = new Alert(
Alert.AlertType.CONFIRMATION,
"Are you sure you want to exit?"
);
Button exitButton = (Button)
closeConfirmation.getDialogPane().lookupButton(
ButtonType.OK
);
exitButton.setText("Exit");
closeConfirmation.setHeaderText("Confirm Exit");
closeConfirmation.initModality(Modality.APPLICATION_MODAL);
closeConfirmation.initOwner(mainStage);
// normally, you would just use the default alert positioning,
// but for this simple sample the main stage is small,
// so explicitly position the alert so that the main window can still be
seen.
closeConfirmation.setX(mainStage.getX());
closeConfirmation.setY(mainStage.getY() + mainStage.getHeight());
Optional<ButtonType> closeResponse = closeConfirmation.showAndWait();
if (!ButtonType.OK.equals(closeResponse.get())) {
event.consume();
}
};
public static void main(String[] args) {
launch(args);
}
}

This can not be done directly in AnchorPane because positions are fixed there. You must use a container that allows "floating positioning". For example HBox.
<AnchorPane xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1">
<children>
<HBox alignment="CENTER" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Button text="Exit" />
</children>
</HBox>
</children>
</AnchorPane>

Related

Selecting ComboBox option on key press. I would like the selection model to auto scroll/jump to the selected option

What I have so far:
When I press for example the C key button, this happens:
First item starts with C is selected.
The selectOptionOnKey (see below) function selected the option that starts with letter C.
The problem:
The combo box stores more option that can be displayed at once. So when the option which is selected is not in the displayed area I want the combo box to scroll down/jump to that option but I don't know how to do that.
Selected an option by pressing a letter key - option is not in the displayed area. - This will happen with the current code.
Selected an option by pressing a letter key - option is not in the displayed area. - This is what I want to happen!
Sample code:
Main:
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("sample.fxml"));
Scene scene = new Scene(root, 850.0, 650.0);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Controller:
import java.net.URL;
import java.util.ResourceBundle;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.ComboBox;
import javafx.scene.control.SingleSelectionModel;
import javafx.scene.input.KeyCode;
public class SampleController implements Initializable {
#FXML private ComboBox<String> cb;
//Entered random options
private final ObservableList<String> options = FXCollections.observableArrayList(
"Aab",
"Aer",
"Aeq",
"Arx",
"Byad",
"Csca",
"Csee",
"Cfefe",
"Cead",
"Defea",
"Dqeqe",
"Fefaf",
"Gert",
"Wqad",
"Xsad",
"Zzz"
);
#Override
public void initialize(URL location, ResourceBundle resources) {
cb.getItems().addAll(options);
selectOptionOnKey();
}
/* When you press a letter key
* this method will search for an option(item) that starts with the input letter key
* and it selects the first occurrence in combo box
*/
public void selectOptionOnKey() {
cb.setOnKeyPressed(e -> {
KeyCode keyCode = e.getCode();
if (keyCode.isLetterKey()) {
char key = keyCode.getName().charAt(0);
SingleSelectionModel<String> cbSelectionModel = cb.getSelectionModel();
cbSelectionModel.select(0);
for (int i = 0; i < options.size(); i++) {
if(cbSelectionModel.getSelectedItem().charAt(0) == key) {
// option which starts with the input letter found -> select it
cbSelectionModel.select(i);
/* Before exiting the function it would be nice if after the selection,
the combo box would auto slide/jump to the option which is selected.
I don't know how to do that. */
return;
}
else
cbSelectionModel.selectNext();
}
}
});
}
}
FXML:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="SampleController">
<children>
<ComboBox fx:id="cb" layoutX="300.0" layoutY="300.0" prefHeight="25.0" prefWidth="173.0" />
</children>
</AnchorPane>
Any help is much appreciated.
As general behaviour, selection in a virtual control does not scroll the selected index/item into the visible region. Slightly astonished that the list in a comboBox' dropdown is no exception to that rule - would have expected at the one that's selected on opening to be visible.
The way out is to scroll the newly selected item in code. This involves:
get hold of the comboBox' skin
get the listView by skin.getPopupContent(): while that method is public, its return type is an implementation detail ..
call list.scrollTo(index)
A code snippet, could be called whenever the selection changed, f.i. in your key handler:
cbSelectionModel.select(i);
ComboBoxListViewSkin<?> skin = (ComboBoxListViewSkin<?>) cb.getSkin();
ListView<?> list = (ListView<?>) skin.getPopupContent();
list.scrollTo(i);

JavaFX imageview actions

I'm trying to build a replica of MineSweeper, to do this I'm trying to setup some premade images (100x100 pixels made in Photoshop) into an imageview and then when clicked hiding it (to reveal the number below). Without much complexity -- Just the image going visible and invisible I am finding a lot of issues and difficulties.
It is likely due to a complete lack of knowledge on Javafx in general but I even following tutorials to the t I am still unable to implement this feature. I will attach to this my Main.Java code, my sample.fxml code (although it's not called anymore), and then the image I'm trying to hide when clicked.
I have done a lot of research on this (past couple of days) and haven't found anything that solves my problems.
Main.java:
package sample;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Main extends Application {
ImageView button;
#Override
public void start(Stage primaryStage) throws Exception{
primaryStage.setTitle("MineSweeper XP");
button = new ImageView();
button.setOnMouseClicked(new EventHandler<MouseEvent>)(){
public void handle(MouseEvent event){
}
}
StackPane layout = new StackPane();
layout.getChildren().add(button);
Scene scene = new Scene(layout,1000, 1000);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
sample.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="100.0" prefWidth="100.0" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1">
<children>
<ImageView fx:id="button" fitHeight="150.0" fitWidth="100.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="#../textures/Button_Custom.png" />
</image>
</ImageView>
</children>
</AnchorPane>
The "Button" that will be used for all the MineSweeper keys
My singular goal at this given moment is to create a window of any size where I can put the button anywhere and then when the user clicks that button it disappears.
One possible solution is to create your own ImageView that holds the code for mouse clicks within.
Here's a sample TileButton class:
class TileButton extends ImageView {
public TileButton() {
// Set parameters for the image
Image graphic = new Image("minesweeper/tile.png");
setImage(graphic);
setFitWidth(24);
setFitHeight(24);
// When this button is click, set its visibility to false.
setOnMouseClicked(e -> {
setVisible(false);
});
}
}
Using this custom class, all the logic for the "button" is contained within the TileButton itself.
Now, you can populate your mine field with a GridPane of StackPane containers in each cell. The StackPane allows you to stack nodes on top of each other. So, place a Label with the number you want into each StackPane and then add your new TileButton on top of it.
Below is a complete sample application to demonstrate. Note, I am not implementing any of the actual game logic here, just providing a sample you can copy/paste and see in action. I also have not spent any time formatting and styling the ImageView, but your could use CSS to make it act like a standard button as well.
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
VBox root = new VBox(5);
root.setPadding(new Insets(10));
root.setAlignment(Pos.CENTER);
// GridPane to hold the cells
GridPane gridPane = new GridPane();
gridPane.setGridLinesVisible(true);
gridPane.setHgap(2);
gridPane.setVgap(2);
// Populate the Gridpane with a 10x10 grid
int number = 0;
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
// Add a new StackPane for each grid cell. The Stackpane will hold a number and the
// TileButton. When the TileButton is clicked, it disappears, revealing the number below
StackPane pane = new StackPane();
pane.getChildren().add(new Label(String.valueOf(number)));
pane.getChildren().add(new TileButton());
gridPane.add(pane, j, i);
// Just increment our sample number
number++;
}
}
root.getChildren().add(gridPane);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
class TileButton extends ImageView {
public TileButton() {
// Set parameters for the image
Image graphic = new Image("minesweeper/tile.png");
setImage(graphic);
setFitWidth(24);
setFitHeight(24);
// When this button is click, set its visibility to false.
setOnMouseClicked(e -> {
setVisible(false);
});
}
}
The above sample application produces this:
Note I am not using FXML for this as creating a grid of multiple custom objects is much simpler in Java than FXML.
Per kleopatra's suggestion, this can be accomplished using a custom Button instead. With the new TileButton class below, you can add the buttons in our loop using gridPane.add(new TileButton(String.valueOf(number)), j, i);:
class TileButton extends Button {
public TileButton(String text) {
// Set the button's size
setPrefSize(24,24);
setStyle("-fx-padding: 0");
// Set the graphic to our tile.png image
setGraphic(new ImageView("sample/done/minesweeper/tile.png"){{
setFitWidth(24);
setFitHeight(24);
}});
// Set the button to display only our graphic initially
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
// Set the action to remove the graphic when the button is clicked
setOnAction(event -> {
setGraphic(new Label(text));
});
}
}

Opening different websites in JavaFX WebView (Update UI?)

I'm trying to load different urls into a javafx webview after a given time. I have searched for a solution for at least 2 weeks but i wasn't able to find anything or wasn't able to transfer the solutions of similar problems to my code. Below are the necessary parts of the code:
Main.java
package application;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.scene.Parent;
import javafx.scene.Scene;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
Parent root = FXMLLoader.load(getClass().getResource("/application/FXML.fxml"));
primaryStage.setTitle("Window");
primaryStage.setScene(new Scene (root));
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
FXMLController.java
package application;
import java.io.File;
import java.net.URL;
import java.nio.file.Files;
import java.util.List;
import java.util.ResourceBundle;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Slider;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.FileChooser;
import javafx.stage.FileChooser.ExtensionFilter;
public class FXMLController implements Initializable {
#FXML private Button btnAddUrl;
#FXML private Button btnStart;
#FXML private Slider sliderTime2NextUrl;
#FXML private WebView webviewWindow;
private WebEngine engine;
private FileChooser fileChooser;
private List<String> url;
private int urlTime;
public void initialize(URL arg0, ResourceBundle arg1) {
btnAddUrl.setOnAction(this::addUrls);
btnStart.setOnAction(this::urlLoad);
fileChooser = new FileChooser();
engine = webviewWindow.getEngine();
getsliderTime2NextUrl();
}//initialize
#FXML
private void addUrls(ActionEvent event) {
fileChooser.setTitle("Add Like List");
fileChooser.getExtensionFilters().addAll(
new ExtensionFilter("TXT Files", "*.TXT"),
new ExtensionFilter("txt Files", "*.txt"));
File file = fileChooser.showOpenDialog(null);
try {
url = Files.readAllLines(file.toPath());
} catch (Exception e) {
System.out.println("shit");
}
System.out.println(url);
}
#FXML
private void getsliderTime2NextUrl() {
sliderTime2NextUrl.valueProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
System.out.println("Slider Value Changed (newValue: " + newValue.intValue() + ")");
double getTime;
getTime = sliderTime2NextUrl.valueProperty().getValue();
urlTime = (int) getTime;
}//changed
});//addListener
}//getsliderTime2NextUrl
#FXML
private void urlLoad (ActionEvent event) {
Task<Void> task = new Task<Void>() {
#Override protected Void call() throws Exception{
urlTime = urlTime * 1000 ;
System.out.println("next Url in: " + urlTime);
for(int i = 0; i < url.size(); i++) {
System.out.println("Url.size = "+ url.size());
System.out.println("Url = "+ i);
if(isCancelled()) {
updateMessage("Cancelled");
break;
}
updateMessage("Iteration " + i);
updateProgress(i, 1000);
try {
Thread.sleep(urlTime);
System.out.println("Thread Sleeps for :" + urlTime);
} catch (InterruptedException interrupted) {
if (isCancelled()) {
updateMessage("Cancelled");
break;
}
}
}
return null;
}
};
new Thread(task).start();
}
/*
#FXML
private void urlLoad (ActionEvent event) {
urlTime = urlTime * 1000 ;
System.out.println("next Url in: " + urlTime);
for(int i = 0; i < url.size(); i++) {
System.out.println("Url.size = "+ url.size());
System.out.println("Url = "+ i);
String Link = url.get(i);
engine.load(Link);
System.out.println("Url loaded = " + Link);
}
}*/
}
FXML.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.web.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<AnchorPane maxHeight="1.7976931348623157E308"
maxWidth="1.7976931348623157E308"
minHeight="400.0" minWidth="500.0"
prefHeight="500.0" prefWidth="500.0"
xmlns="http://javafx.com/javafx/8"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="application.FXMLController">
<children>
<MenuBar maxHeight="25.0" maxWidth="1920.0" prefHeight="25.0" prefWidth="500.0">
<menus>
<Menu mnemonicParsing="false" text="File">
<items>
<MenuItem mnemonicParsing="false" text="Close" />
</items>
</Menu>
<Menu mnemonicParsing="false" text="Edit">
<items>
<MenuItem mnemonicParsing="false" text="Delete" />
</items>
</Menu>
<Menu mnemonicParsing="false" text="Help">
<items>
<MenuItem mnemonicParsing="false" text="About" />
</items>
</Menu>
</menus>
</MenuBar>
<Button fx:id="btnAddUrl" layoutX="66.0" layoutY="39.0" mnemonicParsing="false" onAction="#addUrls" prefHeight="25.0" prefWidth="69.0" text="AddUrl" />
<Button fx:id="btnStart" layoutX="36.0" layoutY="108.0" mnemonicParsing="false" onAction="#urlLoad" prefHeight="31.0" prefWidth="108.0" text="Start!">
<font>
<Font size="18.0" />
</font>
</Button>
<Slider fx:id="sliderTime2NextUrl" blockIncrement="1.0" layoutX="204.0" layoutY="39.0" majorTickUnit="2.0" max="15.0" min="3.0" minorTickCount="1" prefHeight="38.0" prefWidth="251.0" showTickLabels="true" showTickMarks="true" snapToTicks="true" value="3.0" />
<WebView fx:id="webviewWindow" layoutY="147.0" minHeight="-1.0" minWidth="-1.0" prefHeight="350.0" prefWidth="500.0" />
</children>
</AnchorPane>
I used the second "urlLoad"-Method (FXMLController.java) in the /* */ first but recognized that all Links are loaded immediately and only the last one stays active. After that I added "Thread.sleep(urlTime) + try & catch" but it freezed my whole GUI until the last URL was loaded. While searching for a solution, that was the first time I heard of workers, tasks & services.
I found out why this happened and tried to add a "Task" around the code. It was a long jorney until I found the code of the first "urlLoad"-Method in FXMLController.java. Thats exactly what my code with the Urls shoud do
![urlLoad-Method 1] there should be an image if i had 10 reputation
it gives out the numbers in the console with the given time from the slider.
Then i added my engine.load() part to the first "urlLoad"-Method
//updateMessage("Iteration " + i);
//updateProgress(i, 1000);
String Link = url.get(i);
engine.load(Link);
System.out.println("Url loaded = " + Link);
But then the method stops at the first Url and doesnt show anything:
![urlLoad-Method 1 with engine.load] there should be an image if i had 10 reputation
My assumption is, that the "urlLoad"-Method does not update the WebView Window in my GUI. I hope somebody can help me.
Thanks
Edit: After adding
task.setOnFailed((WorkerStateEvent t) -> {
throw new RuntimeException(task.getException());
});
I got the following exception:
Exception in thread "JavaFX Application Thread" java.lang.RuntimeException: java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-5
at application.FXMLController.lambda$2(FXMLController.java:111)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.concurrent.EventHelper.fireEvent(EventHelper.java:219)
at javafx.concurrent.Task.fireEvent(Task.java:1356)
at javafx.concurrent.Task.setState(Task.java:707)
at javafx.concurrent.Task$TaskCallable.lambda$call$502(Task.java:1453)
at com.sun.javafx.application.PlatformImpl.lambda$null$172(PlatformImpl.java:295)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$173(PlatformImpl.java:294)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$147(WinApplication.java:177)
at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-5
at com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:279)
at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:423)
at javafx.scene.web.WebEngine.checkThread(WebEngine.java:1243)
at javafx.scene.web.WebEngine.load(WebEngine.java:913)
at application.FXMLController$2.call(FXMLController.java:92)
at application.FXMLController$2.call(FXMLController.java:1)
at javafx.concurrent.Task$TaskCallable.call(Task.java:1423)
at java.util.concurrent.FutureTask.run(Unknown Source)
... 1 more
you need to use
Platform.Runlater(()->{/*your task content here*/});
Javafx is pissy about what operations it allows you to run in which thread. Essentially, you have no business running code designated for ui control functionality on a non-ui thread... hence "thread 5" in your case.
Be careful, however, as you still need that thread.sleep routine in your background thread, as the javafx thread will sleep, and then proceed with the url.load() routine.
so...
javafxapplication thread (runs - handles its own operations)
thread 5 (runs whatever you want)
->
javafxapplication thread (runs but is idle from your POV)
thread 5 (invokes platform.runlater(/* engine.load("x") */))
->
javafxapplicationthread (executes engine.load())
thread 5 (etc etc etc)
good luck

Javafx: How can I code the calculation to get results when I click the button?

package javafx_tipcalc;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
import java.text.NumberFormat;
public class JavaFX_TipCalc extends Application {
// declare interface controls
// declare labels
Label titleLabel, checkAmtLabel, tipPercentLabel, splitLabel;
Label tipAmtLabel, totalLabel, amtPerPersonLabel;
// declare text fields
TextField checkAmtText, tipAmtText, totalText, amtPerPersonText;
// declare a slider
Slider tipPercentSlider;
// declare a choice box
ChoiceBox splitChoiceBox;
// declare a button
Button calcTipButton;
// declare currency and percent formatter
NumberFormat currency = NumberFormat.getCurrencyInstance();
NumberFormat persent = NumberFormat.getCurrencyInstance();
// declare a grid pane (8 rows and 2 columns)
GridPane grid;
#Override
public void start(Stage primaryStage) {
// instantiate labels and their properties
titleLabel = new Label("Tip Calculator");
titleLabel.setMaxWidth(Double.MAX_VALUE);
titleLabel.setAlignment(Pos.CENTER);
checkAmtLabel = new Label("Check Amount");
checkAmtLabel.setMaxWidth(Double.MAX_VALUE);
checkAmtLabel.setAlignment(Pos.CENTER_RIGHT);
tipPercentLabel = new Label("Tip Percent: 15%");
tipPercentLabel.setMaxWidth(Double.MAX_VALUE);
tipPercentLabel.setAlignment(Pos.CENTER_RIGHT);
splitLabel = new Label("Split");
splitLabel.setMaxWidth(Double.MAX_VALUE);
splitLabel.setAlignment(Pos.CENTER_RIGHT);
tipAmtLabel = new Label("Tip Amount");
tipAmtLabel.setMaxWidth(Double.MAX_VALUE);
tipAmtLabel.setAlignment(Pos.CENTER_RIGHT);
totalLabel = new Label("Total");
totalLabel.setMaxWidth(Double.MAX_VALUE);
totalLabel.setAlignment(Pos.CENTER_RIGHT);
amtPerPersonLabel = new Label("Amount per Person");
amtPerPersonLabel.setMaxWidth(Double.MAX_VALUE);
amtPerPersonLabel.setAlignment(Pos.CENTER_RIGHT);
// instantiate text fileds and their properties
double textFieldWidth = 100;
checkAmtText = new TextField();
checkAmtText.setOnMouseClicked(e -> ResetFields());
checkAmtText.setPrefWidth(textFieldWidth);
checkAmtText.setAlignment(Pos.CENTER_RIGHT);
tipAmtText = new TextField();
tipAmtText.setFocusTraversable(false);
tipAmtText.setPrefWidth(textFieldWidth);
tipAmtText.setAlignment(Pos.CENTER_RIGHT);
tipAmtText.setEditable(false);
totalText = new TextField();
totalText.setFocusTraversable(false);
totalText.setPrefWidth(textFieldWidth);
totalText.setAlignment(Pos.CENTER_RIGHT);
totalText.setEditable(false);
amtPerPersonText = new TextField();
amtPerPersonText.setFocusTraversable(false);
amtPerPersonText.setPrefWidth(textFieldWidth);
amtPerPersonText.setAlignment(Pos.CENTER_RIGHT);
amtPerPersonText.setEditable(false);
// instantiate a slider and its properties
tipPercentSlider = new Slider();
tipPercentSlider.setPrefWidth(10);
tipPercentSlider = new javafx.scene.control.Slider();
tipPercentSlider.setPrefWidth(150);
tipPercentSlider.setMin(0);
tipPercentSlider.setMax(25);
tipPercentSlider.setMajorTickUnit(5);
tipPercentSlider.setMinorTickCount(1);
tipPercentSlider.setBlockIncrement(1);
tipPercentSlider.setShowTickLabels(true);
tipPercentSlider.setShowTickMarks(true);
tipPercentSlider.setSnapToTicks(true);
tipPercentSlider.setValue(15);
tipPercentSlider.setOrientation(Orientation.HORIZONTAL);
tipPercentSlider.valueProperty().addListener(
(observable, oldvalue, newvalue) ->
{
tipPercentLabel.setText(String.format("Tip Percent:
%2d%s",newvalue.intValue(),"%"));
} );
// instantiate a choice box and its properties
splitChoiceBox = new ChoiceBox();
splitChoiceBox.getItems().addAll("1 Way", "2 Ways", "3 Ways", "4
Ways", "5 Ways");
splitChoiceBox.setValue("1 Way");
splitChoiceBox.setPrefWidth(150);
// instantiate a button and its properties
calcTipButton = new Button("Calculate Tip");
calcTipButton.setMaxWidth(Double.MAX_VALUE);
calcTipButton.setOnAction(e -> CalcButtonClick()) ;
// instantiate a grid pane and its properties
grid = new GridPane();
grid.setHgap(10);
grid.setVgap(10);
grid.setPadding(new Insets(10));
grid.add(titleLabel, 0, 0, 2, 1);
grid.addRow(1, checkAmtLabel, checkAmtText);
grid.addRow(2, tipPercentLabel, tipPercentSlider);
grid.addRow(3, splitLabel, splitChoiceBox);
grid.add(calcTipButton, 0, 4, 2, 1);
grid.addRow(5, tipAmtLabel, tipAmtText);
grid.addRow(6, totalLabel, totalText);
grid.addRow(7, amtPerPersonLabel, amtPerPersonText);
// instantiate the grid pane and put items in in grid
Scene scene = new Scene(grid);
scene.getRoot().setStyle("-fx-font: 20 'Comic Sans MS'");
primaryStage.setTitle("Tip Calculator");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
private void CalcButtonClick() {
/*
Get the check amount from checkAmtText
Get the tip percent from the slider
Get the split the choice box
Tip amount = check amount * tip percent
Total amount = check amount + tip amount
Amount per person = total amount / split
Print the tip amount in tipAmtText
Print the total amount in total Text
Print the split amtPerPerson Text
*/
double tipAmnt, checkAmnt, tipPercent,
totalAmnt, AmntPerPerson;
tipAmnt = Double.parseDouble(tipAmtText.getText());
AmntPerPerson =
Double.parseDouble(amtPerPersonText.getText());
checkAmnt = Double.parseDouble(checkAmtText.getText());
totalAmnt = Double.parseDouble(totalText.getText());
tipPercent = Double.parseDouble(tipPercentLabel.getText());
tipAmnt = checkAmnt * tipPercent;
totalAmnt = checkAmnt + tipAmnt;
tipAmtText.setText(currency.format(tipAmnt));
totalText.setText(currency.format(totalAmnt));
}
private void ResetFields() {
/*
Clear the check amount
Clear the tip amount
Clear the total amount
Clear the amount per person
Set the tip percent slider to 15%
Set the split choice box to “1 Way”
*/
}
}
You should use eventListener or FXML. Second, I think, would be easier. Need example?
UPDATE: (I added an example)
Main.java
package application;
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) {
try {
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setLocation((getClass().getResource("Sample.fxml")));
Parent panel = fxmlLoader.load();
Scene scene = new Scene(panel, 300, 150);
primaryStage.setTitle("Sum of two numbers");
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
Sample.fxml
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="150.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.SampleController">
<children>
<AnchorPane prefHeight="55.0" prefWidth="300.0">
<children>
<HBox prefHeight="25.0" prefWidth="200.0" AnchorPane.leftAnchor="50.0" AnchorPane.rightAnchor="50.0">
<children>
<Label text="x">
<HBox.margin>
<Insets left="31.0" />
</HBox.margin>
</Label>
<Label text="y">
<HBox.margin>
<Insets left="62.0" />
</HBox.margin>
</Label>
<Label text="z">
<HBox.margin>
<Insets left="62.0" />
</HBox.margin>
</Label>
</children>
</HBox>
<HBox prefHeight="100.0" prefWidth="200.0" AnchorPane.bottomAnchor="20.0" AnchorPane.leftAnchor="50.0" AnchorPane.rightAnchor="50.0" AnchorPane.topAnchor="20.0">
<children>
<TextField fx:id="xTextField" />
<TextField fx:id="yTextField" />
<TextField fx:id="zTextField" />
</children>
</HBox>
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0">
<HBox AnchorPane.bottomAnchor="20.0" AnchorPane.leftAnchor="50.0" AnchorPane.rightAnchor="50.0" AnchorPane.topAnchor="20.0">
<children>
<Button mnemonicParsing="false" onAction="#sumIt" text="Sum it!" AnchorPane.bottomAnchor="20.0" AnchorPane.leftAnchor="50.0" AnchorPane.rightAnchor="50.0" AnchorPane.topAnchor="20.0" />
</children>
<children>
<Button mnemonicParsing="false" onAction="#clearAll" text="Clear all!" AnchorPane.bottomAnchor="20.0" AnchorPane.leftAnchor="50.0" AnchorPane.rightAnchor="50.0" AnchorPane.topAnchor="20.0" />
</children>
</HBox>
</AnchorPane>
</children>
</VBox>
SampleController.java
package application;
import javafx.fxml.FXML;
import javafx.scene.control.TextField;
public class SampleController {
#FXML
TextField xTextField, yTextField, zTextField;
public void sumIt() {
int sum;
sum = Integer.parseInt(xTextField.getText()) + Integer.parseInt(yTextField.getText());
zTextField.setText(Integer.toString(sum));
}
public void clearAll() {
xTextField.setText("");
yTextField.setText("");
zTextField.setText("");
}
}

ScrollPane containing ImageView does not update its scrollbars after calling setImage()

I am experimenting JavaFX with a simple image viewer. I want it to display an image and, if it does not fit in the window, display scrollbars. The image to dislay is loaded with the FileChooser and set to the ImageView using imageView.setImage(image).
The problem is that the scrollbars of the containing ScrollPane do not update after calling imageView.setImage(image). Instead, I need to perform an action that changes the scene (e.g. resize the window). It behaves the same when an image is displayed and another is loaded, i.e. the sizes of the scrollbars reflect the size of the previous image.
The bug is reproducible using the following cut-down version of the code:
(Java)
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import javafx.application.Application;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
public class ImageViewerBug extends Application
{
#FXML private ImageView imageView;
#Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("ImageViewerBug.fxml"));
fxmlLoader.setController(this);
try {
BorderPane borderPane = (BorderPane) fxmlLoader.load();
Scene scene = new Scene(borderPane);
primaryStage.setScene(scene);
primaryStage.show();
}
catch( IOException e ) {
throw new RuntimeException(e);
}
}
public void loadClicked() {
FileChooser fileChooser = new FileChooser();
File f = fileChooser.showOpenDialog(null);
if( f != null ) {
try( InputStream is = new FileInputStream(f) ) {
Image image = new Image(is);
imageView.setImage(image);
}
catch( IOException e ) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
launch(args);
}
}
(FXML)
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.Group?>
<?import javafx.scene.control.*?>
<?import javafx.scene.image.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.paint.*?>
<BorderPane id="BorderPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml">
<center>
<ScrollPane prefHeight="200.0" prefWidth="200.0">
<content>
<Group>
<ImageView fx:id="imageView" pickOnBounds="true" preserveRatio="true" />
</Group>
</content>
</ScrollPane>
</center>
<top>
<ToolBar>
<items>
<Button mnemonicParsing="false" onAction="#loadClicked" text="Load" />
</items>
</ToolBar>
</top>
</BorderPane>
I can confirm this bug. A quick and dirty workaround that fixed it for me:
public void loadClicked() {
FileChooser fileChooser = new FileChooser();
File f = fileChooser.showOpenDialog(null);
if( f != null ) {
try( InputStream is = new FileInputStream(f) ) {
Image image = new Image(is);
imageView.setImage(image);
imageView.snapshot(new SnapshotParameters(), new WritableImage(1, 1));
double rnd = new Random().nextInt(10);
imageView.setX(rnd/1000);
imageView.setY(rnd/1000);
}
catch( IOException e ) {
e.printStackTrace();
}
}
}
The snapshot() Method sometimes works like a charm whenever some UI-element isn't updated or displayed correctly. But don't ask me why that works, maybe it has something to do with the rendering process, because snapshots forces JavaFX to render the scene graph or the target elements. The random stuff is needed to create the right scrollbar sizes when adding a new image after one already part of the ImageView.
Maybe this has something to do with the strange behaviour i found some weeks earlier. The workaround also solved my problems back then.
(Really late)EDIT
The problem is caused by the Group which wraps the ImageView. If you use a node that inherits from Pane the behaviour of the ScrollPane should be as expected even without the workaround. A Group can still be used as the direct child of the ScrollPane as long as a Pane wraps the ImageView.
Example:
<ScrollPane prefHeight="200.0" prefWidth="200.0">
<content>
<Group id="Group">
<children>
<HBox id="HBox" alignment="CENTER" layoutX="0.0" layoutY="0.0" spacing="5.0">
<children>
<ImageView fx:id="imageView" pickOnBounds="true" preserveRatio="true" />
</children>
</HBox>
</children>
</Group>
</content>
</ScrollPane>
I have had the same problem with image...Instead of
Image image = new Image(is);
You should write:
Image image = new Image("file:" + ABSOLUTE_PATH_TO_FILE);
Image will refresh automatically, you don't have to do anything. For me, it works
use the command:
Scrollbar scrollbar
// ...
// Some activities with children that extends canvas, so scrollbar is needed.
// ...
scrollbar.layout();
this will update your scrollbar.
No Workaround needed.

Resources