I am new to javafX, have run through various tutorials, and googled extensively. I am attempting to write a multiscreen javaFX program whose second screen has a split pane, whose right pane should display a grid and a button. The framework I am using for multiple screens is copied form Angela Caiedo's MultipleScreens Framework tutorial ( https://github.com/acaicedo/JFX-MultiScreen/tree/master/ScreensFramework) .
What works: The screens framework is successful in terms of flipping between my multiple screens.
My problem: I click the button on screen 1 to move to screen 2. Screen 2 successfully shows. On screen 2 I click a button to populate data in my vBox. The code in my controller class for screen 2 is unable to access the vbox (or any other defined containers on this screen).
My main class sets up the controller screens.
public class FieldMapCreator extends Application {
public static String mainID = "main";
public static String mainFile = "MainMapFXMLDocument.fxml";
public static String initialMapID = "initialMap";
public static String initialMapFile = "FXMLMapPicture.fxml";
#Override
public void start(Stage stage) throws Exception {
ScreensController mainContainer = new ScreensController();
mainContainer.loadScreen(FieldMapCreator.mainID, FieldMapCreator.mainFile);
mainContainer.loadScreen(FieldMapCreator.initialMapID, FieldMapCreator.initialMapFile);
mainContainer.setScreen(FieldMapCreator.mainID);
Group root = new Group();
root.getChildren().addAll(mainContainer);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
}
I get to the second screen by clicking a button on screen 1. Button1 code looks like:
#FXML
private void initialMapButtonAction(ActionEvent event) {
myController.setScreen(FieldMapCreator.initialMapID);
}
Screen 2 has a button that when clicked executes a method that creates data for the vbox. The fxml for screen 2 is this:
<AnchorPane id="AnchorPane" fx:id="mainAnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="FieldMapCreator.FXMLMapPictureController">
<children>
<SplitPane fx:id="mapSplitPane" dividerPositions="0.29797979797979796" layoutX="3.0" layoutY="7.0" prefHeight="400.0" prefWidth="600.0">
<items>
<AnchorPane fx:id="anchorPaneLeft" minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0">
<children>
<Button fx:id="backButton" layoutX="14.0" layoutY="348.0" mnemonicParsing="false" onAction="#goToMainScreen" text="Back" />
</children>
</AnchorPane>
<AnchorPane fx:id="anchorPaneRight" minHeight="0.0" minWidth="0.0" prefHeight="364.0" prefWidth="401.0">
<children>
<Button fx:id="vBoxShowMap" layoutX="24.0" layoutY="346.0" mnemonicParsing="false" onAction="#buildMapFromNurseries" prefHeight="26.0" prefWidth="91.0" text="show map" />
<VBox fx:id="mapVBox" layoutX="24.0" layoutY="24.0" onMouseClicked="#vBoxMouseClicked" prefHeight="200.0" prefWidth="309.0" />
</children></AnchorPane>
</items>
</SplitPane>
</children>
</AnchorPane>
The code that fails is in the "showInitialMap()" method of the controller class for screen 2. When executed, the last line of this method "mapVBox.getChildren().add(root)", results in a null pointer exception. I expected this to be resolved via the fxml injection. I also tried the commented out code to retrieve "mapVBox" using scene lookup, but it also results in a null pointer exception. The controller code is below:
public class FXMLMapPictureController implements Initializable, ControlledScreen {
ScreensController myController;
static MyNode[][] mainField ;
#FXML
private AnchorPane mainAnchorPane;
#FXML
private static SplitPane mapSplitPane;
#FXML
private AnchorPane anchorPaneLeft;
#FXML
private static AnchorPane anchorPaneRight;
#FXML
private static VBox mapVBox;
#FXML
private Button vBoxShowMap;
/**
* Initializes the controller class.
*/
#Override
public void initialize(URL url, ResourceBundle rb) {
// Should something be in here ??
}
public void goToMainScreen() {
myController.setScreen(FieldMapCreator.mainID);
}
public void showInitialMap() {
int row = userFieldMap.getNumRows();
int col = userFieldMap.getNumCols();
double gridWidth = 309/col;
double gridHeight = 200/row;
Group root = new Group();
// initialize the field
for (int idx = 0; idx < col; idx++) {
for (int jdx = 0; jdx < row; jdx++) {
// create plot
Plot plot = new Plot(idx, jdx, "Plot" + idx + "/" + jdx);
// Create node to hold the Plot
MyNode node = new MyNode(idx*gridWidth, jdx*gridHeight, gridWidth, gridHeight, plot);
// add plot to group
root.getChildren().add(node);
}
}
//Scene myScene = myController.getScene();
//VBox mapVBox = (VBox)myScene.lookup("#mapVBox");
mapVBox.getChildren().add(root);
}
#Override
public void setScreenParent(ScreensController screenParent) {
myController = screenParent;
}
Is there something missing from the initialize() method? I'm wondering if I am not setting something correctly when I change screens. I am able to access the initial screen's containers without problem from all screen 1 methods including the initialize class. When I attempt the same in screen 2, I get the message "screen hasn't been loaded !!!"
Thanks for any help you can provide.
You are using static fields as FXML injection targets which don't work in Java 8 and should not have been implemented to work in any other version, as static UI elements just don't make sense. Here's a bit more detailed answer. Having resorted to a static field most of the time means the software design of the application needs to be reconsidered.
Related
I'm trying to find a solution of how to add a label into a split pane divider or at least create the illusion of that. My approach was to add three panes into the split pane and drag the middle pane along with the two divider. The problem is that on rapid mouse movements the middle pane gets bigger, but if I define a max height then the dragging doesn't work at all.
My question is if there is a way of directly inserting a label onto the divider or if somebody has a better solution as mine to accomplish something like this.
MWE:
Class
public class MainSplit extends Application {
#FXML
private SplitPane splitPane;
#FXML
private Pane dividerPane;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/splitpane.fxml"));
loader.setController(this);
Parent root = loader.load();
AtomicReference<Double> start = new AtomicReference<>((double) 0);
dividerPane.setOnMousePressed(event -> {
start.set((1.0 / splitPane.getScene().getHeight()) * event.getSceneY());
});
dividerPane.setOnMouseDragged(event -> {
double p = (1.0 / splitPane.getScene().getHeight()) * event.getSceneY();
double diff = p - start.get();
start.set(p);
double[] d = splitPane.getDividerPositions();
splitPane.setDividerPosition(0, d[0] + diff);
splitPane.setDividerPosition(1, d[1] + diff);
});
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
FXML
<SplitPane fx:id="splitPane" dividerPositions="0.36, 0.43" orientation="VERTICAL" prefHeight="400.0" prefWidth="400.0"
xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1">
<AnchorPane/>
<Pane fx:id="dividerPane" minHeight="-Infinity" prefHeight="20.0" style="-fx-background-color: #4545;">
<cursor>
<Cursor fx:constant="V_RESIZE"/>
</cursor>
</Pane>
<AnchorPane/>
</SplitPane>
Let's assume, for simplicity's sake, I have an FXML with a SplitPane. On one side I have a Button; the other has an empty AnchorPane (for now).
The button simply places a pane on the other side of the SplitPane from another FXML file. The newly added pane contains a Button and a Label. Both FXMLs have the same controller for two reasons: 1. The Added Pane FXML is not used elsewhere, 2. The Added Pane FXML has so little functions that it's not really worth it to have a separate controller for it.
Pressing the button on the newly added pane sets the text in its label to "Hello World".
Split Pane
<SplitPane fx:controller="myController".... >
<items>
<VBox .... >
<Button fx:id="mainButton" onAction="#handleFirstButton".... />
</VBox>
<AnchorPane fx:id="secondPane" ....>
</items>
</SplitPane>
Added Pane
<VBox fx:controller="myController" ....>
<Button fx:id="btn" onAction="#changeText" ... />
<Label fx:id="lbl" .... />
</VBox>
Controller Class
// Regular stuff here
#FXML
AnchorPane secondPane;
#FXML
Button mainButton;
#FXML
Button btn;
#FXML
Label lbl;
public void initialize(URL arg0, ResourceBundle arg1) {
}
private void loadSecondPane {
FXMLLoader loader = FXMLLoader.load(getClass().getResource("AddedPane.fxml"));
VBox box = loader.load();
secondPane.setTopAnchor(box);
}
#FXML
private void handleFirstButton(ActionEvent event) {
loadSecondPane();
}
#FXML
private void changeText(ActionEvent event) {
lbl.setText("Hello World");
}
The problem is, when the Added Pane is loaded, its components will NOT be defined in the controller, and trying to perform any method on them will produce a NullPointerException.
Is there a way around this or is having a separate controller mandatory in this case? And assuming I have 2+ buttons on the first side of the pane, and each button produces a different pane on the other side, does the solution remain the same?
NOTE: I'm fairly new with javafx, so excuse any mistypes.
I've searched the entire web (metaphorically) to find an explanation on how to create the jar file that can then be imported by the scene builder to add extra, custom components. Currently, I am trying to create a slider with a textField that displays it's value, with a biDirectional link using a NumberStringConverter. I have the classes all setup, but now I need to bundle them in a jar file, and that's the part that doesn't work for me. These are the classes:
FXML:
<fx:root type="javafx.scene.layout.HBox" xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml" fx:controller="main.java.valueSlider">
<Slider fx:id="slider" HBox.hgrow="ALWAYS"/>
<Label fx:id="label" text="value"/>
</fx:root>
Now I can just import this FXML file and use it as a shortcut to create a slider with a label, but I want there to be a controller involved for the biDirectional link. This is the controller class:
public class valueSlider extends HBox {
#FXML
private TextField value;
#FXML
private Slider slider;
private DoubleProperty sliderPos = new SimpleDoubleProperty();
private DoubleProperty
sliderMin = new SimpleDoubleProperty(),
sliderMax = new SimpleDoubleProperty();
public valueSlider() {
try {
FXMLLoader l = new FXMLLoader(getClass().getResource("valueSlider.fxml"));
l.setController(this);
l.setRoot(this);
l.load();
}
catch (IOException e) {
e.printStackTrace();
}
}
#FXML
private void initialize() {
slider.minProperty().bindBidirectional(sliderMin);
slider.maxProperty().bindBidirectional(sliderMax);
slider.valueProperty().bindBidirectional(sliderPos);
value.textProperty().bindBidirectional(sliderPos, new NumberStringConverter());
}
public double getSliderPos() {
return sliderPos.get();
}
public DoubleProperty sliderPosProperty() {
return sliderPos;
}
public void setSliderPos(double sliderPos) {
this.sliderPos.set(sliderPos);
}
}
So the question now is: how do I export these two classes in a jar file and so that I can load it into the scene builder?
Quick question: I made a new FXML file in the same package and tried to use the component like so:
<AnchorPane xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.121" fx:controller="main.java.controller">
<children>
<valueSlider sliderPos="75"/>
</children>
</AnchorPane>
But when I tried to open the scene builder in the IDE (intelliJ IDEA) it said:
Failed to open the file in the Scene Builder
java.lang.ClassNotFoundException: Unresolved import
javafx.fxml.LoadException:
/G:/GitHub/customParts/src/main/resources/test.fxml
But it doesn't provide any more information, but when I remove the valueSlider component it works perfectly fine.
Please let me know if you know how to solve this problem.
Thanks in advance,
Lenardjee
This question already has answers here:
Javafx PropertyValueFactory not populating Tableview
(2 answers)
Closed 6 years ago.
I just started with javafx and I wanted to create a TableView with 3 columns where I can display some values.
I created the TableView and the columns with the scene editor as fxml file.
Then I created a class named Values with the special properties I matched to the columns where they should fit in.
Finally I set the observable list with the "Value" objects in it as items of the table. When I start the application, it only shows me an empty table.
I looked like 4 hours in the internet now and still not found an answer why this is not working for me.
Here my code:
Value Class:
public class Values {
public SimpleDoubleProperty PSI = new SimpleDoubleProperty(0);
public SimpleDoubleProperty ALPHA = new SimpleDoubleProperty(0);
public SimpleDoubleProperty DELTA = new SimpleDoubleProperty(0);
public Values(Double _PSI, Double _ALPHA, Double _DELTA) {
setPSI(_PSI);
setALPHA(_ALPHA);
setDELTA(_DELTA);
}
private void setPSI(Double p){
PSI.set(p);
}
private void setALPHA(Double p){
ALPHA.set(p);
}
private void setDELTA(Double p){
DELTA.set(p);
}
}
Controller:
#FXML Label psi;
#FXML Label alpha;
#FXML Label delta;
#FXML TextField betafield;
#FXML TextField lambdafield;
#FXML TextField lambdasatfield;
#FXML TableView<Values> table;
#FXML ObservableList<Values> oblist;
#FXML TableColumn <Values,Double> psicolumn;
#FXML TableColumn <Values,Double> alphacolumn;
#FXML TableColumn <Values,Double> deltacolumn;
#Override
public void initialize(URL location, ResourceBundle resources) {
psicolumn.setCellValueFactory(new PropertyValueFactory<Values, Double>("PSI"));
alphacolumn.setCellValueFactory(new PropertyValueFactory<Values, Double>("ALPHA"));
deltacolumn.setCellValueFactory(new PropertyValueFactory<Values, Double>("DELTA"));
}
#FXML
protected void buttonpressed(){
try {
Calculation cal = new Calculation(Double.parseDouble(betafield.getText()), Double.parseDouble(lambdafield.getText()), Double.parseDouble(lambdasatfield.getText()));
alpha.setText("Alpha: " + " " + cal.calculateAlpha());
delta.setText("Delta:"+ " " + cal.calculateDelta());
psi.setText("Psi:"+ " " + cal.calculatePSI());
table.setItems(FXCollections.observableArrayList(cal.calculateEvaluaiontable()));
}catch (NullPointerException e){
e.printStackTrace();
}
}
And my FXML:
<Tab text="tab" fx:id="tabe">
<content>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
<children>
<TableView layoutX="4.0" layoutY="4.0" prefHeight="192.0" prefWidth="370.0" fx:id="table">
<columns>
<TableColumn prefWidth="120.0" text="PSI" fx:id="psicolumn" />
<TableColumn prefWidth="120.0" text="ALPHA" fx:id="alphacolumn" />
<TableColumn prefWidth="120.0" text="DELTA" fx:id="deltacolumn" />
</columns>
</TableView>
</children>
</AnchorPane>
</content>
</Tab>
Thanks for your help!
Let <name> denote the constructor parameter of PropertyValueFactory and let <Name> denote the same String, but with an uppercase first letter.
PropertyValueFactory can get the value from one of the following sources:
The property getter, i.e. the a method SomeObservableValue <name>Property().
The getter method, i.e. the method SomeType get<Name>().
None of those exist in your Values class.
For the psicolumn to work, Values needs a DoubleProperty PSIProperty() method or a double getPSI() method. (Same issue with the other columns)
I have a grid in which I show different images. When the user clicks on an image, an event should be fired. My code now looks like this:
<GridPane alignment="center" hgap="10" vgap="10">
<ImageView
fx:id="cell1" fitHeight="90" fitWidth="70"
GridPane.columnIndex="0" GridPane.rowIndex="0"
onMouseClicked="#previewClicked1" />
<ImageView
fx:id="cell2" fitHeight="90" fitWidth="
GridPane.columnIndex="1" GridPane.rowIndex="0"
onMouseClicked="#previewClicked2"/>
[...]
</GridPane>
I have like 20 of these cells. Do I really need to create an ImageView object and an event method for every one of these objects? E.g. isn't there a way to object has been clicked?
The best option is simply to do this in Java, rather than in FXML. So your FXML will just be
<GridPane fx:id="imagePane" alignment="center" ... >
</GridPane>
Now in your controller's initialize() method define the ImageViews:
public class MyController {
#FXML
private GridPane imagePane ;
public void initialize() {
int numColumns = ... ;
int numRows = ... ;
for (int col = 0; col < numColumns; col++) {
for (int row = 0; row < numRows; row++) {
ImageView imageView = new ImageView();
imagePane.add(imageView, col, row);
imageView.setOnMouseClicked(e -> {
// handle click for this image...
});
}
}
}
}
This is likely way less code in total anyway (since the FXML will be much smaller), and you can readily organize the image views into an array if you need, etc.
If you really want to define all the image views in FXML, one by one, then you can get the source of the event in the event handler. I don't really like this approach because of the necessary downcast, which can potentially make it brittle if you change things at a later stage. However:
#FXML
private void previewClicked(MouseEvent event) {
ImageView clickedImage = (ImageView) event.getSource();
// ...
}
and then just set onMouseClicked="#previewClicked" for all the ImageViews. You still have to laboriously define all the ImageViews in FXML, one by one, though.