I currently using afterburner.fx to tailor together components of JavaFX based application.
Right now I trying move components in separate fxml files for more comfortable maintenance.
To load such components I using fx:include directive which allow load nested components automatically.
Problem is that with automatic load I loosing possibility get presenter from nested view.
Is there a way to combine automatic load and in same time, be able work with nested components from parent root?
These two seem to work fine together.
Afterburner works by setting a controller factory on the FXML loader, which takes care of instantiating the presenter class and injecting values into it.
The <fx:include> element will propagate the controller factory when loading the included FXML, so you can also inject values into the controller defined in the included FXML. Because afterburner effectively uses a singleton scope for injection, the same instance of injected fields will be used. This means you can readily share your data model between the different presenter classes.
If you want access to the presenter associated with the included FXML, just use the standard technique for "nested controllers".
So, for example:
main.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.cell.PropertyValueFactory?>
<?import javafx.geometry.Insets?>
<BorderPane xmlns:fx="http://javafx.com/fxml" fx:controller="application.MainPresenter">
<center>
<TableView fx:id="table">
<columns>
<TableColumn text="First Name" prefWidth="150">
<cellValueFactory>
<PropertyValueFactory property="firstName" />
</cellValueFactory>
</TableColumn>
<TableColumn text="Last Name" prefWidth="150">
<cellValueFactory>
<PropertyValueFactory property="lastName" />
</cellValueFactory>
</TableColumn>
<TableColumn text="Email" prefWidth="200">
<cellValueFactory>
<PropertyValueFactory property="email" />
</cellValueFactory>
</TableColumn>
</columns>
</TableView>
</center>
<bottom>
<fx:include source="Editor.fxml" fx:id="editor">
<padding>
<Insets top="5" bottom="5" left="5" right="5"/>
</padding>
</fx:include>
</bottom>
</BorderPane>
editor.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.Button?>
<GridPane xmlns:fx="http://javafx.com/fxml" hgap="5" vgap="10" fx:controller="application.EditorPresenter">
<Label GridPane.rowIndex="0" GridPane.columnIndex="0" text="First Name:"/>
<TextField fx:id="firstNameTextField" GridPane.rowIndex="0" GridPane.columnIndex="1"/>
<Label GridPane.rowIndex="1" GridPane.columnIndex="0" text="Last Name"/>
<TextField fx:id="lastNameTextField" GridPane.rowIndex="1" GridPane.columnIndex="1"/>
<Label GridPane.rowIndex="2" GridPane.columnIndex="0" text="Email"/>
<TextField fx:id="emailTextField" GridPane.rowIndex="2" GridPane.columnIndex="1"/>
<HBox GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2">
<Button fx:id="addEditButton" onAction="#addEdit" />
</HBox>
</GridPane>
MainPresenter.java:
package application;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.scene.control.TableView;
public class MainPresenter {
#FXML
private TableView<Person> table ;
// This is the controller (presenter) for the included fxml
// It is injected by the FXMLLoader; the rule is that "Controller" needs to be
// appended to the fx:id attribute of the <fx:include> tag.
// This is not used in this example but is here to demonstrate how to access it
// if needed.
#FXML
private EditorPresenter editorController ;
#Inject
private DataModel dataModel ;
public void initialize() {
table.setItems(dataModel.getPeople());
table.getSelectionModel().selectedItemProperty().addListener(
(obs, oldPerson, newPerson) -> dataModel.setCurrentPerson(newPerson));
dataModel.currentPersonProperty().addListener((obs, oldPerson, newPerson) -> {
if (newPerson == null) {
table.getSelectionModel().clearSelection();
} else {
table.getSelectionModel().select(newPerson);
}
});
dataModel.getPeople().addAll(
new Person("Jacob", "Smith", "jacob.smith#example.com"),
new Person("Isabella", "Johnson", "isabella.johnson#example.com"),
new Person("Ethan", "Williams", "ethan.williams#example.com"),
new Person("Emma", "Jones", "emma.jones#example.com"),
new Person("Michael", "Brown", "michael.brown#example.com")
);
}
}
EditorPresenter.java:
package application;
import javafx.beans.binding.Bindings;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javax.inject.Inject;
public class EditorPresenter {
#FXML
private TextField firstNameTextField ;
#FXML
private TextField lastNameTextField ;
#FXML
private TextField emailTextField ;
#FXML
private Button addEditButton ;
#Inject
private DataModel dataModel ;
public void initialize() {
addEditButton.textProperty().bind(
Bindings.when(Bindings.isNull(dataModel.currentPersonProperty()))
.then("Add")
.otherwise("Update")
);
dataModel.currentPersonProperty().addListener((obs, oldPerson, newPerson) -> {
if (newPerson == null) {
firstNameTextField.setText("");
lastNameTextField.setText("");
emailTextField.setText("");
} else {
firstNameTextField.setText(newPerson.getFirstName());
lastNameTextField.setText(newPerson.getLastName());
emailTextField.setText(newPerson.getEmail());
}
});
}
#FXML
private void addEdit() {
Person person = dataModel.getCurrentPerson();
String firstName = firstNameTextField.getText();
String lastName = lastNameTextField.getText();
String email = emailTextField.getText();
if (person == null) {
dataModel.getPeople().add(new Person(firstName, lastName, email));
} else {
person.setFirstName(firstName);
person.setLastName(lastName);
person.setEmail(email);
}
}
}
MainView.java:
package application;
import com.airhacks.afterburner.views.FXMLView;
public class MainView extends FXMLView {
}
Main.java (application class):
package application;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
import com.airhacks.afterburner.injection.Injector;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
MainView mainView = new MainView();
Scene scene = new Scene(mainView.getView(), 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
#Override
public void stop() throws Exception {
Injector.forgetAll();
}
public static void main(String[] args) {
launch(args);
}
}
DataModel.java:
package application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
public class DataModel {
private final ObservableList<Person> people = FXCollections.observableArrayList();
private final ObjectProperty<Person> currentPerson = new SimpleObjectProperty<>(this, "currentPerson");
public ObservableList<Person> getPeople() {
return people ;
}
public final Person getCurrentPerson() {
return currentPerson.get();
}
public final void setCurrentPerson(Person person) {
this.currentPerson.set(person);
}
public ObjectProperty<Person> currentPersonProperty() {
return currentPerson ;
}
}
And the usual Person.java example:
package application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Person {
private final StringProperty firstName = new SimpleStringProperty(this, "firstName");
public final String getFirstName() {
return firstName.get();
}
public final void setFirstName(String firstName) {
this.firstName.set(firstName);
}
public StringProperty firstNameProperty() {
return firstName ;
}
private final StringProperty lastName = new SimpleStringProperty(this, "lastName");
public final String getLastName() {
return lastName.get();
}
public final void setLastName(String lastName) {
this.lastName.set(lastName);
}
public StringProperty lastNameProperty() {
return lastName ;
}
private final StringProperty email = new SimpleStringProperty(this, "email");
public final String getEmail() {
return email.get();
}
public final void setEmail(String email) {
this.email.set(email);
}
public StringProperty emailProperty() {
return email ;
}
public Person(String firstName, String lastName, String email) {
setFirstName(firstName);
setLastName(lastName);
setEmail(email);
}
}
Related
In my application, I want to apply the behavior of a ToggleGroup to a group of TitledPanes. To do so, I implemented this:
ToggleAdapter.java
package sample;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableMap;
import javafx.scene.control.TitledPane;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
public class ToggleAdapter implements Toggle {
final private TitledPane titledPane;
final private ObjectProperty<ToggleGroup> toggleGroupProperty = new SimpleObjectProperty<>();
private ToggleAdapter(TitledPane titledPane) {
this.titledPane = titledPane;
}
#Override
public ToggleGroup getToggleGroup() {
return toggleGroupProperty.get();
}
#Override
public void setToggleGroup(ToggleGroup toggleGroup) {
toggleGroupProperty.set(toggleGroup);
}
#Override
public ObjectProperty<ToggleGroup> toggleGroupProperty() {
return toggleGroupProperty;
}
#Override
public boolean isSelected() {
return titledPane.isExpanded();
}
#Override
public void setSelected(boolean selected) {
titledPane.setExpanded(selected);
}
#Override
public BooleanProperty selectedProperty() {
return titledPane.expandedProperty();
}
#Override
public Object getUserData() {
return titledPane.getUserData();
}
#Override
public void setUserData(Object value) {
titledPane.setUserData(value);
}
#Override
public ObservableMap<Object, Object> getProperties() {
return FXCollections.emptyObservableMap();
}
public static Toggle asToggle(final TitledPane titledPane) {
return new ToggleAdapter(titledPane);
}
}
sample.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.TitledPane?>
<?import javafx.scene.layout.VBox?>
<VBox spacing="7.0" xmlns="http://javafx.com/javafx/15.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
<children>
<TitledPane fx:id="titledPane1" text="Title 1">
<content>
<TextArea minHeight="-Infinity" prefHeight="125.0" prefRowCount="1" wrapText="true" />
</content>
</TitledPane>
<TextField />
<TitledPane fx:id="titledPane2" expanded="false" text="Title 2">
<content>
<TextArea minHeight="-Infinity" prefHeight="125.0" prefRowCount="1" wrapText="true" />
</content>
</TitledPane>
<TitledPane fx:id="titledPane3" expanded="false" text="Title 3">
<content>
<TextArea minHeight="-Infinity" prefHeight="125.0" prefRowCount="1" wrapText="true" />
</content>
</TitledPane>
</children>
<padding>
<Insets bottom="14.0" left="14.0" right="14.0" top="14.0" />
</padding>
</VBox>
Controller.java
package sample;
import javafx.fxml.FXML;
import javafx.scene.control.TitledPane;
import javafx.scene.control.ToggleGroup;
public class Controller {
#FXML private TitledPane titledPane1;
#FXML private TitledPane titledPane2;
#FXML private TitledPane titledPane3;
#FXML
private void initialize() {
final var toggleGroup = new ToggleGroup();
final var toggle1 = ToggleAdapter.asToggle(titledPane1);
toggle1.setToggleGroup(toggleGroup);
final var toggle2 = ToggleAdapter.asToggle(titledPane2);
toggle2.setToggleGroup(toggleGroup);
final var toggle3 = ToggleAdapter.asToggle(titledPane3);
toggle3.setToggleGroup(toggleGroup);
toggleGroup.selectToggle(toggle1);
}
}
Main.java
package sample;
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"));
primaryStage.setTitle("Hello World");
final Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
My naive approach doesn't work, but I have no idea why not. Any ideas?
EDIT: I am aware of Acordion, but this would not be suitable because I cannot place all three TitledPanes in the same parent container.
First, you're reinventing the wheel. Don't do that; there's already a control, Accordion, that implements exactly what you're trying to do here.
All you need is:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Accordion?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.TitledPane?>
<Accordion expandedPane="${titledPane1}" xmlns="http://javafx.com/javafx/15.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
<panes>
<TitledPane fx:id="titledPane1" text="Title 1" expanded="true">
<content>
<TextArea minHeight="-Infinity" prefHeight="125.0" prefRowCount="1" wrapText="true" />
</content>
</TitledPane>
<TitledPane fx:id="titledPane2" expanded="false" text="Title 2">
<content>
<TextArea minHeight="-Infinity" prefHeight="125.0" prefRowCount="1" wrapText="true" />
</content>
</TitledPane>
<TitledPane fx:id="titledPane3" expanded="false" text="Title 3">
<content>
<TextArea minHeight="-Infinity" prefHeight="125.0" prefRowCount="1" wrapText="true" />
</content>
</TitledPane>
</panes>
<padding>
<Insets bottom="14.0" left="14.0" right="14.0" top="14.0" />
</padding>
</Accordion>
and
public class Controller {
#FXML private Accordion accordion ;
#FXML private TitledPane titledPane1;
#FXML private TitledPane titledPane2;
#FXML private TitledPane titledPane3;
#FXML
private void initialize() {
accordion.setExpandedPane(titledPane1);
}
}
The ToggleAdapter is not required.
The reason your code doesn't work is that you're assuming, I think, that the ToggleGroup observes the selected state of each of its toggles and updates the other toggle's state when one is selected. This isn't the case; it's actually the responsibility of the toggle implementation to maintain single selection in its toggle group, if it so desires. You could do this by adding a listener to the selected state in the ToggleAdapter (but again, to emphasize, it's always wrong to reinvent functionality defined in the standard API).
private ToggleAdapter(TitledPane titledPane) {
this.titledPane = titledPane;
selectedProperty().addListener(obs -> {
ToggleGroup tg = getToggleGroup();
if (tg != null) {
if (isSelected()) {
tg.selectToggle(this);
} else if (tg.getSelectedToggle() == this) {
tg.selectToggle(null);
}
}
});
}
Changing the implementation of ToggleAdapter to this actually solves the problem:
package sample;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ObjectPropertyBase;
import javafx.collections.FXCollections;
import javafx.collections.ObservableMap;
import javafx.scene.control.TitledPane;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import java.util.Optional;
public class ToggleAdapter implements Toggle {
final private TitledPane titledPane;
final private ObjectProperty<ToggleGroup> toggleGroupProperty = new ObjectPropertyBase<>() {
private ToggleGroup old;
#Override
protected void invalidated() {
if (old != null) {
old.getToggles().remove(ToggleAdapter.this);
}
old = get();
if (get() != null && get().getToggles().contains(ToggleAdapter.this) == false) {
get().getToggles().add(ToggleAdapter.this);
}
}
#Override
public Object getBean() {
return ToggleAdapter.this;
}
#Override
public String getName() {
return "toggleGroup";
}
};
#Override
public ToggleGroup getToggleGroup() {
return toggleGroupProperty.get();
}
#Override
public void setToggleGroup(ToggleGroup toggleGroup) {
toggleGroupProperty.set(toggleGroup);
}
#Override
public ObjectProperty<ToggleGroup> toggleGroupProperty() {
return toggleGroupProperty;
}
#Override
public boolean isSelected() {
return titledPane.isExpanded();
}
#Override
public void setSelected(boolean selected) {
titledPane.setExpanded(selected);
}
#Override
public BooleanProperty selectedProperty() {
return titledPane.expandedProperty();
}
#Override
public Object getUserData() {
return titledPane.getUserData();
}
#Override
public void setUserData(Object value) {
titledPane.setUserData(value);
}
#Override
public ObservableMap<Object, Object> getProperties() {
return FXCollections.emptyObservableMap();
}
public static Toggle asToggle(final TitledPane titledPane) {
return new ToggleAdapter(titledPane);
}
public ToggleAdapter(TitledPane titledPane) {
this.titledPane = titledPane;
selectedProperty().addListener(obs -> {
Optional.ofNullable(getToggleGroup()).ifPresent(toggleGroup -> {
if (isSelected()) {
toggleGroup.selectToggle(this);
} else if (toggleGroup.getSelectedToggle() == this) {
toggleGroup.selectToggle(null);
}
});
});
}
}
Thank you, +James_D for the idea on how to change the implementation.
I am learning JavaFX FXML. In order to add data to TableView I add controller to FXMLLoader in a POJO class.
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/views/my_view.fxml"));
fxmlLoader.setController(controller);
try {
VBox myView = fxmlLoader.load();
controller.getInboxTable().setItems(getMyDisplayedItems());
//...
Root of FXML file has this definition:
<VBox fx:id="rootVBox" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
Thus, I can't specify controller in FXML file.
When I define Buttons in the same class I can't specify onMouseClicked because "No controller specified for top level element".
So, I can either populate TableView with data or attach action handler, but not both. What is the correct way to attach SortedList to TableView AND specify onAction in FXML?
If you want to use FXML, try to manage all the View layer's code there. You can add the controller in the FXML file via the fx:controller tag. Have a look at the Creating an Address Book with FXML at Oracle Docs. Most of the code below is from that tutorial.
Another thing is that using such a way of assigning the controller should fix the "No controller specified for top level element" issue. I added a button which causes the Table View's data is shuffled.
So, assuming you have all the files in the folder called sample in your project folder:
Main.java:
package sample;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
public class Main extends Application {
#Override
public void start(final Stage stage) {
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setLocation(Main.class.getResource("/sample/sample.fxml"));
try {
Parent root;
root = fxmlLoader.load();
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Application.launch(args);
}
}
sample.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.control.cell.PropertyValueFactory?>
<VBox fx:id="rootVBox"
xmlns="http://javafx.com/javafx/8"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="sample.Controller">
<Button text="Shuffle Data" onAction="#shuffleDataButtonClicked"/>
<TableView fx:id="tableView">
<columns>
<TableColumn text="First Name">
<cellValueFactory><PropertyValueFactory property="firstName" />
</cellValueFactory>
</TableColumn>
<TableColumn text="Last Name">
<cellValueFactory><PropertyValueFactory property="lastName" />
</cellValueFactory>
</TableColumn>
<TableColumn text="Email Address">
<cellValueFactory><PropertyValueFactory property="email" />
</cellValueFactory>
</TableColumn>
</columns>
</TableView>
</VBox>
Controller.java:
package sample;
import javafx.fxml.FXML;
import javafx.scene.control.TableView;
import java.util.Collections;
import java.util.Vector;
public class Controller {
#FXML
private TableView<Person> tableView;
#FXML
private void initialize() {
tableView.getItems().addAll(getSomePersonData());
}
private Vector<Person> getSomePersonData() {
Person jacobSmith = new Person("Jacob", "Smith", "jacob.smith#example.com");
Person isabellaJohnson = new Person("Isabella", "Johnson", "isabella.johnson#example.com");
Person ethanWilliams = new Person("Ethan", "Williams", "ethan.williams#example.com");
Person emmaJones = new Person("Emma", "Jones", "emma.jones#example.com");
Person michaelBrown = new Person("Michael", "Brown", "michael.brown#example.com");
Vector<Person> people = new Vector<>();
people.add(jacobSmith);
people.add(isabellaJohnson);
people.add(ethanWilliams);
people.add(emmaJones);
people.add(michaelBrown);
return people;
}
#FXML
private void shuffleDataButtonClicked() {
Collections.shuffle(tableView.getItems());
}
}
Person.java
package sample;
import javafx.beans.property.SimpleStringProperty;
public class Person {
private final SimpleStringProperty firstName = new SimpleStringProperty("");
private final SimpleStringProperty lastName = new SimpleStringProperty("");
private final SimpleStringProperty email = new SimpleStringProperty("");
public Person() {
this("", "", "");
}
public Person(String firstName, String lastName, String email) {
setFirstName(firstName);
setLastName(lastName);
setEmail(email);
}
public String getFirstName() {
return firstName.get();
}
public void setFirstName(String fName) {
firstName.set(fName);
}
public String getLastName() {
return lastName.get();
}
public void setLastName(String fName) {
lastName.set(fName);
}
public String getEmail() {
return email.get();
}
public void setEmail(String fName) {
email.set(fName);
}
}
I have a very strange problem with my test application. I need to fill the JavaFX TableView element with some data. Here is the code:
fxmldocumentController.java
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.TableView; //A
import javafx.scene.control.TableColumn; //B
import javafx.scene.control.cell.PropertyValueFactory; //C
public class fxmldocumentController implements Initializable
{
#FXML
private TableView<employees> mainTableView;
#FXML
private TableColumn<employees, Integer> age;
#FXML
private TableColumn<employees, String> userName, companyName;
#Override
public void initialize(URL url, ResourceBundle rb)
{
// TODO:
mainTableView.getItems().
add(new employees("Yuri P. Bodrov", "VMware", 35));
mainTableView.getItems().
add(new employees("Ivan Y. Bodrov", "VMware", 5));
mainTableView.getItems().
add(new employees("Peter Y. Bodrov", "VMware", 2));
// A problem starts here:
age.setCellValueFactory(new PropertyValueFactory<>("age"));
userName.setCellValueFactory(new PropertyValueFactory<>("userName"));
companyName.
setCellValueFactory(new PropertyValueFactory<>("companyName"));
}
}
fxmldocument.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.text.*?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane id="AnchorPane" prefHeight="300.0" prefWidth="400.0" style="-fx-
background-color: white;"
xmlns="http://javafx.com/javafx/8"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="sampletableviewapp00.fxmldocumentController">
<children>
<Label fx:id="testLabel" layoutX="14.0" layoutY="14.0" style="-fx-
background-color: white;" text="Employees. TableView." textFill="#505050">
<font>
<Font size="14.0" />
</font>
</Label>
<TableView fx:id="mainTableView" layoutX="12.0" layoutY="50.0"
prefHeight="200.0" prefWidth="377.0">
<columns>
<TableColumn prefWidth="90.0" text="UserName" />
<TableColumn prefWidth="119.0" text="CompanyName" />
<TableColumn prefWidth="84.0" text="Age" />
</columns>
</TableView>
</children>
</AnchorPane>
Sampletableviewapp00.java
package sampletableviewapp00;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Sampletableviewapp00 extends Application
{
#Override
public void start(Stage stage) throws Exception
{
Parent root = FXMLLoader.load(getClass().
getClassLoader().getResource("fxmldocument.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args)
{
launch(args);
}
}
employees.java
package sampletableviewapp00;
public class employees
{
String userName, companyName;
int age;
// Generate Properties. Getters:
public int GetAge()
{
return age;
}
public String GetUserName()
{
return userName;
}
public String GetCompanyName()
{
return companyName;
}
// Generate Properties. Setters:
public void SetAge(int age)
{
this.age = age;
}
public void SetUserName(String userName)
{
this.userName = userName;
}
public void SetCompanyName(String companyName)
{
this.companyName = companyName;
}
// Generate Constructor of Employees class:
public employees(String userName, String companyName, int age)
{
this.userName = userName;
this.companyName = companyName;
this.age = age;
}
}
When I run this application the NetBeans IDE 8.2 returns this stack of exceptions/errors: see outputError.png as attachment
outputError.png
outputError02.PNG
Dear colleagues! Do you have any ideas to resolve this problem? Could you try to write this code by yourself and run? Thanks in advance! :-)
You have fxmldocument.xml but tried to load "fxmldocument.fxml".
Rename the file to have fxml extension.
Also make sure you put the fxml file under /resources/yourpackagepath/ folder and load as:
Sampletableviewapp00.class.getResource("fxmldocument.fxml")
This line is triggering the exception:
Parent root = FXMLLoader.load(getClass().
getClassLoader().getResource("fxmldocument.fxml"));
Your fxml document's extension is xml in your project and you are trying to load it as .fxml in the above line.
Rename fxmldocument.xml to fxmldocument.fxml.
What i need:
Need an editable combobox which filters the data on the popup upon typing and first matching item should be highlighted and should set as the text in the combo upon pressing enter.
The popup should be a tableview with 2 or 3 columns. (screen shot attached.)(in the image it is a textfield but i prefer combo so that if the user is not sure about the values, he can click the combo button and see the entire list and select one.)
I will be binding some data as a source to the tableview which should act as the popup for the combobox.
I know i should go for a custom control but dont know where to start?
Reference URL: enter link description here
Thanks in Advance.
What i have tried so far:
Guys,
With the idea you guys gave, I have tried this so far and able to achieve.
(For now, i am not showing the tableview dynamically below the text field (eventually dats wat i want))
1. A tableview with static data is already loaded and added to the scene.
2. Having a text field below the tableview. (this is named as txt)
3. Having another text field below the first text field(this is named as txt1) (when i press tab from the previous text field cursor should come here)
i have a predicate set for the table and i am updating my predicate as the user types in the txt. (working)
Able to filter the table and the first matching row will highlighted. (working)
When the user press either "Tab" or "Enter" the highlighted row in the tableview should be set as the value in the txt.(working)
Needed:
1. When i press "Tab" or "Enter", the highlighted row will be set as the value in the text field (txt) and also the cursor should move to next focusable node. in my case it is the 2nd text field (txt1). I dont want to say txt1.requestFocus() bcoz in realtime, i have many text fields in the scene and this control will be a user control. so cant hardcode anything.
When the user types some text in the text field(not full text. eg: "do" where my table contains "Dom", "Don"), if there are multiple matches, currently both the records will be displayed in the table but the first one will be highlighted. User should be able to select the 2nd row if he wants by pressing the down arrow from the text field itself.
Main.java
package application;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
VBox root = FXMLLoader.load(this.getClass().getResource("MainView.fxml"));
Scene scene = new Scene(root,500,300);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
MainController.java
package application;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Parent;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.stage.Popup;
public class MainController implements Initializable
{
private #FXML TableView<Person> table;
private #FXML TableColumn<Person, String> firstNameCol;
private #FXML TableColumn<Person, String> lastNameCol;
private #FXML TableColumn<Person, String> emailCol;
private #FXML TableColumn<Person, Integer> ageCol;
private #FXML TextField txt;
private #FXML TextField txt1;
private #FXML Button btn;
#Override
public void initialize(URL location, ResourceBundle resources)
{
Platform.runLater(new Runnable() {
#Override
public void run() {
txt.requestFocus();
}
});
ObservableList<Person> obsList =FXCollections.observableArrayList();
obsList.add(new Person("Sam", "P1LasttName", "P1Email#gmail.com", 20));
obsList.add(new Person("Dom", "P2LasttName", "P2Email#gmail.com", 30));
obsList.add(new Person("Ken", "P3LasttName", "P3Email#gmail.com", 40));
obsList.add(new Person("Don", "P4LasttName", "P4Email#gmail.com", 50));
obsList.add(new Person("Tom", "P5LasttName", "P5Email#gmail.com", 60));
FilteredList<Person> filteredList = new FilteredList<>(obsList, p->true);
table.setItems(filteredList);
txt.textProperty().addListener((obs, oldValue, newValue) ->{
filteredList.setPredicate(person-> {
if(newValue == null || newValue.isEmpty())
return true;
if(person.getFirstName().trim().toLowerCase().contains(newValue.toLowerCase()))
return true;
return false;
});
Platform.runLater(new Runnable() {
#Override
public void run()
{
// we don't want repeated selections
table.getSelectionModel().clearSelection();
//get the focus
table.requestFocus();
//select first item in TableView model
table.getSelectionModel().selectFirst();
//set the focus on the first element
table.getFocusModel().focus(0);
//render the selected item in the TableView
//tableClickHandler(null);
}
});
Platform.runLater(new Runnable() {
#Override
public void run()
{
txt.requestFocus();
txt.end();
}
});
});
table.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event)
{
if(event.getCode() == KeyCode.ENTER)
{
txt.setText(table.getSelectionModel().getSelectedItem().getFirstName());
}
}
});
txt.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event)
{
if(event.getCode() == KeyCode.ENTER || event.getCode() == KeyCode.TAB)
//if(event.getCode() == KeyCode.ENTER)
{
txt.setText(table.getSelectionModel().getSelectedItem().getFirstName());
/*Platform.runLater(new Runnable() {
public void run() {
txt1.requestFocus();
}
});*/
}
}
});
/*txt.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event)
{
if(event.getCode() == KeyCode.TAB)
{
//txt.setText(table.getSelectionModel().getSelectedItem().getFirstName());
if(txt.getSkin() instanceof BehaviorSkinBase)
{
//((BehaviorSkinBase)txt.getSkin()).getBehavior().traverseNext();
BehaviorBase x = ((BehaviorSkinBase)txt.getSkin()).getBehavior();
((TextFieldBehavior)x).callAction("TraverseNext");
}
event.consume();
}
}
});*/
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event)
{
/*
Popup popup = new Popup();
popup.getContent().add(new TableView());
//popup.show(txt, txt.localToScreen(0, 0).getX() + txt.getWidth()/2, txt.localToScreen(0, 0).getY() + txt.getHeight());
popup.show(txt, txt.localToScreen(0, 0).getX(), txt.localToScreen(0, 0).getY() + txt.getHeight() + 2);
*/
Parent vbox = null;
try {
vbox = FXMLLoader.load(this.getClass().getResource("TableView.fxml"));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Popup popup = new Popup();
popup.getContent().add(vbox);
//popup.show(txt, txt.localToScreen(0, 0).getX() + txt.getWidth()/2, txt.localToScreen(0, 0).getY() + txt.getHeight());
//popup.show(txt, txt.localToScreen(0, 0).getX(), txt.localToScreen(0, 0).getY() + txt.getHeight() + 2);
popup.show(txt, txt.localToScreen(0, 0).getX(), txt.localToScreen(0, 0).getY() + txt.getHeight() + 2);
}
});
}
}
Person.java
package application;
public class Person
{
private String firstName;
private String lastName;
private String email;
private Integer age;
public Person(){}
public Person(String firstName, String lastName, String email, Integer age)
{
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.age = age;
}
public String getFirstName()
{
return firstName;
}
public void setFirstName(String firstName)
{
this.firstName = firstName;
}
public String getLastName()
{
return lastName;
}
public void setLastName(String lastName)
{
this.lastName = lastName;
}
public String getEmail()
{
return email;
}
public void setEmail(String email)
{
this.email = email;
}
public Integer getAge()
{
return age;
}
public void setAge(Integer age)
{
this.age = age;
}
}
application.css
.table-row-cell:selected
{
-fx-background-color: lightgreen;
/* the below style will remove the border lines of the selected row */
-fx-table-cell-border-color: transparent;
}
MainView.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.cell.PropertyValueFactory?>
<?import javafx.scene.layout.VBox?>
<!-- <?import application.Person?> -->
<VBox spacing="10.0" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.MainController">
<children>
<TableView fx:id="table" prefHeight="250.0" prefWidth="437.0">
<columns>
<TableColumn fx:id="firstNameCol" prefWidth="120.0" text="First Name">
<cellValueFactory><PropertyValueFactory property="firstName" /></cellValueFactory>
</TableColumn>
<TableColumn fx:id="lastNameCol" prefWidth="120.0" text="Last Name">
<cellValueFactory><PropertyValueFactory property="lastName" /></cellValueFactory>
</TableColumn>
<TableColumn fx:id="emailCol" prefWidth="120.0" text="Email">
<cellValueFactory><PropertyValueFactory property="email" /></cellValueFactory>
</TableColumn>
<TableColumn fx:id="ageCol" prefWidth="75.0" text="Age">
<cellValueFactory><PropertyValueFactory property="age" /></cellValueFactory>
</TableColumn>
</columns>
</TableView>
<Button text="Button" fx:id="btn"/>
<TextField fx:id="txt" promptText="Type to Filter" />
<TextField fx:id="txt1" promptText="Focus should be here when tab is pressed from pervious txt" />
</children>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
</VBox>
TableView.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.cell.PropertyValueFactory?>
<?import javafx.scene.layout.VBox?>
<?import application.Person?>
<?import javafx.collections.*?>
<!--
<VBox xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.65">
<children>
-->
<!-- <TableView xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.65" fx:id="table" prefHeight="160.0" prefWidth="440.0"> -->
<TableView xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.65" fx:id="table" prefHeight="140.0">
<columns>
<TableColumn fx:id="firstNameCol" prefWidth="120.0" text="First Name">
<cellValueFactory><PropertyValueFactory property="firstName" /></cellValueFactory>
</TableColumn>
<TableColumn fx:id="lastNameCol" prefWidth="120.0" text="Last Name">
<cellValueFactory><PropertyValueFactory property="lastName" /></cellValueFactory>
</TableColumn>
<TableColumn fx:id="emailCol" prefWidth="120.0" text="Email">
<cellValueFactory><PropertyValueFactory property="email" /></cellValueFactory>
</TableColumn>
<TableColumn fx:id="ageCol" prefWidth="75.0" text="Age">
<cellValueFactory><PropertyValueFactory property="age" /></cellValueFactory>
</TableColumn>
</columns>
<columnResizePolicy><TableView fx:constant="CONSTRAINED_RESIZE_POLICY" /></columnResizePolicy>
<items>
<FXCollections fx:factory="observableArrayList">
<Person firstName="P1FirstName" lastName="P1LasttName" email="P1Email#gmail.com" age="20"/>
<Person firstName="P2FirstName" lastName="P2LasttName" email="P2Email#gmail.com" age="30"/>
<Person firstName="P3FirstName" lastName="P3LasttName" email="P3Email#gmail.com" age="40"/>
<Person firstName="P4FirstName" lastName="P4LasttName" email="P4Email#gmail.com" age="50"/>
<Person firstName="P5FirstName" lastName="P5LasttName" email="P5Email#gmail.com" age="60"/>
</FXCollections>
</items>
</TableView>
<!--
</children>
</VBox>
-->
Any help is appreciated. Thanks!
I'm currently using an application that allows the user to search through given files for data they might look for, and everything works swimmingly.
The next implementation is to allow the user to populate their own data and save it to a separate file.
Since the view-area of the searched data is identical to how I would make a form for writing new data, I figured that I would just re-use the fields.
However, I have a TableView as one of the items, and I'm not certain how you extract the information from the table.
I was presuming that since the TableView is populated with a custom Row class that I could retrieve the set of Rows that make up the table and deconstruct them that way, but I cannot ascertain how this would best be done.
Concurrently, I'm trying to learn how to override elements of the TableView's cell factories to make the cells editable (because being editable doesn't do anything by default?), so that might be a place to consolidate strategy.
You just need to call getItems() on the table and iterate through the returned list. Assuming everything is set up in the normal "JavaFX way", any properties in the lists elements will be automatically updated by the editing mechanism.
Here is a complete, FXML-based example.
EditableTableExample.fxml (in package application):
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.cell.TextFieldTableCell?>
<?import javafx.scene.layout.HBox?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<BorderPane xmlns:fx="http://javafx.com/fxml/1"
fx:controller="application.EditableTableViewController">
<center>
<TableView fx:id="table" editable="true">
<columns>
<TableColumn text="First Name" prefWidth="150"
fx:id="firstNameColumn">
<cellFactory>
<TextFieldTableCell fx:factory="forTableColumn" />
</cellFactory>
</TableColumn>
<TableColumn text="Last Name" prefWidth="150" fx:id="lastNameColumn">
<cellFactory>
<TextFieldTableCell fx:factory="forTableColumn" />
</cellFactory>
</TableColumn>
<TableColumn text="Email" prefWidth="150" fx:id="emailColumn">
<cellFactory>
<TextFieldTableCell fx:factory="forTableColumn" />
</cellFactory>
</TableColumn>
</columns>
</TableView>
</center>
<bottom>
<HBox alignment="CENTER">
<padding>
<Insets top="10" right="10" left="10" bottom="10" />
</padding>
<children>
<Button onAction="#showData" text="Show Data" />
</children>
</HBox>
</bottom>
</BorderPane>
EditableTableController:
package application;
import javafx.fxml.FXML;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
public class EditableTableViewController {
#FXML
private TableView<Person> table ;
#FXML
private TableColumn<Person, String> firstNameColumn ;
#FXML
private TableColumn<Person, String> lastNameColumn ;
#FXML
private TableColumn<Person, String> emailColumn ;
public void initialize() {
firstNameColumn.setCellValueFactory(cellData -> cellData.getValue().firstNameProperty());
lastNameColumn.setCellValueFactory(cellData -> cellData.getValue().lastNameProperty());
emailColumn.setCellValueFactory(cellData -> cellData.getValue().emailProperty());
table.getItems().addAll(
new Person("Jacob", "Smith", "jacob.smith#example.com"),
new Person("Isabella", "Johnson", "isabella.johnson#example.com"),
new Person("Ethan", "Williams", "ethan.williams#example.com"),
new Person("Emma", "Jones", "emma.jones#example.com"),
new Person("Michael", "Brown", "michael.brown#example.com")
) ;
}
#FXML
private void showData() {
for (Person person : table.getItems()) {
String formatted = String.format("%s %s (%s)", person.getFirstName(), person.getLastName(), person.getEmail());
System.out.println(formatted);
}
System.out.println();
}
}
Model class Person:
package application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Person {
private final StringProperty firstName = new SimpleStringProperty(this, "firstName");
public final String getFirstName() {
return firstNameProperty().get();
}
public final void setFirstName(String firstName) {
firstNameProperty().set(firstName);
}
public StringProperty firstNameProperty() {
return firstName ;
}
private final StringProperty lastName = new SimpleStringProperty(this, "lastName");
public final String getLastName() {
return lastNameProperty().get();
}
public final void setLastName(String lastName) {
lastNameProperty().set(lastName);
}
public StringProperty lastNameProperty() {
return lastName ;
}
private final StringProperty email = new SimpleStringProperty(this, "email");
public final String getEmail() {
return emailProperty().get();
}
public final void setEmail(String email) {
emailProperty().set(email);
}
public StringProperty emailProperty() {
return email ;
}
public Person(String firstName, String lastName, String email) {
this.setFirstName(firstName);
this.setLastName(lastName);
this.setEmail(email);
}
}
Application class:
package application;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class EditableTableViewExample extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("EditableTableExample.fxml"));
BorderPane root = loader.load();
Scene scene = new Scene(root, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The button is just for demonstration, but pressing it will invoke the showData() method in the controller, which iterates through the table's list of items and retrieves the property values from each one. If you double-click a cell to edit, type to change the value, and then commit the edit with Enter, then pressing the button will show the updated values.