I am writing a simulation software for my Masters and it consists on a "Graph container" where you can link nodes to generate equations according to what I link. These equations will, then, enable me to simulate my model. I am using Java 8 and JavaFX for that, with Scene Builder and FXML.
Searching on the web, I found the Graph-Editor (https://github.com/tesis-dynaware/graph-editor), which will help me a lot with what I need. Following the Tutorial on the project's site, I could reproduce it and it is running. But on my software I do not need to create a new window as the tutorial does to use the graphs - instead, I want to have a TabPane that enables me to create as many models as I need, like a text editor, and if I want I can save it on XML, etc...
My problem is: I tried to put the graphs from the tutorial inside the Tab they do on the tutorial (with the getView method) and it is not working. I tried it in two different ways, which result in an empty Tab, with no nodes and no error on the console.
First try
I tried putting into a Pane and set the GraphEditor inside the Pane.
My java code:
private GraphEditor graphEditor = new DefaultGraphEditor();
#FXML
private Pane graphEditorPane;
#FXML
public void initialize(){
graphEditorPane = new Pane(graphEditor.getView());
GModel model = GraphFactory.eINSTANCE.createGModel();
graphEditor.setModel(model);
addNodes(model);
}
My FXML code:
<TabPane tabClosingPolicy="UNAVAILABLE" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<tabs>
<Tab text="Modelo 1">
<content>
<AnchorPane minHeight="0.0" minWidth="0.0">
<children>
<Pane fx:id="graphEditorPane" prefHeight="571.0" prefWidth="1000.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
</content>
</Tab>
</tabs>
</TabPane>
Second way
I have seen their demo source code and what I understood was that they created an instance of their GraphEditorContainer object and then their FXML file has that GraphEditorContainer, but mine doesn't work that way. Maybe I got what they did wrong (I am a beginner in Java and JavaFX).
My java code:
private GraphEditor graphEditor = new DefaultGraphEditor();
#FXML
private GraphEditorContainer graphEditorContainer;
#FXML
public void initialize(){
graphEditorContainer = new GraphEditorContainer();
GModel model = GraphFactory.eINSTANCE.createGModel();
graphEditor.setModel(model);
graphEditorContainer.setGraphEditor(graphEditor);
addNodes(model);
}
My FXML code:
<?import de.tesis.dynaware.grapheditor.GraphEditorContainer?>
<TabPane tabClosingPolicy="UNAVAILABLE" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<tabs>
<Tab text="Modelo 1">
<content>
<AnchorPane minHeight="0.0" minWidth="0.0">
<children>
<GraphEditorContainer fx:id="graphEditorContainer" minWidth="0" minHeight="0" maxWidth="+Infinity" maxHeight="+Infinity"/>
</children>
</AnchorPane>
</content>
</Tab>
</tabs>
</TabPane>
I could put the code that opens the window and draws the nodes in the handleNew function (code below), but not in the Tab.
Stage secondaryStage = new Stage();
GraphEditor graphEditor = new DefaultGraphEditor();
Scene scene = new Scene(graphEditor.getView(), 800, 600);
secondaryStage.setScene(scene);
secondaryStage.show();
GModel model = GraphFactory.eINSTANCE.createGModel();
graphEditor.setModel(model);
addNodes(model);
If it's possible could you help me?
Thank You
Error on console:
javafx.fxml.LoadException: GraphEditorContainer is not a valid type.
simply means that you didn't put the import for GraphEditorContainer in the FXML file. Something like
<? import com.somecompany.somepackage.GraphEditorContainer ?>
near the top of the FXML file (with the other imports), obviously edited for the correct package name.
In the controller, it is always a mistake to initialize #FXML-annotated fields, for obvious reasons, so replace
#FXML
private GraphEditorContainer graphEditorContainer = new GraphEditorContainer();
with
#FXML
private GraphEditorContainer graphEditorContainer ;
Using custom (or 3rd party) controls in SceneBuilder is covered in Adding a custom component to SceneBuilder 2.0
Related
Relevant FXML :
<ScrollPane fx:id="scrollpane_var" hbarPolicy="NEVER" prefHeight="200.0" prefWidth="200.0" GridPane.columnSpan="3" GridPane.rowIndex="2" GridPane.rowSpan="5">
<content>
<TableView fx:id="tableview_var" prefHeight="169.0" prefWidth="292.0">
<columns>
<TableColumn prefWidth="69.0" text="Quantité" />
<TableColumn prefWidth="161.0" text="Nom" />
<TableColumn prefWidth="56.0" text="Prix" />
</columns>
</TableView>
</content>
</ScrollPane>
Controller class Code :
public class Lancer {
#FXML // fx:id="scrollpane_var
private ScrollPane scrollpane_var; // Value injected by FXMLLoader
#FXML // fx:id="tableview_var"
private TableView<String> tableview_var; // Value injected by FXMLLoaderte void initialize()
public void initialize() {
tableview_var.prefWidthProperty().bind(scrollpane_var.prefWidthProperty());
}
Visual Representation :
Click to see Scenebuilder screenshot
as the title says i'm trying to bind a TableView's width to it's Parent container and the documentation is a bit confusing as i am very new to GUI Applications .
This line of code is the furthest i've gottent and i dont know why it's not working :
public void initialize() {
tableview_var.prefWidthProperty().bind(scrollpane_var.prefWidthProperty());
}
A TableView manages its own scrolling, so you should not be putting it inside a ScrollPane at all. Just omit the scroll pane from the layout entirely.
In general, if you want the content of a scroll pane to fit the available width of the scroll pane, you would use the fitToWidth property, instead of trying to use a binding. In general, binding the pref width like this is a bad idea; if the layout or container you are using doesn't provide the functionality you need, and it almost always will, you should subclass Pane and override layoutChildren() and other methods. Here, the TableView already provides scrolling, and in more general cases, the ScrollPane allows you to specify the content should fit the width of the scroll pane.
<ScrollPane ... fitToWidth="true>
<!-- ... -->
</ScrollPane>
But again, here the scroll pane is redundant.
Example:
<AnchorPane xmlns="http://javafx.com/javafx/9.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="HomeController">
<children>
<VBox fx:id="vBoxParent" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity">
<children>
<MenuBar fx:id="menubar" prefHeight="30.0" prefWidth="800.0">
...
With this code, the stage opens fullsize, but the menubar isn´t covering all the lenght. What do I have to add?
If you want the MenuBar to take the full size of VBox, you can replace VBox with MenuBar, placing MenuBar directly in AnchorPane, and giving Anchor Pane Constraints to the MenuBar (to be responsive).
If you want to keep VBox for some reason, you can change the MenuBar properties:
set Vgrow to: allways (allways increase the vertical length)
set Max Width to: MAX_VALUE
I recommend you to use SceneBuilder, to easily find functions and preview changes.
I've got a Java project I'm working on that uses JavaFX to generate a UI. I'm not much of a Java programmer (not much of a programmer in general - I'm a CS student), so it's a bit of a learning experience, but generally I'm answering my own questions as I go. I'm now hitting a sticky spot with a TableView, though, and getting a bit lost - specifically, I can't seem to set any kind of action in response to interacting with the TableView.
The current layout of my project is Main.java, Controller.java and UI.fxml. In Main the pane/scene is loaded from UI.fxml as the program starts. (Technically the project contains several other bits of code, but I'm trying to only post what's relevant.) I have a TableView defined in UI.fxml as so:
<TableView fx:id="queueTable">
<columns>
<TableColumn text="Title">
<cellValueFactory>
<PropertyValueFactory property="tableTitle"/>
</cellValueFactory>
</TableColumn>
<TableColumn text="Artist">
<cellValueFactory>
<PropertyValueFactory property="tableArtist"/>
</cellValueFactory>
</TableColumn>
<TableColumn text="Album">
<cellValueFactory>
<PropertyValueFactory property="tableAlbum"/>
</cellValueFactory>
</TableColumn>
</columns>
</TableView>
Similarly I have several buttons defined in UI.fxml with onAction as so:
<Button fx:id="buttonQueueAdd" text="Add" onAction="#addMediaToQueue"/>
In my previous code I was able to define an event for when an item in the TableView was selected, in the method where my TableView was being generated. Now, however, all the UI creation is done via the FXML file. The FXML file has a controller class it refers to (Controller.java), where all the actions are defined for the various buttons, but I can't seem to get it to accept any attribute like onAction, setOnMousePressed, etc. in the FXML file for the TableView like it accepts an 'onAction' attribute for the Button.
It seems like I can get a workaround going by defining the action in my Main class (or creating a method in my Controller class that's called in the Main class, when the scene is being created), but that won't work unless I make queueTable static in my Controller class... which then breaks all the functions that manipulate items in the TableView - for example, I can no longer add items to the TableView/the UI does not refresh to show items are added.
It seems like the best solution is to not have the TableView created in FXML, to instead have the TableView created and all its onMousePressed events and etc. set up outside of the FXML file and have them added to the scene in my Main class's start method. This is how it was done previously, and it worked acceptably then. However, that seems messy, and leads to my UI elements being strewn across multiple classes and/or the FXML file - this is a project I'm working on with several other students, and I'm trying to keep everything as streamlined/organized as possible. Do I have to bite the bullet and do it the "messy" way, or is there a better option?
You can still generate the TableView using the fxml, but add a initialize method to the controller. This method is invoked by the FXMLLoader after it's done creating the object stucture and can be used to do modifications to the object structure that you cannot do from fxml:
#FXML
private void initialize() {
queueTable.getSelectionModel().selectedIndexProperty().addListener((observable, oldValue, newValue) -> {
...
});
}
In case you need to add this listener from Main, you could add a method to the controller and get the controller from the FXMLLoader instance you use to create the fxml.
Controller
public void addSelectedIndexListener(ChangeListener<? super Number> listener) {
queueTable.getSelectionModel().selectedIndexProperty().addListener(listener);
}
Main
FXMLLoader loader = new FXMLLoader(fxmlUrl);
myNode = loader.load();
Controller controller = loader.getController();
controller.addSelectedIndexListener((observable, oldValue, newValue) -> {
...
});
Note that is an property property should depend on a property of the selection model, you may be able to use expression binding to achieve the desired effect.
<BorderPane xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1">
<center>
<TableView fx:id="table" BorderPane.alignment="CENTER">
<columns>
<TableColumn text="value">
<cellValueFactory>
<PropertyValueFactory property="value"/>
</cellValueFactory>
</TableColumn>
</columns>
<items>
<FXCollections fx:factory="observableArrayList">
<Item value="Hello World"/>
<Item value="42"/>
</FXCollections>
</items>
</TableView>
</center>
<left>
<Button mnemonicParsing="false" text="Remove" BorderPane.alignment="CENTER" disable="${table.selectionModel.selectedIndex+0<0}" />
</left>
</BorderPane>
Note that the table.selectionModel.selectedIndex+0<0 part is a workaround used, since JavaFX currently doesn't use null-safe equality checks (table.selectionModel.selectedItem==null doesn't work) and cannot get the type of the 0 constant right for comparison (table.selectionModel.selectedIndex<0), so this currently doesn't seem like a good option.
A JavaFX application exists and the application is starting from the Main.class file which extends Application:
public class Main extends Application {
/**
* Keep a reference to the main Stage
*/
public static Stage stage;
/**
* MainScene Controller
*/
public static MainScene mainSceneController;
/**
* The Capture Window of the application
*/
public static CaptureWindow captureWindowController;
/**
* Settings Scene Controller
*/
public static SettingsController settingsController;
#Override
public void start(Stage primary) throws Exception {
stage = primary;
..........
// CaptureWindow
FXMLLoader loader1 = new FXMLLoader(getClass().getResource("/fxml/CaptureWindow.fxml"));
loader1.load();
captureWindowController = loader1.getController();
// MainScene
mainSceneController = new MainScene();
.........
}
}
Description
As you can see above I have 3 FXMLControllers(one is reusable[extends StackPane],others not).I have declared all of them static cause i want to access variables from one FXMLController from the other FXMLControllers.I use this strategy every time I use JavaFX and I don't think is good...
How I can change the code below so I can access variables or methods of one FXMLController from other FXMLController? Is that possible without using static keyword?
Consider that the Controllers are represented from different classes which can be in different packages.
Also before writing this question I had a look Static #FXML variables in FXML Controller
Actually the answer for this question seems a little bit complicated it has to do with MVC pattern and it's evolution until now.We will use MVP Pattern.
After a long discussion i got a link on this website http://martinfowler.com/eaaDev/uiArchs.html defining the historical evolution of the different patterns used from the old ages of Smalltalk until now.
The actually solution is using Model Viewer Presenter Pattern(MVP) which can be visually described using these images:
For more you can read(http://www.wildcrest.com/Potel/Portfolio/mvp.pdf)
For an example on JavaFX have a look on James_D answer here (Applying MVC With JavaFx)
Last but not least have a look at the comments here:
Finally:
If anything is inaccurate feel free to edit.
I think you are not supposed to declare #FXML annotated attributes with the static keyword. See this GitHub project on how you should do it. Like that you instantiate a controller only when it is needed, and your app will be stateless.
JavaFx is mostly composed of set of [well designed] tools but unfortunately by itself does not provide a good framework for creating complex UI designs e.g. MVC/MVP patterns, view flows and actions on multiple controllers, So you have to rely on third-party application frameworks for those for example:
DataFx
JacpFx
JRebirth
JFX Flow
In my opinion none of them are widely used or mature enough to be considered a de facto standard but using them is encouraged.
Example Using DataFx
DataFx uses a concept named Flow to associate views sharing a flow of events (and possibly data) among themselves. By using Flow combined with EventSystem you can define and access methods on other controllers and assign custom event listeners to various events associated with JavaFx Nodes in different controllers sharing a flow.
Here is an example from DataFx samples which represents two separate sender and receiver views with distinct FXML files and controllers:
public class EventSystemDemo extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
HBox box = new HBox();
box.setSpacing(12);
box.setPadding(new Insets(12));
box.setFillHeight(true);
box.setAlignment(Pos.CENTER);
Flow senderFlow = new Flow(ProducerController.class);
box.getChildren().add(senderFlow.start());
Flow receiverFlow = new Flow(ReceiverController.class);
box.getChildren().add(receiverFlow.start());
primaryStage.setScene(new Scene(box));
primaryStage.show();
}
public static void main(String... args) {
launch(args);
}
}
Sender view controller:
#ViewController("producer.fxml")
public class ProducerController {
#FXML
#EventTrigger()
private Button sendButton;
#FXML
private TextField textField;
#EventProducer("test-message")
private String getMessage() {
return textField.getText();
}
}
Receiver view controller:
#ViewController("receiver.fxml")
public class ReceiverController {
#FXML
private Label messageLabel;
#OnEvent("test-message")
private void onNewChatMessage(Event<String> e) {
messageLabel.setText(e.getContent());
}
}
Sender View:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.text.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<VBox alignment="TOP_CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" spacing="12.0" style="-fx-border-color: darkgrey; -fx-border-width: 2;" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Label text="Sender">
<font>
<Font name="System Bold" size="24.0" />
</font>
</Label>
<TextField fx:id="textField" />
<Button fx:id="sendButton" mnemonicParsing="false" text="send" />
</children>
<padding>
<Insets bottom="12.0" left="12.0" right="12.0" top="12.0" />
</padding>
</VBox>
Receiver View:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.text.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<VBox alignment="TOP_CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" spacing="12.0" style="-fx-border-color: darkgrey; -fx-border-width: 2;" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Label text="Receiver">
<font>
<Font name="System Bold" size="24.0" />
</font></Label>
<Label fx:id="messageLabel" />
</children>
<padding>
<Insets bottom="12.0" left="12.0" right="12.0" top="12.0" />
</padding>
</VBox>
For further reading:
http://www.oracle.com/technetwork/java/javafx/community/3rd-party-1844355.html
http://www.guigarage.com/2015/02/quick-overview-datafx-mvc-flow-api/
http://www.guigarage.com/2014/05/datafx-8-0-tutorials/
http://jacpfx.org/2014/11/03/JacpFX_DataFX_a_perfect_match.html
Passing Parameters JavaFX FXML
You can implement your own controller factory so you would be able of creating your own controllers context.
I have used spring with javaFX, and I have implemented my own controllers factory using spring, in that way you can inject one controller in other (you shouldn't need it as those connections should be on the model)
If you want to try spring with Java fx: How to wire multiple fxml controllers using spring dependency-injection?
Definitely you should use an Application Framework that will help you to structure your code.
If you want to try JRebirth, just cut your application in 4 parts:
MainModel composed by:
MainSceneModel
CaptureWindowModel
SettingsModel
You can access to one from anyone by using getModel(Model.class) or send async message using sendWave(Wave).
MainModel can use innerComponent to tightly link child-model to it.
It's pretty simple to use, if you are interested in I can prototype you a sample app just let me know.
The idea is: on a TableView of N columns to have the first M columns always visible even when you use the horizontal scroller.
The only thing near my requirement is this Binding two tableviews together such that they scroll in sync.
The idea to put side by side two tables is not the best by my point of view because
1) The sort of the column is partially indipendent between the two tables: if you use the same observableList the rows are sorted in both tables but it is not possible the sort on multiple columns where at least one column is not on the same table
2) There is no syncronous scroll with the mouse wheel or with the arrows keys
I know that, probably, I can cope with problems like these using EventHandlers and Listeners but I am hoping it is possible to use only one table.
So, the question is: are there any configurable properties on TableView or TableColumns to have the behaviour I am looking for?
I am able to freeze columns in javafx table view. We need to create our custom table column class where in we will have two methods setFixed and isFixed which will be used to make some column as fixed.
Apart from this you need to create your own
TableViewskin
TableHeaderRow - basically in this class you need to override the getRootHeader() method
NestedTableColumnHeader - In this class override layoutChildren() method and add new method to layout the fixedColumns
VirtualFlow
TableView - override createDefaultSkin() , add new booleanProperty showColumnHeaderand one ObservableArrayList for fixedTableColumn
TableRow - Override createDefaultSkin()
TableRowSkinBase - override layoutChildren() method to handle fixed columns.
There is currently no such feature. In fact, even manipulating the scroll bar and responding to scrollbar events are problematic. Two options I can think of:
Option #1 - Multiple Table
Create a new layout which contains the two tables and two scroll bars like in the FXML snippet below:
<BorderPane prefHeight="200.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<bottom>
<ScrollBar fx:id="hScroll" />
</bottom>
<center>
<HBox fx:id="varTable" prefHeight="100.0" prefWidth="200.0">
<children>
<TableView fx:id="fixedTable" prefHeight="200.0" prefWidth="200.0">
<columns>
<TableColumn prefWidth="75.0" text="Column X" />
<TableColumn prefWidth="75.0" text="Column X" />
</columns>
</TableView>
<TableView prefHeight="200.0" prefWidth="200.0" HBox.hgrow="ALWAYS">
<columns>
<TableColumn prefWidth="75.0" text="Column X" />
<TableColumn prefWidth="75.0" text="Column X" />
</columns>
</TableView>
</children>
</HBox>
</center>
<right>
<ScrollBar fx:id="vScroll" orientation="VERTICAL" />
</right>
</BorderPane>
Note the second tabe has HBox.hgrow="ALWAYS" set.
Write a function to locate the scroll bar in a TableView:
private static final ScrollBar getScrollBar(final TableView<?> tableView) {
for (final VirtualFlow virtualFlow: Nodes.filter(Nodes.descendents(tableView, 2), VirtualFlow.class)) {
for (final Node subNode: virtualFlow.getChildrenUnmodifiable()) {
if (subNode instanceof ScrollBar && ((ScrollBar)subNode).getOrientation() == Orientation.VERTICAL) {
return (ScrollBar)subNode;
}
}
}
return null;
}
Use this to locate the two vertical scroll bars and bind their properties (like min, max, value etc.) to your own vertical scroll bar and then hide the original scroll bars. You will also need to set managed=false so that they do not take up space in the layout.
Locate and hide the horizontal scroll bars and bind the properties of the 'moving' table horizontal scroll bar to your own horizontal scroll bar.
We are succesfully using this technique to link two tables with a single scroll bar while waiting for this Jira to be fixed.
Option #2 - Write your own TableViewSkin
Download the JavaFx source to see what they do with the skin and then you can either write a complete custom skin or write a skin that wraps two regular skins and implement in a similar way to Option #1 above
So, the question is: are there any configurable properties on TableView or TableColumns to have the behaviour I am looking for?
Apparently not, but there is a feature request for this behavior on the JavaFX Jira (you'll need to register to view it):
https://javafx-jira.kenai.com/browse/RT-19454
I suggest you vote for it. :^)
JavaFX doesn't have this functionality. Check out ControlsFX's SpreadsheetView.
Code worked with javafx 11
import javafx.application.Platform;
import javafx.scene.AccessibleAttribute;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.TableCell;
import javafx.scene.layout.Region;
public class LockedTableCell<T, S> extends TableCell<T, S> {
{
Platform.runLater(() -> {
try {
ScrollBar scrollBar = (ScrollBar) getTableView()
.queryAccessibleAttribute(AccessibleAttribute.HORIZONTAL_SCROLLBAR);
// set fx:id of TableColumn and get region of column header by #id
Region headerNode = (Region) getTableView().lookup("#" + getTableColumn().getId());
scrollBar.valueProperty().addListener((ob, o, n) -> {
double doubleValue = n.doubleValue();
// move header and cell with translateX & bring it front
headerNode.setTranslateX(doubleValue);
headerNode.toFront();
this.setTranslateX(doubleValue);
this.toFront();
});
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
Using it (with first M columns)
tableColumnInstance1.setCellFactory(param -> new LockedTableCell<>() {...});
tableColumnInstance2.setCellFactory(param -> new LockedTableCell<>() {...});
...
tableColumnInstanceM.setCellFactory(param -> new LockedTableCell<>() {...});