In this answer a loader class is used to dynamically load a ComboBox while it is constructed (without post processing).
I wanted to use a similar technique to pre-process and reuse a Polygon , but a Polygon doesn't expose items property, or a similar one.
My solution was to extend Polygon and add such property:
public class DPolygon extends javafx.scene.shape.Polygon{
public ObjectProperty<ObservableList<Double>> items =
new SimpleObjectProperty<>(this, "items");
public DPolygon() {
items.addListener(new ChangeListener<ObservableList<Double>>() {
#Override
public void changed(ObservableValue<? extends ObservableList<Double>>
observable, ObservableList<Double> oldValue,
ObservableList<Double> newValue) {
addPolyPoints();
}
});
}
private void addPolyPoints() { super.getPoints().setAll(items.get());}
public ObjectProperty<ObservableList<Double>>itemsProperty() { return items; }
public final ObservableList<Double> getItems() {return items.get(); }
public final void setItems(ObservableList<Double> value) {items.set(value); }
}
Have a loader class to supply the points:
public class PointsLoader5 {
private ObservableList<Double> items;
public ObservableList<Double> getItems() {
items = FXCollections.observableArrayList(
new Double[] {100.,0.,100., 100., 0., 0.});
return items;
}
}
Use both in the FXML file:
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.Double?>
<?import javafx.xml_polygon.DPolygon?>
<DPolygon fx:id="triangle" fill="GREEN" stroke="RED" strokeType="INSIDE"
items="${pointsLoader.items}" xmlns:fx="http://javafx.com/fxml/1" >
</DPolygon>
And put it all together to test:
public class DrawTrianle5 extends Application {
#Override
public void start(Stage primaryStage) throws IOException {
primaryStage.setTitle("Set points using loader");
Group group = new Group();
GridPane grid = new GridPane();
grid.setAlignment(Pos.CENTER);
grid.setHgap(10);
grid.setVgap(10);
grid.setPadding(new Insets(25, 25, 25, 25));
group.getChildren().add(grid);
FXMLLoader loader = new FXMLLoader(getClass().getResource("triangle/Triangle5.fxml"));
loader.getNamespace().put("pointsLoader", new PointsLoader5());
DPolygon triangle = loader.load();
grid.add(triangle, 0, 0);
Scene scene = new Scene(group, 450, 175);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
It works with no problem.
My question is: is it the right approach, or is there a better way to dynamically loads point to a Polygon constructed byFXML, avoiding post-processing ?
The only thing preventing you doing this with the current Polygon class is that there is no suitable constructor argument with a #NamedArg annotation.
So one way to do this would be:
import java.util.List;
import javafx.beans.NamedArg;
import javafx.scene.shape.Polygon;
public class DPolygon extends Polygon {
public DPolygon(#NamedArg("points") List<Double> points) {
super(points.stream().mapToDouble(Double::doubleValue).toArray());
}
}
Then you just need
<DPolygon xmlns:fx="http://javafx.com/fxml/1" points="$points" fill="GREEN" stroke="RED" strokeType="INSIDE" />
Test:
import java.util.Arrays;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.shape.Polygon;
import javafx.stage.Stage;
public class PolygonTest extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("Polygon.fxml"));
loader.getNamespace().put("points", Arrays.asList(100.0,0.0,100.0,100.0,0.0,0.0));
Polygon poly = loader.load();
Scene scene = new Scene(new Group(poly), 450, 175);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
It feels like there should be an approach without the subclass at all, but I can't quite find one that works.
Related
I am trying to add GUIs, created from individual files and add them into my main code.
While it seems to be working, kind of, however, it is missing some elements. For example, in my GridPane, there are a label and a text, both of which are missing. Likewise, for my treeview, there is a treeitem within, however, that is missing as well.
What I am trying to attempt is to reduce the amount of code in the main field and as well as to call relevant events between the Guis, eg. if I select something in the TreeView, that selected TreeItem information will be populated in the GridPane.
Client.java
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class Client extends Application
{
private treeviewGui tvGui;
private gridpaneGui inputFieldsGui;
public void init()
{
tvGui = new treeviewGui();
inputFieldsGui = new gridpaneGui();
}
#Override
public void start(Stage topView)
{
topView.setTitle("Test Application");
HBox mainLayout = new HBox(10);
mainLayout.getChildren().addAll(tvGui, inputFieldsGui);
Scene scene = new Scene(mainLayout);
topView.centerOnScreen();
topView.setScene(scene);
topView.show();
}
public static void main(String[] argv)
{
launch(argv);
}
}
treeviewGui.java
import javafx.scene.control.*;
public class treeviewGui extends TreeView
{
private TreeView treeview;
public treeviewGui()
{
treeview = new TreeView();
preload();
}
private void preload()
{
TreeItem<String> newTI = new TreeItem<>("blah");
treeview.setRoot(newTI);
}
}
gridPane.java
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.geometry.Pos;
import javafx.scene.text.Text;
public class gridpaneGui extends GridPane
{
private GridPane gridPane;
public Text fnameTxt;
public gridpaneGui()
{
gridPane = new GridPane();
gridPane.setAlignment(Pos.CENTER);
gridPane.setHgap(5);
gridPane.setVgap(5);
// First Name
Label fnameLbl = new Label("First Name");
fnameTxt = new Text("-");
gridPane.addRow(0, fnameLbl, fnameTxt);
}
public void setFname(String nameStr)
{
fnameTxt.setText(nameStr);
}
}
Im working with javaFX and i would like to add a Rectangle object to the game root from a seperate player class. Therfor i created a getRoot function in my Game, and tried adding the rectangle from my player class. Although for some reason it just dosent work.
public class Test {
public void run() {
System.out.println("loaded");
GameScene gameScene = new GameScene();
gameScene.getRoot().getChildren().add(new Sprite(50,50,50,50, Color.GREEN,"TEST", 1, 0));
}
}
public class GameScene {
private Group root = new Group();
public Scene getScene(){
Scene scene = new Scene(root, Color.BLACK);
Test test = new Test();
test.run();
return scene;
}
public Group getRoot() {
return root;
}
}
The gameScene is still just empty when i run it, although the "loaded" string is printed to the console.
Try this for modifying root by another class:
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class GameScene extends Application {
private Group root;
#Override
public void start(Stage stage) {
root = new Group();
stage.setScene(getScene());
stage.show();
}
public Scene getScene(){
Scene scene = new Scene(root);
Test test = new Test();
test.run(root);
return scene;
}
public static void main(String[] args) {
launch(args);
}
}
class Test {
public void run(Group root) {
root.getChildren().add(new Rectangle(50, 50, Color.BLACK));
}
}
Is there a possibility to use a controller with a JavaFX GUI without using FXML.
I noticed that the FXML file contains an fx-controller attribute to bind the controller but i don't find it an easy way to work with it.
Any ideas about have an MVC arch with JavaFX without using the FXML file or JavaFX Scene Builder ?
Your question isn't particularly clear to me: you just create the classes and basically tie everything together with listeners. I don't know if this helps, but here is a simple example that just has a couple of text fields and a label displaying their sum. This is what I regard as "classical MVC": the view observes the model and updates the UI elements if the model changes. It registers handlers with the UI elements and delegates to the controller if events happen: the controller in turn processes the input (if necessary) and updates the model.
Model:
package mvcexample;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ReadOnlyIntegerWrapper;
import javafx.beans.property.SimpleIntegerProperty;
public class AdditionModel {
private final IntegerProperty x = new SimpleIntegerProperty();
private final IntegerProperty y = new SimpleIntegerProperty();
private final ReadOnlyIntegerWrapper sum = new ReadOnlyIntegerWrapper();
public AdditionModel() {
sum.bind(x.add(y));
}
public final IntegerProperty xProperty() {
return this.x;
}
public final int getX() {
return this.xProperty().get();
}
public final void setX(final int x) {
this.xProperty().set(x);
}
public final IntegerProperty yProperty() {
return this.y;
}
public final int getY() {
return this.yProperty().get();
}
public final void setY(final int y) {
this.yProperty().set(y);
}
public final javafx.beans.property.ReadOnlyIntegerProperty sumProperty() {
return this.sum.getReadOnlyProperty();
}
public final int getSum() {
return this.sumProperty().get();
}
}
Controller:
package mvcexample;
public class AdditionController {
private final AdditionModel model ;
public AdditionController(AdditionModel model) {
this.model = model ;
}
public void updateX(String x) {
model.setX(convertStringToInt(x));
}
public void updateY(String y) {
model.setY(convertStringToInt(y));
}
private int convertStringToInt(String s) {
if (s == null || s.isEmpty()) {
return 0 ;
}
if ("-".equals(s)) {
return 0 ;
}
return Integer.parseInt(s);
}
}
View:
package mvcexample;
import javafx.geometry.HPos;
import javafx.geometry.Pos;
import javafx.scene.Parent;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.TextFormatter.Change;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
public class AdditionView {
private GridPane view ;
private TextField xField;
private TextField yField;
private Label sumLabel;
private AdditionController controller ;
private AdditionModel model ;
public AdditionView(AdditionController controller, AdditionModel model) {
this.controller = controller ;
this.model = model ;
createAndConfigurePane();
createAndLayoutControls();
updateControllerFromListeners();
observeModelAndUpdateControls();
}
public Parent asParent() {
return view ;
}
private void observeModelAndUpdateControls() {
model.xProperty().addListener((obs, oldX, newX) ->
updateIfNeeded(newX, xField));
model.yProperty().addListener((obs, oldY, newY) ->
updateIfNeeded(newY, yField));
sumLabel.textProperty().bind(model.sumProperty().asString());
}
private void updateIfNeeded(Number value, TextField field) {
String s = value.toString() ;
if (! field.getText().equals(s)) {
field.setText(s);
}
}
private void updateControllerFromListeners() {
xField.textProperty().addListener((obs, oldText, newText) -> controller.updateX(newText));
yField.textProperty().addListener((obs, oldText, newText) -> controller.updateY(newText));
}
private void createAndLayoutControls() {
xField = new TextField();
configTextFieldForInts(xField);
yField = new TextField();
configTextFieldForInts(yField);
sumLabel = new Label();
view.addRow(0, new Label("X:"), xField);
view.addRow(1, new Label("Y:"), yField);
view.addRow(2, new Label("Sum:"), sumLabel);
}
private void createAndConfigurePane() {
view = new GridPane();
ColumnConstraints leftCol = new ColumnConstraints();
leftCol.setHalignment(HPos.RIGHT);
leftCol.setHgrow(Priority.NEVER);
ColumnConstraints rightCol = new ColumnConstraints();
rightCol.setHgrow(Priority.SOMETIMES);
view.getColumnConstraints().addAll(leftCol, rightCol);
view.setAlignment(Pos.CENTER);
view.setHgap(5);
view.setVgap(10);
}
private void configTextFieldForInts(TextField field) {
field.setTextFormatter(new TextFormatter<Integer>((Change c) -> {
if (c.getControlNewText().matches("-?\\d*")) {
return c ;
}
return null ;
}));
}
}
Application class:
package mvcexample;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class MVCExample extends Application {
#Override
public void start(Stage primaryStage) {
AdditionModel model = new AdditionModel();
AdditionController controller = new AdditionController(model);
AdditionView view = new AdditionView(controller, model);
Scene scene = new Scene(view.asParent(), 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I use JavaFX extensively and do not use FXML or scenebuilder. So I can vouch that it can be done.
Below is the auto generated code made by my IDE to get an JavaFX main class. This will be the root of your application. You will then add to it to create your application.
public class NewFXMain extends Application {
#Override
public void start(Stage primaryStage) {
Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("Hello World!");
}
});
StackPane root = new StackPane();
root.getChildren().add(btn);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
For the rest of us... Here is a VERY simple example showing how to create a JavaFX form without the use of any FXML files. This example can be used within an app that is already running, so I've skipped the Main class and all that ... it's just meant to show the simplicity of JavaFX.
In a nutshell, you simply create your scene based on a container such as an AnchorPane, then you create your Stage and assign the Scene to the stage ... add your controls then show the stage
package javafx;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
public class SimpleFX {
private AnchorPane anchorPane;
private TextArea textArea () {
TextArea textArea = new TextArea();
textArea.setLayoutX(20);
textArea.setLayoutY(20);
textArea.setMaxWidth(450);
textArea.setMinHeight(380);
return textArea;
}
private TextField textField () {
TextField textField = new TextField();
textField.setLayoutX(20);
textField.setLayoutY(410);
textField.setMinWidth(450);
textField.setMinHeight(25);
return textField;
}
private Button button() {
Button button = new Button("Button");
button.setLayoutX(240);
button.setLayoutY(450);
return button;
}
private void addControls () {
anchorPane.getChildren().add(0,textArea());
anchorPane.getChildren().add(1,textField());
anchorPane.getChildren().add(2,button());
}
public void startForm () {
anchorPane = new AnchorPane();
Scene scene = new Scene(anchorPane, 500, 500);
Stage stage = new Stage();
stage.setScene(scene);
addControls();
stage.show();
}
}
I have problem with JavaFX. I created two scenes and switch button.
When I click that button I'm changing scene. But earlier i set fullscreen on true and after I pressed the button, windows taskbar shows for a moment. Is there any way to change scenes without having this taskbar visible?
There is the code:
----Main class----
import java.io.IOException;
public class Main {
public static void main(String[] args) throws InterruptedException, IOException {
DesktopApplication.launch(DesktopApplication.class);
}
}
----DesktopApplication class----
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.HBox;
import javafx.scene.text.Text;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class DesktopApplication extends Application implements Runnable {
Scene firstScene;
Scene secondScene;
Scene scene;
public static Stage primaryStagePublic;
public DesktopApplication() {
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Title");
primaryStage.initStyle(StageStyle.TRANSPARENT);
primaryStage.initStyle(StageStyle.UNDECORATED);
int width = (int) Screen.getPrimary().getBounds().getWidth();
int height = (int) Screen.getPrimary().getBounds().getHeight();
HBox mainLayout = new HBox();
mainLayout.getChildren().add(new Text("hello!"));
MyLayout myLayout = new MyLayout(this);
firstScene = new Scene(myLayout,width,height);
secondScene = new Scene(mainLayout, width, height);
scene = firstScene;
primaryStage.setScene(scene);
primaryStage.setFullScreen(true);
primaryStage.setFullScreenExitKeyCombination(KeyCombination.NO_MATCH);
primaryStage.show();
primaryStagePublic = primaryStage;
}
#Override
public void run() {
Thread thread = new Thread() {
public void run() {
launch(DesktopApplication.class);
}
};
thread.start();
while (true) {
}
}
public void swapScenes(Stage primaryStage){
primaryStage.setScene(secondScene);
primaryStage.setFullScreen(true);
primaryStage.setFullScreenExitKeyCombination(KeyCombination.NO_MATCH);
}
}
----MyLayout class----
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
public class MyLayout extends HBox{
private DesktopApplication desktopApplication;
public MyLayout(DesktopApplication desktopApplication) {
this.desktopApplication = desktopApplication;
init();
}
private void init(){
this.setStyle("-fx-background-color: #f8ff7d;");
Label text = new Label("testing");
Button button = new Button("Button");
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
desktopApplication.swapScenes(DesktopApplication.primaryStagePublic);
}
});
this.getChildren().addAll(text, button);
}
}
I had a similar issue and solved it like #James_D suggested: Do not replace the scene as a whole, but only the root element:
public void swapScenes(Parent newContent){
stage.getScene().setRoot(newContent);
}
This requires changing the rest of the initialisation code a bit:
public class DesktopApplication extends Application implements Runnable {
Parent myLayout;
Parent mainLayout;
Scene scene;
public static Stage stage; // if possible make this private and non static
public DesktopApplication() {
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Title");
primaryStage.initStyle(StageStyle.TRANSPARENT);
primaryStage.initStyle(StageStyle.UNDECORATED);
int width = (int) Screen.getPrimary().getBounds().getWidth();
int height = (int) Screen.getPrimary().getBounds().getHeight();
mainLayout = new HBox();
mainLayout.getChildren().add(new Text("hello!"));
myLayout = new MyLayout(this);
scene = new Scene(myLayout,width,height);
primaryStage.setScene(scene);
primaryStage.setFullScreen(true);
primaryStage.setFullScreenExitKeyCombination(KeyCombination.NO_MATCH);
primaryStage.show();
primaryStagePublic = primaryStage;
}
...
I personally solved it (After a few months of looking) by, instead of doing primaryStage.setFullScreen(true), which glitches or something, doing primaryStage.setMaximized(true) along with primaryStage.setWidth(var1) and primaryStage.setHeight(var2). My hypothesis on why setFullScreen deosn't work is a bug in full screen exclusive mode. Or, there just isn't enough permissions or something and it bugs out.
I can add a ChangeListener to a Scene and call it on the scene.widthProperty() and
scene.heightProperty(), but this doesn't apply when the window is maximized via the Maximize button.
I can't find any onResize property of the window when it is accessed like scene.getWindow()
Here's what I have to resize columns in a table based off resizing the window.
How can I make that resizeColumns listener be added to when the whole window is Maximized (which doesn't qualify as a scene.widthProperty() or scene.heightProperty()
ChangeListener<Object> resizeColumns = new ChangeListener<Object>(){
#Override
public void changed(ObservableValue arg0, Object arg1, Object arg2) {
new Thread() {
// runnable for that thread
public void run() {
Platform.runLater(new Runnable(){
public void run() {
// what will be ran in gui thread
Double width =primaryStage.getWidth();
DraftController controller = (DraftController)loader.getController();
TableView<Player> teamTable =controller.getTeamTable();
centerColumns(width, controller, teamTable);
TableView<Player> top10Table = controller.getTop10Table();
AnchorPane anchor = controller.getAnchorPane();
centerColumns(anchor.getWidth()+anchor.getWidth()*.04,controller,top10Table);
}
private void centerColumns(Double width, DraftController controller, TableView<Player> teamTable) {
ObservableList<TableColumn<Player, ?>> columnList = teamTable.getColumns();
for (int i=0 ; i<columnList.size(); i++){
columnList.get(i).setPrefWidth((width-17)/teamTable.getColumns().size());
}
}
});
}
}.start();
}
};
scene.widthProperty().addListener(resizeColumns);
scene.heightProperty().addListener(resizeColumns);
guage: lang-java -->
My little example works well on maximizing the stage:
package org.example;
import javafx.application.Application;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class ResizeScene extends Application {
#Override
public void start(final Stage primaryStage) throws Exception {
final StackPane stack = new StackPane();
final Text resolution = new Text();
stack.getChildren().add(resolution);
final Scene scene = new Scene(stack);
primaryStage.setScene(scene);
final InvalidationListener resizeListener = new InvalidationListener() {
#Override
public void invalidated(final Observable observable) {
final double width = scene.getWidth();
final double height = scene.getHeight();
resolution.setText(width + " x " + height);
}
};
scene.widthProperty().addListener(resizeListener);
scene.heightProperty().addListener(resizeListener);
// Initial Size
primaryStage.setWidth(800);
primaryStage.setHeight(600);
primaryStage.show();
}
public static void main(final String[] args) {
launch(args);
}
}