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);
}
}
Related
In this code, I'm trying to read data from the bin file and set it in the table view column. But I can't set the loop properly in loadTableFromFileButtonOnClick.It is only showing the latest value from the bin file. But I want to load all the binding data from the bin file Here is my controller class code.
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.URL;
import java.time.LocalDate;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.DatePicker;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.MouseEvent;
public class FXMLMainSceneController implements Initializable {
#FXML private TextField idTxt;
#FXML private TextField nameTxt;
#FXML private TextField deptTxt;
#FXML private TextField cgpaTxt;
#FXML private DatePicker birthdayDatePicker;
#FXML private TableView<Student> tableView;
#FXML private TableColumn<Student, String> idColumn;
#FXML private TableColumn<Student, String> nameColumn;
#FXML private TableColumn<Student, LocalDate> birthdayColumn;
#FXML private TableColumn<Student, String> deptColumn;
#FXML private TableColumn<Student, String> cgpaColumn;
#Override
public void initialize(URL url, ResourceBundle rb) {
idColumn.setCellValueFactory(new PropertyValueFactory<Student,String>("id"));
nameColumn.setCellValueFactory(new PropertyValueFactory<Student,String>("name"));
birthdayColumn.setCellValueFactory(new PropertyValueFactory<Student,LocalDate>("birthday"));
deptColumn.setCellValueFactory(new PropertyValueFactory<Student,String>("dept"));
cgpaColumn.setCellValueFactory(new PropertyValueFactory<Student,String>("cgpa"));
}
#FXML
private void saveToFileButtonOnClick(ActionEvent event) {
Student stud = new Student(
Integer.parseInt(idTxt.getText()),
nameTxt.getText(),
birthdayDatePicker.getValue(),
deptTxt.getText(),
Float.parseFloat(cgpaTxt.getText())
);
idTxt.setText(null); nameTxt.setText(null); cgpaTxt.setText(null); deptTxt.setText(null);
stud.display();
try {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Stud.bin"));
oos.writeObject(stud);
oos.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
#FXML
private void loadTableFromFileButtonOnClick(ActionEvent event) {
ObjectInputStream ois=null;
try {
Student s;
//There will be a loop for set up all the data ,i tried bt can't apply it properly
ois = new ObjectInputStream(new FileInputStream("Stud.bin"));
s = (Student) ois.readObject();
s.display();
tableView.getItems().add(s);
} catch (Exception ex) {
try {
if(ois!=null)
ois.close();
}
catch (IOException e) {
e.printStackTrace();
}
ex.printStackTrace();
}
}
#FXML
private void idTxtOnMouseClick(MouseEvent event) {
idTxt.setText(null);
}
#FXML
private void nameTxtOnMouseClick(MouseEvent event) {
nameTxt.setText(null);
}
#FXML
private void cgpaTxtOnMouseClick(MouseEvent event) {
cgpaTxt.setText(null);
}
#FXML
private void deptTxtOnMouseClick(MouseEvent event) {
deptTxt.setText(null);
}
}
Here is my subclass
import java.io.Serializable;
import java.time.LocalDate;
public class Student extends Person implements Serializable{
int id;
String dept;
float cgpa;
public Student(int id, String name, LocalDate birthday, String dept, float cgpa) {
super(name, birthday);
this.id = id;
this.dept = dept;
this.cgpa = cgpa;
}
public void setId(int id) {
this.id = id;
}
public void setDept(String dept) {
this.dept = dept;
}
public void setCgpa(float cgpa) {
this.cgpa = cgpa;
}
public int getId() {
return id;
}
public String getDept() {
return dept;
}
public float getCgpa() {
return cgpa;
}
#Override
public String toString(){
return "Id="+id+", Name="+name+", DoB="+birthday+", Dept="+dept+", Cgpa="+cgpa;
}
public void display(){
System.out.println("Id="+id+", Name="+name+", DoB="+birthday+", Dept="+dept+", Cgpa="+cgpa);
}
}
here is my superclass
import java.io.Serializable;
import java.time.LocalDate;
import javafx.beans.property.SimpleStringProperty;
public class Person implements Serializable{
protected String name;
protected LocalDate birthday;
public Person(String name, LocalDate birthday) {
this.name = name;
this.birthday = birthday;
}
public void setName(String name) {
this.name = name;
}
public void setBirthday(LocalDate birthday) {
this.birthday = birthday;
}
public String getName() {
//return firstName;
return name;
}
public LocalDate getBirthday() {
return birthday;
}
}
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 know questions similar to this have been asked, and on different dates, but I'll put an SSCCE in here and try to ask this simply.
I would like to be able to update the data model, and have any views upon it automatically update, such that any caller updating the model is not aware of whatever views there presently are. This is what I learned/tried so far, and without calling TableView.refresh() it does not update. What am I missing?
main.java:
package application;
import javafx.application.Application;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
public class Main extends Application {
#Override
public void start(Stage stage) {
// data
ObservableList<Crew> data = FXCollections.observableArrayList();
data.addAll(new Crew(1, "A"), new Crew(2, "B"));
// table
TableColumn<Crew, Integer> crewIdCol = new TableColumn<Crew, Integer>("Crew ID");
crewIdCol.setCellValueFactory(new PropertyValueFactory<Crew, Integer>("crewId"));
crewIdCol.setMinWidth(120);
TableColumn<Crew, String> crewNameCol = new TableColumn<Crew, String>("Crew Name");
crewNameCol.setCellValueFactory(new PropertyValueFactory<Crew, String>("crewName"));
crewNameCol.setMinWidth(180);
TableView<Crew> table = new TableView<Crew>(data);
table.getColumns().addAll(crewIdCol, crewNameCol);
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
// button
Button button = new Button(" test ");
button.setOnAction(ae -> {
// test
StringProperty nameProp = data.get(0).crewName();
if(nameProp.get().equals("A")) {
data.get(0).setCrewName("foo");
// table.refresh();
System.out.println("foo");
} else {
data.get(0).setCrewName("A");
// table.refresh();
System.out.println("A");
}
});
VBox box = new VBox(10);
box.setAlignment(Pos.CENTER);;
box.getChildren().addAll(table, button);
Scene scene = new Scene(box);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Crew.java
package application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Crew {
private final IntegerProperty crewId = new SimpleIntegerProperty();
private final StringProperty crewName = new SimpleStringProperty();
Crew(int id, String name) {
crewId.set(id);
crewName.set(name);
}
public IntegerProperty crewId() { return crewId; }
public final int getCrewId() { return crewId.get(); }
public final void setCrewId(int id) { crewId.set(id); }
public StringProperty crewName() { return crewName; }
public final String getCrewName() { return crewName.get(); }
public final void setCrewName(String name) { crewName.set(name); }
}
Your model class Crew has the "wrong" name for the property accessor methods. Without following the recommended method naming scheme, the (somewhat legacy code) PropertyValueFactory will not be able to find the properties, and thus will not be able to observe them for changes:
package application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Crew {
private final IntegerProperty crewId = new SimpleIntegerProperty();
private final StringProperty crewName = new SimpleStringProperty();
Crew(int id, String name) {
crewId.set(id);
crewName.set(name);
}
public IntegerProperty crewIdProperty() { return crewId; }
public final int getCrewId() { return crewId.get(); }
public final void setCrewId(int id) { crewId.set(id); }
public StringProperty crewNameProperty() { return crewName; }
public final String getCrewName() { return crewName.get(); }
public final void setCrewName(String name) { crewName.set(name); }
}
Alternatively, just implement the callback directly:
crewIdCol.setCellValueFactory(cellData -> cellData.getValue().crewIdProperty());
in which case the compiler will ensure that you use an existing method name for the property.
My TableView has a column with a ToggleButton. All the buttons belong to one group, you can only select one button (one row).
But my TableView has a lot of rows and the ToggleGroup seems to work.
That is until I scroll drown.
When I select one ToggleButton and scroll down no other button should be selected but there is always one that is selected per view.
Is this fixable?
Edit: Here is a SSCCE :
MainApp.java :
package p1;
import java.io.IOException;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
public class MainApp extends Application {
private Stage primaryStage;
private AnchorPane rootLayout;
private ObservableList<Person> personData = FXCollections.observableArrayList();
public MainApp(){
for(int i=0;i<40;i++){
personData.add(new Person("person " +i));
}
}
public ObservableList<Person> getPersonData(){
return personData;
}
#Override
public void start(Stage primaryStage) throws Exception {
this.primaryStage = primaryStage;
try{
FXMLLoader loader =new FXMLLoader();
loader.setLocation(MainApp.class.getResource("People.fxml"));
rootLayout = (AnchorPane)loader.load();
Scene scene = new Scene(rootLayout);
primaryStage.setScene(scene);
primaryStage.show();
PeopleController controller = loader.getController();
controller.setMainApp(this);
} catch(IOException e){
e.printStackTrace();
}
}
public Stage getPrimaryStage(){
return primaryStage;
}
public static void main(String[] args){
launch(args);
}}
People.fxml :
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8" fx:controller="p1.PeopleController">
<children>
<TableView fx:id="personTable" layoutX="160.0" layoutY="49.0" prefHeight="351.0" prefWidth="600.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="49.0">
<columns>
<TableColumn fx:id="nameColumn" prefWidth="75.0" text="Name" />
<TableColumn fx:id="previewColumn" prefWidth="75.0" text="Preview"/>
</columns>
</TableView>
</children>
</AnchorPane>
PeopleController.java :
package p1;
import com.sun.prism.impl.Disposer;
import javafx.fxml.FXML;
import javafx.geometry.Pos;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.util.Callback;
public class PeopleController{
#FXML private TableView<Person> personTable;
#FXML private TableColumn<Person, String> nameColumn;
#FXML private TableColumn previewColumn;
private MainApp mainApp;
final ToggleGroup group = new ToggleGroup();
#FXML
public void initialize() {
nameColumn.setCellValueFactory(cellData -> cellData.getValue().NameProperty());
previewColumn.setCellFactory(
new Callback<TableColumn<Disposer.Record, Boolean>, TableCell<Disposer.Record, Boolean>>() {
#Override
public TableCell<Disposer.Record, Boolean> call(TableColumn<Disposer.Record, Boolean> p) {
ButtonCell cell = new ButtonCell(group);
cell.setAlignment(Pos.CENTER);
return cell;
}
});
}
public void setMainApp(MainApp mainApp){
this.mainApp = mainApp;
personTable.setItems(mainApp.getPersonData());
}
public class ButtonCell extends TableCell<Disposer.Record, Boolean> {
final ToggleButton cellButton = new ToggleButton("click");
public ButtonCell(ToggleGroup group){
cellButton.setToggleGroup(group);
}
#Override
protected void updateItem(Boolean t, boolean empty) {
super.updateItem(t, empty);
if(!empty){
setGraphic(cellButton);
}
}}}
Person.java :
package p1;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Person {
private final StringProperty name;
public Person(){
this(null);
}
public Person(String name){
this.name = new SimpleStringProperty(name);
}
public String getName(){
return name.get();
}
public void setName(String name){
this.name.set(name);
}
public StringProperty NameProperty(){
return name;
} }
The reason that the toggle seems to "jump" when scrolling is the re-use of the cells: the selected state sticks to the button, not the item. Consequently, you can't use a toggleGroup (except in the not-so-common and not recommended case that the items in your data implement Toggle) to keep the toggle state. You need to implement the toggle-logic yourself.
One option is a custom SingleSelectionModel and a custom ButtonCell that talks to the model (as do all other collaborators). Unfortunately, FX doesn't have a publicly accessible concrete implementation of the model. As often, the heavy lifting - which in this case is to update itself on modifications to the items - is left to client code (and not done in this example as well ;-)
Something like:
public class ToggleButtonTableExample extends Application {
public static class DataSelectionModel<S> extends SingleSelectionModel<S> {
private ListProperty<S> listProperty;
public DataSelectionModel(Property<ObservableList<S>> items) {
//listProperty = BugPropertyAdapters.listProperty(items);
listProperty = new SimpleListProperty<>();
listProperty.bindBidirectional(items);
ListChangeListener<S> itemsContentObserver = c -> {
itemsChanged(c);
};
listProperty.addListener(itemsContentObserver);
}
protected void itemsChanged(Change<? extends S> c) {
// TODO need to implement update on modificatins to the underlying list
}
#Override
protected S getModelItem(int index) {
if (index < 0 || index >= getItemCount()) return null;
return listProperty.get(index);
}
#Override
protected int getItemCount() {
return listProperty.getSize();
}
}
public static class ButtonCellX<S, T> extends TableCell<S, T> {
private ToggleButton cellButton;
private SingleSelectionModel<S> model;
public ButtonCellX(SingleSelectionModel<S> group) {
this.model = group;
cellButton = new ToggleButton("click");
cellButton.setOnAction(e -> updateToggle());
updateToggle();
setAlignment(Pos.CENTER);
}
protected void updateToggle() {
model.select(cellButton.isSelected()? getIndex() : -1);
}
#Override
protected void updateItem(T t, boolean empty) {
super.updateItem(t, empty);
if (empty) {
setGraphic(null);
} else {
cellButton.setSelected(model.isSelected(getIndex()));
setGraphic(cellButton);
}
}
}
private Parent getContent() {
TableView<Person> table = new TableView<>();
table.setItems(Person.persons());
TableColumn<Person, String> name = new TableColumn<>("Name");
name.setCellValueFactory(new PropertyValueFactory<>("lastName"));
SingleSelectionModel<Person> model = new DataSelectionModel<>(table.itemsProperty());
TableColumn<Person, Boolean> toggle = new TableColumn<>("Preview");
toggle.setCellFactory(c -> new ButtonCellX<Person, Boolean>(model));
toggle.setCellValueFactory(f -> {
Object value = f.getValue();
return Bindings.equal(value, model.selectedItemProperty());
});
table.getColumns().addAll(name, toggle);
Button select = new Button("Select 0");
select.setOnAction(e -> {
model.select(0);
});
VBox content = new VBox(10, table, select);
return content;
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setScene(new Scene(getContent()));
//primaryStage.setTitle(FXUtils.version());
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
#SuppressWarnings("unused")
private static final Logger LOG = Logger
.getLogger(ChoiceBoxTableCellDynamic.class.getName());
}
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.