Javafx multiple calls to get method when using setCellValueFactory - javafx

Why does a
tableColumn.setCellValueFactory(new PropertyValueFactory<Object1, Integer>("Number"));
method call the getNumber method multiple times when I only add one row to a tableView?
The number field is in object2 that is used in object1. So here Object1 one provides a getNumber method that gets a number stored in an arraylist in object2.
Here is my getNumber method:
public double getNumber() {
setNumber++;
return sets.get(setNumber).getNumber();
}

The value is fetched when the TableView shows it. When you modify your value when it is retrieved, you are just doing that and you just count the fetches.
Imagine you have a TableView with 10 visible rows and 20 entries in the list. When you scroll down and up, the value is fetched for the rows which newly appear in the visible area.
If you try this:
public class SO extends Application {
public static class Item {
final int v;
Item(int v) { this.v = v; }
public int getV() {
System.out.println(v);
return v;
}
}
#Override
public void start(Stage primaryStage) throws Exception {
StackPane pane = new StackPane();
Scene scene = new Scene(pane, 300, 100);
primaryStage.setScene(scene);
ObservableList<Item> list = FXCollections.observableArrayList();
for (int i=0; i<20; i++) {
Item item = new Item(i);
list.add(item);
}
TableView<Item> tv = new TableView<>(list);
TableColumn<Item, String> col = new TableColumn<>("Column");
col.setCellValueFactory(new PropertyValueFactory<Item,String>("v"));
tv.getColumns().add(col);
pane.getChildren().add(tv);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
When scrolling the TableView to the end, it will print increasing numbers in the console. When scrolling back, it prints decreasing numbers as it displays values from the beginning of the list. Part of the output:
5
6
7
8
9
9
2
2
1
1
0

Related

How to listen to on selected event in choice box javafx

Im using scenebuilder and I have come up with 3 choiceboxes. The second choicebox depends on the input of the first choicebox and the third depends on the 2nd. How can I achieve this?
I've tried this
#FXML
private ChoiceBox course;
course.getSelectionModel().selectedIndexProperty().addListener(
(ObservableValue<? extends Number> ov,
Number old_val, Number new_val) -> {
//some code here
}
);
But this event only occurs if i switch value, the first selection would not trigger this event, which is not what I want.
How can I achieve this, thanks in advance.
You can do something like this where everytime an action is done it will set the values of the next one. Make note of the .getItems().clear(); this will ensure the list is emptied everytime so that you don't have old values in the list. The for loop however is not important only there to add some variety to the text values I added
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
ChoiceBox<String> choiceBoxOne = new ChoiceBox<>();
choiceBoxOne.setPrefWidth(100);
choiceBoxOne.getItems().addAll("Choice1", "Choice2", "Choice3");
ChoiceBox<String> choiceBoxTwo = new ChoiceBox<>();
choiceBoxTwo.setPrefWidth(100);
ChoiceBox<String> choiceBoxThree = new ChoiceBox<>();
choiceBoxThree.setPrefWidth(100);
choiceBoxOne.setOnAction(event -> {
choiceBoxTwo.getItems().clear();
//The above line is important otherwise everytime there is an action it will just keep adding more
if(choiceBoxOne.getValue()!=null) {//This cannot be null but I added because idk what yours will look like
for (int i = 3; i < 6; i++) {
choiceBoxTwo.getItems().add(choiceBoxOne.getValue() + i);
}
}
});
choiceBoxTwo.setOnAction(event -> {
choiceBoxThree.getItems().clear();
//The above line is important otherwise everytime there is an action it will just keep adding more
if(choiceBoxTwo.getValue()!=null) {//This can be null if ChoiceBoxOne is changed
for (int i = 6; i < 9; i++) {
choiceBoxThree.getItems().add(choiceBoxTwo.getValue() + i);
}
}
});
VBox vBox = new VBox();
vBox.setPrefSize(300, 300);
vBox.setAlignment(Pos.TOP_CENTER);
vBox.getChildren().addAll(choiceBoxOne, choiceBoxTwo, choiceBoxThree);
primaryStage.setScene(new Scene(vBox));
primaryStage.show();
}
public static void main(String[] args) { launch(args); }
}

Reloading a comboBox after changing it's values

I am populating a comboBox with items taken from a sql database when initializing the thread.
If anyone's interested:
public void initialize(URL arg0, ResourceBundle arg1) {
if(arr != null || arr.length > 0) {
for(int i = 0; i<arr.length; i++) {
cmBox.getItems().add(arr[i]);
}
}
}
I have a part of my code that adds a new value to the sql table, and I want to re-populate the comboBox when that happens.
if I do:
cmBox.getItems().clear();
arr = sqld.selectAll();
if(arr != null || arr.length > 0) {
for(int i = 0; i<arr.length; i++) {
cmBox.getItems().add(arr[i]);
}
}
It works fine, but I can't see the new changes unless I close and re-open the window that displays the comboBox.
No errors or anything, just looking for creative ways of re-loading a comboBox and have it actually show the updated values without manually closing and re-opening the window.
You don't need a refresh button just run comboBox.setItems(...) when you add "a new value to the sql table" this should update the combo box here is an example
public class Main extends Application {
private int[] data;
private int dataCount = 0;
#Override
public void start(Stage primaryStage) throws Exception{
data = randomizeData(dataCount);
ComboBox comboBox = new ComboBox();
comboBox.setItems(FXCollections.observableArrayList(
Arrays.stream(data).boxed().collect(Collectors.toList())));
Button updateDataButton = new Button("Update values in SQL Table");
updateDataButton.setOnAction(event -> {
//Update your SQL data
updateData();
//Refresh List
comboBox.setItems(FXCollections.observableArrayList(
Arrays.stream(data).boxed().collect(Collectors.toList())));
});
VBox vBox = new VBox();
vBox.setAlignment(Pos.CENTER);
vBox.getChildren().addAll(comboBox, updateDataButton);
Scene scene = new Scene(vBox);
primaryStage.setScene(scene);
primaryStage.show();
}
private int[] randomizeData(int additional){
int[] data = new int[5+additional];
for (int i = 0; i < data.length; i++) {
data[i] = (int) (Math.random()*10);
}
return data;
}
private void updateData(){
data = randomizeData(++dataCount);
}
public static void main(String[] args) { launch(args); }
}

JavaFX retrieve TableCells of selected row

In my JavaFX TableView, I am trying to retrieve TableCells from a selected row to mark them
with custom colors.
Simply changing the colors of the entire row does not work in this case, as I use different color shadings in each cell depending
on the value of each cell
The example below shows two approaches I tried I to solve the problem
1) Use a listener to retrieve cells in the selected row. Printing the row index and content already works
However, I could not find how to retrieve a TableCell from table.getSelectionModel().
2) Try a dirty workaround to add the TableCells to a global data structure in the columnCellFactory.
However, the TableCells do not get added to the tableCells ArrayList for some reason.
To obtain a short example, the imports and the Classes defining the EditingCell (custom TableCell) and CellEditEvent were omitted.
package TableViewColExample;
public class TableViewExample extends Application {
private Callback<TableColumn, TableCell> columnCellFactory ;
final TableView<String[]> table = new TableView<String[]>();
ObservableSet<Integer> selectedRowIndexes = FXCollections.observableSet();
ObservableSet<String> selectedRows = FXCollections.observableSet();
ArrayList<ArrayList<EditingCell>> tableColumns = new ArrayList<ArrayList<EditingCell>>();
#Override
public void start(Stage stage) {
String[][] dat = new String[][]{
{"C1","C2","C3"},{"a","b","c"},{"d","e","f"},{"g","i","h"}};
ObservableList<String []> data = FXCollections.observableArrayList();
data.addAll(Arrays.asList(dat));
data.remove(0);
table.setItems(data);
for (int i = 0; i < dat[0].length; i++) {
TableColumn tc = new TableColumn(dat[0][i]);
final int colNo = i;
tc.setCellValueFactory(new Callback<CellDataFeatures<String[], String>, ObservableValue<String>>() {
public ObservableValue<String> call(CellDataFeatures<String[], String> p) {
return new SimpleStringProperty((p.getValue()[colNo]));
}
});
ArrayList<EditingCell> tableCells = new ArrayList<EditingCell>();
columnCellFactory =
new Callback<TableColumn, TableCell>() {
public TableCell call(TableColumn p) {
EditingCell tcell = new EditingCell();
//For some reason, the EditingCell is never added to the list
tableCells.add(tcell);
return tcell;
}
};
tc.setCellFactory(columnCellFactory);
tableColumns.add(tableCells);
//The printed value here is 0, which means that the Factory does not add the Editing Cell to the List
System.out.println(" Column rows "+tableCells.size());
table.getColumns().add(tc);
}
//Output: TableColumns 3, TableRows 0
System.out.println("TableColumns "+ tableColumns.size() + " Table rows "+tableColumns.get(0).size());
table.setItems(data);
table.getSelectionModel().getSelectedCells().addListener((Change<? extends TablePosition> change) -> {
selectedRows.clear();
table.getSelectionModel().getSelectedCells().stream().map(TablePosition::getRow).f orEach(row -> {
selectedRowIndexes.add(row);
System.out.println(selectedRowIndexes.toString());
});
table.getSelectionModel().getSelectedItems().forEach(row -> {
selectedRows.add(Arrays.toString(row));
System.out.println(selectedRows.toString());
});
});
stage.setScene(new Scene(table));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Javafx combobox not updating dropdown size upon change on realtime?

I am using Javafx v8.0.25-b18.
The problem I occur is that the size of the dynamic combox's dropdown list doesn't change, so if I had initially two items in the dropdown, then the dropdown size will be good for two items, but if I now populate the dynamic combox with three items then I get a small scrollbar inside!?, If I remove an item - I will have a blank space in the combox !?
I want to "reset" the dropdown size each time I put values into it, so it will be the right size each time it gets populated at runtime.
To clarify even more I am adding three images:
1. The first screenshot shows the initial dropdown size of 2
The second screenshot shows the same combox, where now at runtime I am adding 2 values, I EXPECT it to have now a dropdown with the size of 4, but instead the dropdown size stays 2 and only adds an unwanted scrollbar
Last screenshot is when I remove items and only one item remains in the combox, I EXPECT to see a dropdown of 1 item, but instead I unfortunately see a dropdown the size of 2 thus an empty space instead of the second item
I am adding the simple code to create this scenario, I want to thank #Gikkman that helped getting this far and the code is actually his!
public class Test extends Application {
private int index = 0;
#Override
public void start(Stage primaryStage) throws IOException {
VBox vbox = new VBox();
vbox.setSpacing(10);
vbox.setAlignment(Pos.CENTER);
final ComboBox<String> box = new ComboBox<>();
box.setPrefWidth(200);
box.setVisibleRowCount(10);
Button add = new Button("Add");
Button remove = new Button("Remove");
add.setOnAction( new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
box.getItems().add("Item " + index++);
box.getItems().add("Item " + index++);
}
});
remove.setOnAction( new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
if( index > 0 )
box.getItems().remove(--index);
}
});
vbox.getChildren().addAll(add, remove, box);
Scene scene = new Scene(vbox);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Try this:
box.hide(); //before you set new visibleRowCount value
box.setVisibleRowCount(rows); // set new visibleRowCount value
box.show(); //after you set new visibleRowCount value
It works for me with editable comboBox and I think it will work in your case.
I had same problem and I solved it with a quick trick.
Just try to show and immediately hide !
add.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
box.getItems().add("Item " + index++);
box.getItems().add("Item " + index++);
box.show();
box.hide();
}
});
Just like to offer my two cents here. You may add the following codes to your combobox which define a custom listview popup that has variable height according to the current number of items. You can tweak the maximum number of items to be displayed in the popup.
yourComboBox.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
#Override
public ListCell<String> call(ListView<String> param) {
ListCell cell = new ListCell<String>() {
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
int numItems = getListView().getItems().size();
int height = 175; // set the maximum height of the popup
if (numItems <= 5) height = numItems * 35; // set the height of the popup if number of items is equal to or less than 5
getListView().setPrefHeight(height);
if (!empty) {
setText(item.toString());
} else {
setText(null);
}
}
};
return cell;
}
});
You don't have to change the number of entries to be displayed. The implementation will handle that automatically.
Say you want to display at most 10 items. Then, you use comboBox.setVisibleRowCount( 10 ); If there are less than 10 items at any time, Javafx will only show as many rows as there are items.
Actually, changing the number of visible rows at runtime can sometimes cause errors, from my experience, so you are better of with just having a set number.
Hope that helps.
I have some problems understanding what the problem is. I made a short example bellow, can you try it and then say what it doesn't do that you want to do.
public class Test extends Application{
private int index = 0;
#Override
public void start(Stage primaryStage) throws IOException{
VBox vbox = new VBox();
vbox.setSpacing(10);
vbox.setAlignment(Pos.CENTER);
ComboBox<String> box = new ComboBox<>();
box.setPrefWidth(200);
box.setVisibleRowCount(10);
Button add = new Button("Add");
Button remove = new Button("Remove");
add.setOnAction( new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
box.getItems().add("Item " + index++);
}
});
remove.setOnAction( new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
if( index > 0 )
box.getItems().remove(--index);
}
});
vbox.getChildren().addAll(add, remove, box);
Scene scene = new Scene(vbox);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
You can use two JavaFx list. First one is previous com box list, another one is final combo box list. then you can change dynamically using yourCombo.getItems().setAll(Your List);
Here is my sample code:
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class ComboBoxTest extends Application {
#Override
public void start(final Stage primaryStage) throws Exception {
primaryStage.centerOnScreen();
primaryStage.setHeight(200);
primaryStage.setWidth(300);
List<String> list1 = new ArrayList<>();
list1.add("one");
list1.add("two");
list1.add("three");
List<String> list2 = new ArrayList<>();
list2.add("one");
list2.add("two");
list2.add("three");
list2.add("four");
final ComboBox<String> combo = new ComboBox<String>();
combo.getItems().setAll(list1);
Button button = new Button("Change combo contents");
button.setOnAction(event -> {
if ( combo.getItems().size() == 3 ) {
combo.getItems().setAll(list2);
} else {
combo.getItems().setAll(list1);
}
combo.show();
});
VBox box = new VBox(20, combo, button );
box.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
primaryStage.setScene(new Scene( new StackPane(box) ));
primaryStage.show();
}
public static void main(String[] args) throws Exception {
launch(args);
}
}

Sort ObservableList from high to low - javafx

Is there any way to sort a ObservableList based on the values from high to low?
Say I have a
ObservableList<XYChart.Data> data;
containing a String and a Double. I want the list sorted based on the Double from highest to lowest values. The reason I want this is because charts look way better if their values are shown from the highest to the lowest.
I have something like this now:
sortedData = new SortedList<>(data);
sortedData.comparatorProperty().bind(mycomparatorProperty());
You can create a Comparator, compare by the Y value, and then reverse the order:
data.sort(Comparator.comparing(XYChart.Data<String,Double>::getYValue).reversed());
This will sort your collection as intented.
Or you can return a new collection:
List<XYChart.Data<String,Double>> sortedData =
data.stream()
.sorted(Comparator.comparing(XYChart.Data<String,Double>::getYValue).reversed())
.collect(Collectors.toList());
EDIT
For the sake of clarity, this is a full sample:
public class FXMain extends Application {
private final ObservableList<XYChart.Data<String,Double>> data =
FXCollections.observableArrayList(
new XYChart.Data("P1",200d),
new XYChart.Data("P2",150d),
new XYChart.Data("P3",250d));
#Override
public void start(Stage primaryStage) {
Button btn = new Button"Sort data!");
btn.setOnAction(e -> {
ObservableList<XYChart.Data<String,Double>> data2 =
data.stream()
.sorted(Comparator.
comparing(XYChart.Data<String,Double>::getYValue).reversed())
.peek(System.out::println)
.collect(Collectors.toCollection(()->FXCollections.observableArrayList()));
});
StackPane root = new StackPane();
root.getChildren().add(btn);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Resources