I have the need to have a selection listener and select method on a pane to be able to monitor and present a highlight when a node is clicked on.
I did the following:
public class PaneWithSelectionListener extends Pane {
private ObjectProperty<Annotation> selectedAnnotation = new SimpleObjectProperty<>();
public PaneWithSelectionListener() {
super();
selectedAnnotation.addListener((obs, oldAnno, newAnno) -> {
if (oldAnno != null) {
oldAnno.setStyle("");
}
if (newAnno != null) {
newAnno.setStyle("-fx-border-color: blue;-fx-border-insets: 5;-fx-border-width: 1;-fx-border-style: dashed;");
}
});
setOnMouseClicked(e->selectAnnotation(null));
}
public void selectAnnotation(Annotation ann){
selectedAnnotation.set(ann);
}
}
And this works great - however I am not able to work with SceneBuilder anymore since my FXML references this PaneWithSelectionListener rather than Pane. I am not sure how to get my custom pane into SceneBuilder. I have looked at other questions and they are all a combination of FXML and Controllers - where this is just a Pane.
Does anyone know of a way to do this, or perhaps swap the Pane for a PaneWithSelectionListener at initialization time?
Thanks
If the issue is just to make your custom class available in SceneBuilder, you can do so with the following steps:
Bundle your custom class (and any supporting classes, such as Annotation) as a jar file
In SceneBuilder, activate the drop-down button next to "Library" in the top of the left pane:
Choose "Import JAR/FXML File..."
Select the Jar file created from step 1
Make sure the class you need access to in SceneBuilder (PaneWithSelectionListener) is checked
Press "Import Component"
PaneWithSelectionListener will now appear in SceneBuilder under "Custom" in the left pane:
You'll notice the drop-down in SceneBuilder has a "Custom Library Folder" option, from which you can open the folder where the jar files are stored. For a quick option, you can just copy jar files to this folder and (after a short delay), the contained classes will appear in the "Custom" list.
I created a CustomCB a combo box which is of type a userObject. In my case I have used <APerson> as userObject. Entire project and the TESTER are here. First one is CustomCB, the components and the JAR file generated out of this code is added to the Library. This can be loaded in SCENE BUILDER. Thereafter it is available for scene design.
Then of course, you have a tester CustomCB2, which also can use a FXML instead of the way I have done.
I actually want the items starting with the text I type in the ComboBox to appear in the list. But I don't know how to do it. Because the FIRST NAME or LAST NAME of the PERSON class can start with 'Pe'. If I find a solution, I will post it here.
This is the Custom Component.
package customCB;
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
*
* #author Hornigold Arthur
*/
public class APerson {
private final StringProperty firstName;
private final StringProperty lastName;
private final IntegerProperty familyID;
private final IntegerProperty personID;
public APerson() {
this(null, null, 0,0);
}
/**
* Constructor with some initial data.
*
* #param familyID
* #param familyName
*/
public APerson (String firstName, String lastName, int familyID, int personID) {
this.firstName = new SimpleStringProperty(firstName);
this.lastName = new SimpleStringProperty(lastName);
this.familyID = new SimpleIntegerProperty(familyID);
this.personID = new SimpleIntegerProperty(personID);
}
public int getFamilyID() {
return familyID.get();
}
public void setFamilyID(int FamilyID) {
this.familyID.set(FamilyID);
}
public IntegerProperty familyIDProperty() {
return familyID;
}
public int getPersonID() {
return personID.get();
}
public void setPersonID(int PersonID) {
this.personID.set(PersonID);
}
public IntegerProperty personIDProperty() {
return personID;
}
public String getFirstName() {
return firstName.get();
}
public void setFirstName(String FirstName) {
this.firstName.set(FirstName);
}
public StringProperty firstNameProperty() {
return firstName;
}
public String getLastName() {
return lastName.get();
}
public void setLastName(String LastName) {
this.lastName.set(LastName);
}
public StringProperty lastNameProperty() {
return lastName;
}
public String toString() {
String name = getFirstName() + " " + getLastName()+ " [" + getFamilyID() +"]";
return name;
}
}
This is the FXML for the Custom Component.
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.layout.VBox?>
<fx:root stylesheets="#application.css" type="javafx.scene.layout.VBox" xmlns="http://javafx.com/javafx/10.0.1" xmlns:fx="http://javafx.com/fxml/1">
<ComboBox fx:id="myCustomCombo" editable="true" onAction="#cbOnAction" prefWidth="300.0" style="-fx-background-color: white;" />
</fx:root>
This is the controller for this FXML but do not mention it in FXML file. It throws error.
package customCB;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import org.controlsfx.control.textfield.TextFields;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListView;
import javafx.scene.layout.VBox;
public class CustomComboController extends VBox{
#FXML
private ResourceBundle resources;
#FXML
private URL location;
#FXML
private ComboBox<APerson> myCustomCombo;
#FXML
void cbOnAction(ActionEvent event) {
}
#FXML
void initialize() {
assert myCustomCombo != null : "fx:id=\"myCustomCombo\" was not injected: check your FXML file 'CustomLvFXML.fxml'.";
}
public CustomComboController() {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("customCombo.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try {
fxmlLoader.load();
} catch (IOException exception) {
throw new RuntimeException(exception);
}
}
public void setCBValues(javafx.collections.ObservableList<APerson> values) {
myCustomCombo.setItems(values);
myCustomCombo.setEditable(true);
TextFields.bindAutoCompletion(myCustomCombo.getEditor(), myCustomCombo.getItems());
}
}
Download controlsfx-8.40.12.jar from WEB and include this in the BUILD PATH as library.
Now create a jar file for this project. "CustomCB.jar".
This JAR file has to be included as custom control in Scene Builder. I use version 10.0.
Now that it is part of Scene builder you can use this component in designing, unless you can do it the way I have done in my TEST CODE. You need to include "CustomCB.jar" as part of the library for building.
This is the code for the tester.
package customCB2;
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
public class Main extends Application {
static private javafx.collections.ObservableList<APerson> fathers = javafx.collections.FXCollections.observableArrayList();
static private javafx.collections.ObservableList<APerson> mothers = javafx.collections.FXCollections.observableArrayList();
#Override
public void start(Stage stage) throws Exception {
CustomComboController customControl2 = new CustomComboController();
CustomComboController customControl3 = new CustomComboController();
loadFathers();
loadMothers();
customControl2.setCBValues(fathers);
customControl3.setCBValues(mothers);
VBox root = new VBox();
root.getChildren().addAll(customControl2, customControl3);
stage.setScene(new Scene(root));
stage.setTitle("Custom Control Combo box");
stage.setWidth(300);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
private void loadFathers() {
fathers.clear();
fathers.add(new APerson("Hornigold","Arthur",1,63));
fathers.add(new APerson("Andrews","Sundareson",2,60));
fathers.add(new APerson("Christopher","Easweradoss",3,57));
fathers.add(new APerson("Arthur","Kennedy",4,55));
}
private void loadMothers() {
mothers.clear();
mothers.add(new APerson("Victoria","Arthur",1,95));
mothers.add(new APerson("Eliza", "Daniel",1,60));
mothers.add(new APerson("Nesammal", "Rivington",2,57));
mothers.add(new APerson("Ratnammal","Andews",1,55));
}
}
It needs lot of improvements. If anyone can improvise, please add it here.
Related
I have been endeavouring to implement a TableView using data generated in the initialize() method of the Controller and a user interface provide by the fxml.
However, the TableView fails to poulate and displays an empty view with the message 'No content in table'.
Consequently I have created a simplified application as follows.
If anybody can suggest what I may have omitted it will be much appreciated.
Controller
package sample;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
public class Controller {
#FXML
private TableView tvNicknames;
#FXML
private TableColumn tcName;
#FXML
private TableColumn tcNickname;
#FXML
public void initialize(){
ObservableList<DataModel> nickList =
FXCollections.observableArrayList(
new DataModel(new SimpleStringProperty("Alan"), new SimpleStringProperty("Pip")),
new DataModel(new SimpleStringProperty("Bertie"), new SimpleStringProperty("Beets")),
new DataModel(new SimpleStringProperty("Charlie"), new SimpleStringProperty("Collie")),
new DataModel(new SimpleStringProperty("Dave"), new SimpleStringProperty("Daffy")),
new DataModel(new SimpleStringProperty("Ernie"), new SimpleStringProperty("Einstein"))
);
//specify cell factories for each TableColumn
tcName.setCellValueFactory(new PropertyValueFactory<>("name"));
tcNickname.setCellValueFactory(new PropertyValueFactory<>("nickname"));
}
}
DataModel
package sample;
import javafx.beans.property.SimpleStringProperty;
public class DataModel {
private SimpleStringProperty name;
private SimpleStringProperty nickname;
public DataModel() {
}
public DataModel(SimpleStringProperty name, SimpleStringProperty nickname) {
this.name = name;
this.nickname = nickname;
}
public String getName() {
return name.get();
}
public SimpleStringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public String getNickname() {
return nickname.get();
}
public SimpleStringProperty nicknameProperty() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname.set(nickname);
}
}
fxml
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.FlowPane?>
<FlowPane fx:controller="sample.Controller"
xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10">
<TableView fx:id="tvNicknames" prefWidth="250" prefHeight="200">
<columns>
<TableColumn fx:id="tcName" text="Name" prefWidth="100"/>
<TableColumn fx:id="tcNickname" text="Nickname" prefWidth="100" />
</columns>
</TableView>
</FlowPane>
You simply forgot to add the list to the TableView. You basically need to add the following line to method initialize in class Controller.
tvNicknames.setItems(nickList);
In your DataModel class, you don't need a default constructor. I removed it. Also, I prefer to send property values to the constructor and instantiate properties in the constructor. Hence I changed the other constructor in class DataModel. I don't know if it's better, it just feels more correct to me. Here is DataModel class with my changes.
package sample;
import javafx.beans.property.SimpleStringProperty;
public class DataModel {
private SimpleStringProperty name;
private SimpleStringProperty nickname;
public DataModel(String name, String nickname) {
this.name = new SimpleStringProperty(this, "name", name);
this.nickname = new SimpleStringProperty(this, "nickname", nickname);
}
public String getName() {
return name.get();
}
public SimpleStringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public String getNickname() {
return nickname.get();
}
public SimpleStringProperty nicknameProperty() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname.set(nickname);
}
}
Here is Controller class with the fix that I described above. Notice also that the creation of the list has changed because of the change to the DataModel constructor. Also I added type parameters to the types for the variables. Again, not mandatory, but I feel that it is more correct.
package sample;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
public class Controller {
#FXML
private TableView<DataModel> tvNicknames;
#FXML
private TableColumn<DataModel, String> tcName;
#FXML
private TableColumn<DataModel, String> tcNickname;
#FXML
public void initialize(){
ObservableList<DataModel> nickList =
FXCollections.observableArrayList(
new DataModel("Alan", "Pip"),
new DataModel("Bertie", "Beets"),
new DataModel("Charlie", "Collie"),
new DataModel("Dave", "Daffy"),
new DataModel("Ernie", "Einstein")
);
tvNicknames.setItems(nickList); // ADDED THIS LINE
//specify cell factories for each TableColumn
tcName.setCellValueFactory(new PropertyValueFactory<>("name"));
tcNickname.setCellValueFactory(new PropertyValueFactory<>("nickname"));
}
}
Of-course I created a class that extends javafx.application.Application in order to test the above code. Here is the code of that class.
Note that I named the FXML file nickname.fxml and I did not change anything in it. It is the same as in your question.
package sample;
import java.net.URL;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
public class NickName extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
URL location = getClass().getResource("nickname.fxml");
FXMLLoader loader = new FXMLLoader(location);
FlowPane root = loader.load();
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I have a simple ComboBox that I've used ControlsFX to make auto-completing. This works perfectly except for one annoying flaw: When the user uses the dropdown to select an item with the mouse, the autocomplete window opens up, essentially offering matching suggestions to the user.
The desired behavior is for the selection of a value with the mouse to close the popup altogether.
The code below will demonstrate:
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.StringConverter;
import org.controlsfx.control.textfield.TextFields;
public class AutoCompleteComboBox extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
// Simple Interface
VBox root = new VBox(10);
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(10));
// Create a standard ComboBox
ComboBox<Person> comboBox = new ComboBox<>();
// Sample items
ObservableList<Person> people = FXCollections.observableArrayList();
people.addAll(
new Person("William", 34),
new Person("Ashley", 12),
new Person("Marvin", 63),
new Person("Suresh", 18),
new Person("Jackson", 24)
);
comboBox.setItems(people);
// Add StringConverter
comboBox.setConverter(new StringConverter<Person>() {
#Override
public String toString(Person person) {
return (person == null ? null : person.getName());
}
#Override
public Person fromString(String string) {
for (Person person : comboBox.getItems()) {
if (person.getName().equalsIgnoreCase(string)) {
return person;
}
}
return null;
}
});
// Make the ComboBox autocomplete using ControlsFX
comboBox.setEditable(true);
TextFields.bindAutoCompletion(comboBox.getEditor(), comboBox.getItems());
root.getChildren().add(comboBox);
// Show the stage
primaryStage.setScene(new Scene(root));
primaryStage.setTitle("Sample");
primaryStage.show();
}
}
class Person {
private final String name;
private final int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
#Override
public String toString() {
return name;
}
}
So typing into the editor and selecting a value with the keyboard works fine. But if you click the arrow to open the dropdown and select a value that way, you can see the issue (it essentially forces the user to select a value twice).
How would I go about preventing this behavior?
I would like to ask what is the most elegant way to capture value change of CheckBoxTableCell in my TableView.
My goal is to save new value in DB which my example shows:
printedColumn.setCellValueFactory(f -> f.getValue().getPrintedProperty());
printedColumn.setCellFactory(CheckBoxTableCell.forTableColumn(new Callback<Integer, ObservableValue<Boolean>>() {
#Override
public ObservableValue<Boolean> call(Integer param) {
ProductFx productFx = addProductModel.getProductFxObservableList().get(param);
updateInDatabase(productFx);
return productFx.getPrintedProperty();
}
}));
This works fine, but I don't feel like it's the best way to achieve that. For other columns I follow this way:
#FXML
public void onEditPrice(TableColumn.CellEditEvent<ProductFx, Number> e) {
ProductFx productFx = e.getRowValue();
productFx.setPrice(e.getNewValue().doubleValue());
updateInDatabase(productFx);
}
fxml:
<TableColumn fx:id="priceColumn" onEditCommit="#onEditPrice" prefWidth="75.0" text="%addProductTable.price" />
Is it possible to do it in similar way with #FXML annotated method and fxml configuration? Maybe some other ideas?
I really find it hard to get the idea behind your question. I publish the code how I think it should be done. If it does not meet your requirements please elaborate on what you are exactly trying to achive.
import javafx.application.Application;
import javafx.beans.property.LongProperty;
import javafx.beans.property.SimpleLongProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.stage.Stage;
public class TableViewApp extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
ObservableList<Product> products = FXCollections.observableArrayList(product -> new ObservableValue[] {product.nameProperty()});
products.add(new Product(1l, "Machine1"));
products.add(new Product(2l, "Machine2"));
products.addListener((ListChangeListener<Product>) change -> {
while (change.next()) {
if (change.wasUpdated()) {
for (int i = change.getFrom(); i < change.getTo(); i++) {
updateInDb(change.getList().get(i));
}
}
}
});
TableView<Product> tableView = new TableView<>(products);
tableView.setEditable(true);
TableColumn<Product, Long> idColumn = new TableColumn<>("Id");
idColumn.setCellValueFactory(cellData -> cellData.getValue().idProperty().asObject());
TableColumn<Product, String> nameColumn = new TableColumn<>("Name");
nameColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty());
nameColumn.setCellFactory(TextFieldTableCell.forTableColumn());
nameColumn.setEditable(true);
tableView.getColumns().add(idColumn);
tableView.getColumns().add(nameColumn);
stage.setScene(new Scene(tableView));
stage.show();
}
private void updateInDb(Product product) {
System.out.println("Update " + product + " in db");
}
}
class Product {
private LongProperty id = new SimpleLongProperty();
private StringProperty name = new SimpleStringProperty();
public Product(long id, String name) {
this.id.set(id);
this.name.set(name);
}
public LongProperty idProperty() {
return id;
}
public long getId() {
return id.get();
}
public StringProperty nameProperty() {
return name;
}
public String getName() {
return name.get();
}
#Override
public String toString() {
return "Product[id=" + getId() + ", name=" + getName() + "]";
}
}
I do not understand in detail how you got your code working. I guess it is not working as intended by the design of the API. I can definitely answer if it is possible to do it with a simple FXML attribute of CheckBoxTableCell: No.
In case of CheckBoxTableCell
... it is not necessary that the cell enter its editing state (...). A side-effect of this is that the usual editing callbacks (such as on edit commit) will not be called. If you want to be notified of changes,
it is recommended to directly observe the boolean properties that are
manipulated by the CheckBox.
as stated by the javadoc.
If the class of printedProperty implements ObservableValue<Boolean>(as stated by your code) you should follow the cited doc and add a ChangeListener to it like
printedColumn.setCellValueFactory(f -> f.getValue().getPrintedProperty());
printedColumn.setCellFactory(CheckBoxTableCell.forTableColumn(new Callback<Integer, ObservableValue<Boolean>>() {
#Override
public ObservableValue<Boolean> call(Integer param) {
ProductFx productFx = addProductModel.getProductFxObservableList().get(param);
return productFx.getPrintedProperty();
}
}));
ObservableList<ProductFx> obs = addProductModel.getProductFxObservableList();
obs.addListener(new ListChangeListener<ProductFx>(){
#Override
public void onChanged(Change<? extends ProductFx> c) {
if(c.wasAdded()) {
for (ProductFx s:c.getAddedSubList()) {
s.getPrintedProperty().addListener(new ChangeListener<Boolean>() {
ProductFx localProductFx=s;
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
updateInDatabase(localProductFx);
}
});
}
}
}
});
But this is not elegant at all.
To your approach to solve the problem:
The Callback<Integer, ObservableValue<Boolean>>() you used is called each time when the displayed cell is updated. This happens especially when you are scrolling through a huge list, because TableView only keeps as many Cell instances as necessary to fill its view-port (doc). They are simply updated during scrolling and your code updates the database each time this happens, so you might run into performance problems for large datasets or slow databases.
PS: As far as I understand your code you do not follow the usual naming conventions for properties. This might lead to problems using reflecting classes like PropertyValueFactory.
I have the need to have a selection listener and select method on a pane to be able to monitor and present a highlight when a node is clicked on.
I did the following:
public class PaneWithSelectionListener extends Pane {
private ObjectProperty<Annotation> selectedAnnotation = new SimpleObjectProperty<>();
public PaneWithSelectionListener() {
super();
selectedAnnotation.addListener((obs, oldAnno, newAnno) -> {
if (oldAnno != null) {
oldAnno.setStyle("");
}
if (newAnno != null) {
newAnno.setStyle("-fx-border-color: blue;-fx-border-insets: 5;-fx-border-width: 1;-fx-border-style: dashed;");
}
});
setOnMouseClicked(e->selectAnnotation(null));
}
public void selectAnnotation(Annotation ann){
selectedAnnotation.set(ann);
}
}
And this works great - however I am not able to work with SceneBuilder anymore since my FXML references this PaneWithSelectionListener rather than Pane. I am not sure how to get my custom pane into SceneBuilder. I have looked at other questions and they are all a combination of FXML and Controllers - where this is just a Pane.
Does anyone know of a way to do this, or perhaps swap the Pane for a PaneWithSelectionListener at initialization time?
Thanks
If the issue is just to make your custom class available in SceneBuilder, you can do so with the following steps:
Bundle your custom class (and any supporting classes, such as Annotation) as a jar file
In SceneBuilder, activate the drop-down button next to "Library" in the top of the left pane:
Choose "Import JAR/FXML File..."
Select the Jar file created from step 1
Make sure the class you need access to in SceneBuilder (PaneWithSelectionListener) is checked
Press "Import Component"
PaneWithSelectionListener will now appear in SceneBuilder under "Custom" in the left pane:
You'll notice the drop-down in SceneBuilder has a "Custom Library Folder" option, from which you can open the folder where the jar files are stored. For a quick option, you can just copy jar files to this folder and (after a short delay), the contained classes will appear in the "Custom" list.
I created a CustomCB a combo box which is of type a userObject. In my case I have used <APerson> as userObject. Entire project and the TESTER are here. First one is CustomCB, the components and the JAR file generated out of this code is added to the Library. This can be loaded in SCENE BUILDER. Thereafter it is available for scene design.
Then of course, you have a tester CustomCB2, which also can use a FXML instead of the way I have done.
I actually want the items starting with the text I type in the ComboBox to appear in the list. But I don't know how to do it. Because the FIRST NAME or LAST NAME of the PERSON class can start with 'Pe'. If I find a solution, I will post it here.
This is the Custom Component.
package customCB;
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
*
* #author Hornigold Arthur
*/
public class APerson {
private final StringProperty firstName;
private final StringProperty lastName;
private final IntegerProperty familyID;
private final IntegerProperty personID;
public APerson() {
this(null, null, 0,0);
}
/**
* Constructor with some initial data.
*
* #param familyID
* #param familyName
*/
public APerson (String firstName, String lastName, int familyID, int personID) {
this.firstName = new SimpleStringProperty(firstName);
this.lastName = new SimpleStringProperty(lastName);
this.familyID = new SimpleIntegerProperty(familyID);
this.personID = new SimpleIntegerProperty(personID);
}
public int getFamilyID() {
return familyID.get();
}
public void setFamilyID(int FamilyID) {
this.familyID.set(FamilyID);
}
public IntegerProperty familyIDProperty() {
return familyID;
}
public int getPersonID() {
return personID.get();
}
public void setPersonID(int PersonID) {
this.personID.set(PersonID);
}
public IntegerProperty personIDProperty() {
return personID;
}
public String getFirstName() {
return firstName.get();
}
public void setFirstName(String FirstName) {
this.firstName.set(FirstName);
}
public StringProperty firstNameProperty() {
return firstName;
}
public String getLastName() {
return lastName.get();
}
public void setLastName(String LastName) {
this.lastName.set(LastName);
}
public StringProperty lastNameProperty() {
return lastName;
}
public String toString() {
String name = getFirstName() + " " + getLastName()+ " [" + getFamilyID() +"]";
return name;
}
}
This is the FXML for the Custom Component.
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.layout.VBox?>
<fx:root stylesheets="#application.css" type="javafx.scene.layout.VBox" xmlns="http://javafx.com/javafx/10.0.1" xmlns:fx="http://javafx.com/fxml/1">
<ComboBox fx:id="myCustomCombo" editable="true" onAction="#cbOnAction" prefWidth="300.0" style="-fx-background-color: white;" />
</fx:root>
This is the controller for this FXML but do not mention it in FXML file. It throws error.
package customCB;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import org.controlsfx.control.textfield.TextFields;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListView;
import javafx.scene.layout.VBox;
public class CustomComboController extends VBox{
#FXML
private ResourceBundle resources;
#FXML
private URL location;
#FXML
private ComboBox<APerson> myCustomCombo;
#FXML
void cbOnAction(ActionEvent event) {
}
#FXML
void initialize() {
assert myCustomCombo != null : "fx:id=\"myCustomCombo\" was not injected: check your FXML file 'CustomLvFXML.fxml'.";
}
public CustomComboController() {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("customCombo.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try {
fxmlLoader.load();
} catch (IOException exception) {
throw new RuntimeException(exception);
}
}
public void setCBValues(javafx.collections.ObservableList<APerson> values) {
myCustomCombo.setItems(values);
myCustomCombo.setEditable(true);
TextFields.bindAutoCompletion(myCustomCombo.getEditor(), myCustomCombo.getItems());
}
}
Download controlsfx-8.40.12.jar from WEB and include this in the BUILD PATH as library.
Now create a jar file for this project. "CustomCB.jar".
This JAR file has to be included as custom control in Scene Builder. I use version 10.0.
Now that it is part of Scene builder you can use this component in designing, unless you can do it the way I have done in my TEST CODE. You need to include "CustomCB.jar" as part of the library for building.
This is the code for the tester.
package customCB2;
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
public class Main extends Application {
static private javafx.collections.ObservableList<APerson> fathers = javafx.collections.FXCollections.observableArrayList();
static private javafx.collections.ObservableList<APerson> mothers = javafx.collections.FXCollections.observableArrayList();
#Override
public void start(Stage stage) throws Exception {
CustomComboController customControl2 = new CustomComboController();
CustomComboController customControl3 = new CustomComboController();
loadFathers();
loadMothers();
customControl2.setCBValues(fathers);
customControl3.setCBValues(mothers);
VBox root = new VBox();
root.getChildren().addAll(customControl2, customControl3);
stage.setScene(new Scene(root));
stage.setTitle("Custom Control Combo box");
stage.setWidth(300);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
private void loadFathers() {
fathers.clear();
fathers.add(new APerson("Hornigold","Arthur",1,63));
fathers.add(new APerson("Andrews","Sundareson",2,60));
fathers.add(new APerson("Christopher","Easweradoss",3,57));
fathers.add(new APerson("Arthur","Kennedy",4,55));
}
private void loadMothers() {
mothers.clear();
mothers.add(new APerson("Victoria","Arthur",1,95));
mothers.add(new APerson("Eliza", "Daniel",1,60));
mothers.add(new APerson("Nesammal", "Rivington",2,57));
mothers.add(new APerson("Ratnammal","Andews",1,55));
}
}
It needs lot of improvements. If anyone can improvise, please add it here.
I am fairly new to javafx and have been following a tutorial (http://code.makery.ch/library/javafx-8-tutorial/) where all my code has come from.
I am having an issue with populating a table with an xml file which I believe is because I want to switch scene first and then display the table on this new scene. In the guide they do it on just the first scene that loads, which if I do I can get to work fine, but when I want the table data to be viewable on a different scene, it doesn't seem to work. All I did was change where some of the code was located within the class to try to reflect this as I didn't want it on my first scene but now it won't display.
LibraryApp
package libraryapp;
import java.io.File;
import java.io.IOException;
import java.util.prefs.Preferences;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import libraryapp.model.Book;
import libraryapp.model.BookListWrapper;
import libraryapp.view.HomeOverviewController;
import libraryapp.view.RootLayoutController;
public class LibraryApp extends Application {
private Stage primaryStage;
private BorderPane rootLayout;
/**
* The data as an observable list of Books.
*/
private ObservableList<Book> bookData = FXCollections.observableArrayList();
/**
* Constructor
*/
public LibraryApp() {
// Add some sample data
bookData.add(new Book("Hans", "Muster"));
bookData.add(new Book("Ruth", "Mueller"));
bookData.add(new Book("Heinz", "Kurz"));
bookData.add(new Book("Cornelia", "Meier"));
bookData.add(new Book("Werner", "Meyer"));
bookData.add(new Book("Lydia", "Kunz"));
bookData.add(new Book("Anna", "Best"));
bookData.add(new Book("Stefan", "Meier"));
bookData.add(new Book("Martin", "Mueller"));
}
/**
* Returns the data as an observable list of Books.
* #return
*/
public ObservableList<Book> getBookData() {
return bookData;
}
#Override
public void start(Stage primaryStage) {
this.primaryStage = primaryStage;
this.primaryStage.setTitle("LibraryApp");
initRootLayout();
showHomeOverview();
}
/**
* Initializes the root layout and tries to load the last opened
* person file.
*/
public void initRootLayout() {
try {
// Load root layout from fxml file.
FXMLLoader loader = new FXMLLoader();
loader.setLocation(LibraryApp.class.getResource("view/RootLayout.fxml"));
rootLayout = (BorderPane) loader.load();
// Show the scene containing the root layout.
Scene scene = new Scene(rootLayout);
primaryStage.setScene(scene);
// Give the controller access to the main app.
RootLayoutController controller = loader.getController();
controller.setLibraryApp(this);
primaryStage.show();
} catch (IOException e) {
e.printStackTrace();
}
// Try to load last opened person file.
File file = getBookFilePath();
if (file != null) {
loadBookDataFromFile(file);
}
}
/**
* Shows the book overview inside the root layout.
*/
public void showHomeOverview() {
try {
// Load home overview.
FXMLLoader loader = new FXMLLoader();
loader.setLocation(LibraryApp.class.getResource("view/HomeOverview.fxml"));
AnchorPane homeOverview = (AnchorPane) loader.load();
// Set home overview into the center of root layout.
rootLayout.setCenter(homeOverview);
// Give the controller access to the main app.
HomeOverviewController controller = loader.getController();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Returns the main stage.
* #return
*/
public Stage getPrimaryStage() {
return primaryStage;
}
public static void main(String[] args) {
launch(args);
}
/**
* Returns the book file preference, i.e. the file that was last opened.
* The preference is read from the OS specific registry. If no such
* preference can be found, null is returned.
*
* #return
*/
public File getBookFilePath() {
Preferences prefs = Preferences.userNodeForPackage(LibraryApp.class);
String filePath = prefs.get("filePath", null);
if (filePath != null) {
return new File(filePath);
} else {
return null;
}
}
/**
* Sets the file path of the currently loaded file. The path is persisted in
* the OS specific registry.
*
* #param file the file or null to remove the path
*/
public void setBookFilePath(File file) {
Preferences prefs = Preferences.userNodeForPackage(LibraryApp.class);
if (file != null) {
prefs.put("filePath", file.getPath());
// Update the stage title.
primaryStage.setTitle("LibraryApp - " + file.getName());
} else {
prefs.remove("filePath");
// Update the stage title.
primaryStage.setTitle("LibraryApp");
}
}
/**
* Loads book data from the specified file. The current book data will
* be replaced.
*
* #param file
*/
public void loadBookDataFromFile(File file) {
try {
JAXBContext context = JAXBContext
.newInstance(BookListWrapper.class);
Unmarshaller um = context.createUnmarshaller();
// Reading XML from the file and unmarshalling.
BookListWrapper wrapper = (BookListWrapper) um.unmarshal(file);
bookData.clear();
bookData.addAll(wrapper.getBooks());
// Save the file path to the registry.
setBookFilePath(file);
} catch (Exception e) { // catches ANY exception
Alert alert = new Alert(AlertType.ERROR);
alert.setTitle("Error");
alert.setHeaderText("Could not load data");
alert.setContentText("Could not load data from file:\n" + file.getPath());
alert.showAndWait();
}
}
/**
* Saves the current book data to the specified file.
*
* #param file
*/
public void saveBookDataToFile(File file) {
try {
JAXBContext context = JAXBContext
.newInstance(BookListWrapper.class);
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
// Wrapping our book data.
BookListWrapper wrapper = new BookListWrapper();
wrapper.setBooks(bookData);
// Marshalling and saving XML to the file.
m.marshal(wrapper, file);
// Save the file path to the registry.
setBookFilePath(file);
} catch (Exception e) { // catches ANY exception
Alert alert = new Alert(AlertType.ERROR);
alert.setTitle("Error");
alert.setHeaderText("Could not save data");
alert.setContentText("Could not save data to file:\n" + file.getPath());
alert.showAndWait();
}
}
}
HomeOverViewController
package libraryapp.view;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.layout.AnchorPane;
import libraryapp.view.BrowseController;
public class HomeOverviewController implements Initializable {
#FXML
private AnchorPane homePane;
#FXML
private void goToBrowse(ActionEvent event) throws IOException {
AnchorPane pane = FXMLLoader.load(getClass().getResource("Browse.fxml"));
homePane.getChildren().setAll(pane);
}
#FXML
private void goToManageAccount(ActionEvent event) throws IOException {
AnchorPane pane = FXMLLoader.load(getClass().getResource("ManageAccount.fxml"));
homePane.getChildren().setAll(pane);
}
#FXML
public void logout(ActionEvent event) throws IOException {
AnchorPane pane = FXMLLoader.load(getClass().getResource("Login.fxml"));
homePane.getChildren().setAll(pane);
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
}
BrowseController
package libraryapp.view;
import java.io.File;
import java.io.IOException;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.AnchorPane;
import libraryapp.LibraryApp;
import libraryapp.model.Book;
public class BrowseController {
#FXML
private TableView<Book> bookTable;
#FXML
private TableColumn<Book, String> titleColumn;
#FXML
private Label titleLabel;
#FXML
private Label authorLabel;
#FXML
private Label isbnLabel;
#FXML
private Label quantityLabel;
#FXML
private AnchorPane browsePane;
// Reference to the main application.
private LibraryApp libraryApp;
/**
* The constructor.
* The constructor is called before the initialize() method.
*/
public BrowseController() {
}
/**
* Initializes the controller class. This method is automatically called
* after the fxml file has been loaded.
*/
#FXML
private void initialize() {
// Initialize the book table
titleColumn.setCellValueFactory(
cellData -> cellData.getValue().titleProperty());
// Clear person details.
showBookDetails(null);
// Listen for selection changes and show the person details when changed.
bookTable.getSelectionModel().selectedItemProperty().addListener(
(observable, oldValue, newValue) -> showBookDetails(newValue));
}
/**
* Is called by the main application to give a reference back to itself.
*
* #param libraryApp
*/
public void setLibraryApp(LibraryApp libraryApp) {
this.libraryApp = libraryApp;
// Add observable list data to the table
bookTable.setItems(libraryApp.getBookData());
}
private void showBookDetails(Book book) {
if (book != null) {
// Fill the labels with info from the book object.
titleLabel.setText(book.getTitle());
authorLabel.setText(book.getAuthor());
isbnLabel.setText(book.getIsbn());
quantityLabel.setText(Integer.toString(book.getQuantity()));
} else {
// Book is null, remove all the text.
titleLabel.setText("");
authorLabel.setText("");
isbnLabel.setText("");
quantityLabel.setText("");
}
}
/**
* Called when the user clicks on the borrow button.
*/
#FXML
private void handleDeleteBook() {
int selectedIndex = bookTable.getSelectionModel().getSelectedIndex();
if (selectedIndex >= 0) {
bookTable.getItems().remove(selectedIndex);
} else {
// Nothing selected.
Alert alert = new Alert(AlertType.WARNING);
alert.initOwner(libraryApp.getPrimaryStage());
alert.setTitle("No Selection");
alert.setHeaderText("No book Selected");
alert.setContentText("Please select a book.");
alert.showAndWait();
}
}
#FXML
public void logout(ActionEvent event) throws IOException {
File bookFile = libraryApp.getBookFilePath();
libraryApp.saveBookDataToFile(bookFile);
AnchorPane pane = FXMLLoader.load(getClass().getResource("Login.fxml"));
browsePane.getChildren().setAll(pane);
}
}
The idea is that I want to press a button which calls goToBrowse which will load the Browse scene and then populate the table there with the data from the xml file. It goes to browse scene fine but does not populate the table.
Please excuse any messy code and any bad naming conventions as I am pretty new to this javafx stuff and have been trying to follow the tutorial that was mentioned before and tweak it to what I thought would be correct.
I believe it is the setLibraryApp that I want to be calling in the BrowseController, but what I have tried doesn't seem to work.