Handle pagination with page factory - java

I have a table view with an anchor pane panel with 2 children, a table view and a pagination. The pagination is not directly linked to the table view (like if you put a button with a label that gets updated).
The only examples I found is that the pagination itself handles the UI updates via it's setPageFactory method.
I know I shouldn't design it like this, unfortunately I don't have the time to change it for now. So here's my current solution:
paginationTab1.setPageFactory(e -> {
updateTableViewWithOffset(e);
//hack, as the pagination is not directly linked with the tableView
//just return an empty component that is not managed by the parent component
Label l = new Label();
l.setManaged(false);
return l;
});
Is this an acceptable workaround (return null doesn't update well the UI after...) ? Or is there a way to get the same listener's behavior as the setPageFactory method provides (i.e get the page offset when it's clicked either on the pagination's arrows or pagination's numbers)?

You can observe the Pagination's currentPageIndexProperty():
paginationTab1.currentPageIndexProperty().addListener((obs, oldIndex, newIndex) ->
updateTableViewWithOffset(newIndex.intValue()));
Here's a SSCCE:
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javafx.application.Application;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.scene.Scene;
import javafx.scene.control.Pagination;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class IndependentPaginationTest extends Application {
private static final int NUM_PAGES = 20 ;
private static final int ITEMS_PER_PAGE = 20 ;
#Override
public void start(Stage primaryStage) {
TableView<String> table = new TableView<>();
TableColumn<String, String> col = new TableColumn<>("Item");
table.getColumns().add(col);
col.setCellValueFactory(cellData -> new ReadOnlyStringWrapper(cellData.getValue()));
updateTable(table, 0);
Pagination pagination = new Pagination();
pagination.setPageCount(NUM_PAGES);
pagination.currentPageIndexProperty().addListener((obs, oldIndex, newIndex) ->
updateTable(table, newIndex.intValue()));
BorderPane root = new BorderPane(table, null, null, pagination, null);
primaryStage.setScene(new Scene(root, 800, 600));
primaryStage.show();
}
private void updateTable(TableView<String> table, Integer index) {
int start = index * ITEMS_PER_PAGE + 1;
int end = start + ITEMS_PER_PAGE ;
table.getItems().setAll(
IntStream.range(start, end)
.mapToObj(Integer::toString)
.map("Item "::concat)
.collect(Collectors.toList()));
}
public static void main(String[] args) {
launch(args);
}
}

Related

change BackGround color of specific elements in a listView (JavaFx)

i'm working on a project and i'd like to find a way to change the background color of some elements in a listView. i've find a way to add css style class to the listView in general but not to specific elements .
Also , i've heard about cell factory but I dont know if cell factory can adapt during the programme or just set up things at the begging
(i have a listView of an object that I call player , and I want that , when the player in the listView get enough points , his name becomes red)
is there a way to do something like this ?
ListView<Players> listview = ...;
for(Player p : listView){
p.addListener(//change color to red)
}
Thanks
I would use ObservableList and addListener to the given List.
Sample code:
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class App extends Application {
private StackPane main;
private ListView<Player> players;
#Override
public void start(Stage stage) {
main = new StackPane();
var scene = new Scene(main, 640, 480);
players = new ListView<Player>();
ObservableList<Player> playerObjs = FXCollections.observableArrayList (
new Player("A", 50),
new Player("B", 30),
new Player("C", 60),
new Player("D", 5),
new Player("E", 0)
);
players.setItems(playerObjs);
playerObjs.addListener(new ListChangeListener<Player>() {
#Override
public void onChanged(Change<? extends Player> change) {
updateView();
}
});
main.getChildren().add(players);
stage.setScene(scene);
stage.show();
}
public void updateView() {
for(int i = 0; i < players.getItems().size(); i++) {
if(players.getItems().get(i).getHp() < 10) {
players.getItems().get(i).setBackground(...);
}
}
}
public static void main(String[] args) {
launch();
}
}
Now, everytime the list changes, it calls updateView(), which if some condition holds, will set the given item Background to some value.
Let me know if that helped.

JavaFX TableView selected cell flickers when adding data

I have a JavaFX TableView with single cell selection enabled. When a user selects a cell the selection highlight will flicker when new data is added to the table
A small example that demonstrates the problem:
import java.util.Random;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.Callback;
public class SelectionBug extends Application
{
public static void main(
String[] args)
{
Application.launch(args);
}
#Override
public void start(
Stage primaryStage) throws Exception
{
final ObservableList<DummyData> list = FXCollections.observableArrayList();
final TableView<DummyData> tableView = new TableView<>(list);
tableView.getColumns().add(createColumn(item -> item.getColumn1()));
tableView.getColumns().add(createColumn(item -> item.getColumn2()));
tableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
tableView.getSelectionModel().setCellSelectionEnabled(true);
final Thread thread = new Thread(() ->
{
while (true)
{
Platform.runLater(() -> list.add(new DummyData()));
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
//do nothing
}
}
});
thread.setDaemon(true);
thread.start();
final BorderPane root = new BorderPane();
root.setCenter(tableView);
primaryStage.setScene(new Scene(root, 500, 500));
primaryStage.show();
}
private TableColumn<DummyData, String> createColumn(
final Callback<DummyData, String> dataGetter)
{
final TableColumn<DummyData, String> column = new TableColumn<>();
column.setCellValueFactory(cellData -> new ReadOnlyStringWrapper(dataGetter.call(cellData.getValue())));
return column;
}
private static class DummyData
{
private final String mColumn1;
private final String mColumn2;
public DummyData()
{
final Random ramdom = new Random();
mColumn1 = Integer.toString(ramdom.nextInt(1000));
mColumn2 = Integer.toString(ramdom.nextInt(1000));
}
public String getColumn1()
{
return mColumn1;
}
public String getColumn2()
{
return mColumn2;
}
}
}
If you run that and select a cell, you'll see the flickering.
My digging so far suggests it's to do with cell recycling in the table view: I changed the Cell Factory to assign and log out a unique ID and the cell's item for each cell object and found that the ID <-> item relationship is not constant; each cell object gets moved around the tableview showing different data with every update to the data model. This means that the selected property is modified on every update, causing the pseudoClassState to change. I suspect it's a subtle timing issue with when the cell is taken out of the tableview and when the cell's selected property is changed
Has anyone else seen this problem, and does anyone have any kind of workaround?
Probably a bit late for you, but I had a similar problem and managed to solve it by wrapping the TableView in an extra AnchorPane.

JavaFX Boolean Binding Based on Selected Items in a TableView

I am attempting to enable a JavaFX Button depending on the aggregate of a property value in the selected rows in a TableView. The following is an example application that demonstrates the problem:
package test;
import java.util.Random;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.MultipleSelectionModel;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(final String[] args) throws Exception {
launch(args);
}
private static class Row {
private final BooleanProperty myProp;
public Row(final boolean value) {
myProp = new SimpleBooleanProperty(value);
}
public BooleanProperty propProperty() { return myProp; }
}
#Override
public void start(final Stage window) throws Exception {
// Create a VBox to hold the table and button
final VBox root = new VBox();
root.setMinSize(200, 200);
// Create the table, and enable multi-select
final TableView<Row> table = new TableView<>();
final MultipleSelectionModel<Row> selectionModel = table.getSelectionModel();
selectionModel.setSelectionMode(SelectionMode.MULTIPLE);
root.getChildren().add(table);
// Create a column based on the value of Row.propProperty()
final TableColumn<Row, Boolean> column = new TableColumn<>("Value");
column.setCellValueFactory(p -> p.getValue().propProperty());
table.getColumns().add(column);
// Add a button below the table
final Button button = new Button("Button");
root.getChildren().add(button);
// Populate the table with true/false values
final ObservableList<Row> rows = table.getItems();
rows.addAll(new Row(false), new Row(false), new Row(false));
// Start a thread to randomly modify the row values
final Random rng = new Random();
final Thread thread = new Thread(() -> {
// Flip the value in a randomly selected row every 10 seconds
try {
do {
final int i = rng.nextInt(rows.size());
System.out.println("Flipping row " + i);
Thread.sleep(10000);
final BooleanProperty prop = rows.get(i).propProperty();
prop.set(!prop.get());
} while (true);
} catch (final InterruptedException e) {
System.out.println("Exiting Thread");
}
}, "Row Flipper Thread");
thread.setDaemon(true);
thread.start();
// Bind the button's disable property such that the button
// is only enabled if one of the selected rows is true
final ObservableList<Row> selectedRows = selectionModel.getSelectedItems();
button.disableProperty().bind(Bindings.createBooleanBinding(() -> {
for (int i = 0; i < selectedRows.size(); ++i) {
if (selectedRows.get(i).propProperty().get()) {
return false;
}
}
return true;
}, selectedRows));
// Show the JavaFX window
final Scene scene = new Scene(root);
window.setScene(scene);
window.show();
}
}
To test, start the above application, and select the row indicated by the text "Flipping row N", where N is in [0, 2]. When the value of the selected row changes to true...
Observed Behavior button remains disabled.
Desired Behavior button becomes enabled.
Does anyone know how to create a BooleanBinding that exhibits the desired behavior?
Your binding needs to be invalidated if any of the propPropertys of the selected rows change. Currently the binding is only observing the selected items list, which will fire events when the list contents change (i.e. items become selected or unselected) but not when properties belonging to items in that list change value.
To do this, create a list with an extractor:
final ObservableList<Row> selectedRows =
FXCollections.observableArrayList(r -> new Observable[]{r.propProperty()});
This list will fire events when items are added or removed, or when the propProperty() of any item in the list changes. (If you need to observe multiple values, you can do so by including them in the array of Observables.)
Of course, you still need this list to contain the selected items in the table. You can ensure this by binding the content of the list to the selectedItems of the selection model:
Bindings.bindContent(selectedRows, selectionModel.getSelectedItems());
Here is a version of your MCVE using this:
import java.util.Random;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.MultipleSelectionModel;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(final String[] args) throws Exception {
launch(args);
}
private static class Row {
private final BooleanProperty myProp;
public Row(final boolean value) {
myProp = new SimpleBooleanProperty(value);
}
public BooleanProperty propProperty() { return myProp; }
}
#Override
public void start(final Stage window) throws Exception {
// Create a VBox to hold the table and button
final VBox root = new VBox();
root.setMinSize(200, 200);
// Create the table, and enable multi-select
final TableView<Row> table = new TableView<>();
final MultipleSelectionModel<Row> selectionModel = table.getSelectionModel();
selectionModel.setSelectionMode(SelectionMode.MULTIPLE);
root.getChildren().add(table);
// Create a column based on the value of Row.propProperty()
final TableColumn<Row, Boolean> column = new TableColumn<>("Value");
column.setCellValueFactory(p -> p.getValue().propProperty());
table.getColumns().add(column);
// Add a button below the table
final Button button = new Button("Button");
root.getChildren().add(button);
// Populate the table with true/false values
final ObservableList<Row> rows = table.getItems();
rows.addAll(new Row(false), new Row(false), new Row(false));
// Start a thread to randomly modify the row values
final Random rng = new Random();
final Thread thread = new Thread(() -> {
// Flip the value in a randomly selected row every 10 seconds
try {
do {
final int i = rng.nextInt(rows.size());
System.out.println("Flipping row " + i);
Thread.sleep(10000);
final BooleanProperty prop = rows.get(i).propProperty();
Platform.runLater(() -> prop.set(!prop.get()));
} while (true);
} catch (final InterruptedException e) {
System.out.println("Exiting Thread");
}
}, "Row Flipper Thread");
thread.setDaemon(true);
thread.start();
// Bind the button's disable property such that the button
// is only enabled if one of the selected rows is true
final ObservableList<Row> selectedRows =
FXCollections.observableArrayList(r -> new Observable[]{r.propProperty()});
Bindings.bindContent(selectedRows, selectionModel.getSelectedItems());
button.disableProperty().bind(Bindings.createBooleanBinding(() -> {
for (int i = 0; i < selectedRows.size(); ++i) {
if (selectedRows.get(i).propProperty().get()) {
return false;
}
}
return true;
}, selectedRows));
// Show the JavaFX window
final Scene scene = new Scene(root);
window.setScene(scene);
window.show();
}
}

Javafx Listview Add and edit element

i want to add and edit directly an element to a listview :
/*
* 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.
*/
package javafx_test;
import java.util.Observable;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.cell.TextFieldListCell;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;
import javafx.util.StringConverter;
/**
*
* #author karim
*/
public class Javafx_test extends Application {
#Override
public void start(Stage primaryStage) {
ObservableList<String> items = FXCollections.observableArrayList("test1", "test2");
ListView<String> list = new ListView<>(items);
list.setEditable(true);
list.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
#Override
public ListCell<String> call(ListView<String> param) {
return new TextFieldListCell<>(new StringConverter<String>() {
#Override
public String toString(String object) {
return object;
}
#Override
public String fromString(String string) {
return string;
}
});
}
});
Button btn = new Button();
btn.setText("Add String");
btn.setOnAction((ActionEvent event) -> {
String c = new String("test");
list.getItems().add(list.getItems().size(), c);
list.scrollTo(c);
list.edit(list.getItems().size() - 1);
});
VBox root = new VBox(list, btn);
Scene scene = new Scene(root);
primaryStage.setTitle("test!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
Everything seems correct but that not working, it like its try to modify the first item not the newly added item in the last index, i don't know why
That's a bug.
There seems to be some truly horrible interplay between focus and editing. The basic problem seems to be that when a list cell loses focus, it cancels any editing. I think that by clicking on the button, you cause the focus to shift to that button, and then on the next rendering pulse the list cell sees it has lost focus and cancels editing. I can't quite explain why the first item in the list appears to go to an editing state, but I suspect it is due to some further interaction with the list's focusModel, which manages focus of individual items.
For a truly ugly hack, use an AnimationTimer to delay the call to ListView.edit(...) by an additional rendering frame. (In case you're not familiar with it, an AnimationTimer defines a handle(...) method that is invoked once on each rendering pulse; here I just count one frame and then call edit, and stop the timer.)
btn.setOnAction((ActionEvent event) -> {
String c = "test"+(list.getItems().size()+1);
list.getItems().add(list.getItems().size(), c);
list.scrollTo(list.getItems().size() - 1);
// list.edit(list.getItems().size() - 1);
new AnimationTimer() {
int frameCount = 0 ;
#Override
public void handle(long now) {
frameCount++ ;
if (frameCount > 1) {
list.edit(list.getItems().size() - 1);
stop();
}
}
}.start();
});
Calling scrollTo(...) with an index instead of an item seems more robust too (especially as you have items in there that are equal to each other :).)
Maybe someone else can come up with something a bit cleaner that this...
The issue seems to be the Cells not being updated before calling edit. Since updating the cells is done during layout, calling layout before starting the edit should fix the issue:
Example:
#Override
public void start(Stage primaryStage) {
ListView<String> listView = new ListView<>();
listView.setEditable(true);
listView.setCellFactory(TextFieldListCell.forListView());
Button editButton = new Button("Add & Edit");
editButton.setOnAction((ActionEvent event) -> {
listView.getItems().add("");
listView.scrollTo(listView.getItems().size() - 1);
listView.layout();
listView.edit(listView.getItems().size() - 1);
});
Scene scene = new Scene(new VBox(listView, editButton));
primaryStage.setScene(scene);
primaryStage.show();
}
What James_D already mentioned: It seems that the index for the edit method is calculated wrong. In the following example it should not grab the correct index, but it does.
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ListView;
import javafx.scene.control.cell.TextFieldListCell;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class ListEdit extends Application {
int i = 3;
#Override
public void start(Stage primaryStage) {
ObservableList<String> items = FXCollections.observableArrayList("test1", "test2");
ListView<String> list = new ListView<>(items);
list.setCellFactory(TextFieldListCell.forListView());
list.setEditable(true);
Button btn = new Button();
btn.setText("Add String");
btn.setOnAction((ActionEvent event) -> {
list.getItems().add(i - 1, "test" + i);
list.edit(i - 2);
i++;
});
VBox root = new VBox(list, btn);
Scene scene = new Scene(root);
primaryStage.setTitle("test!");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

PaginationSample Javafx cannot assign a value to final variable pagination

I'm trying to reproduce a Pagination Sample from oracle samples, but when I imported the project something strange happened that I can not build and run the project:
The complete code is:
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import javafx.util.Callback;
public class PaginationSample extends Application {
private final Pagination pagination;
private Image[] images = new Image[7];
private void init(Stage primaryStage) {
Group root = new Group();
primaryStage.setScene(new Scene(root));
VBox outerBox = new VBox();
outerBox.setAlignment(Pos.CENTER);
//Images for our pages
for (int i = 0; i < 7; i++) {
images[i] = new Image(PaginationSample.class.getResource("animal" + (i + 1) + ".jpg").toExternalForm(), false);
}
pagination = PaginationBuilder.create().pageCount(7).pageFactory(new Callback<Integer, Node>() {
#Override public Node call(Integer pageIndex) {
return createAnimalPage(pageIndex);
}
}).build();
//Style can be numeric page indicators or bullet indicators
Button styleButton = ButtonBuilder.create().text("Toggle pagination style").onAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent me) {
if (!pagination.getStyleClass().contains(Pagination.STYLE_CLASS_BULLET)) {
pagination.getStyleClass().add(Pagination.STYLE_CLASS_BULLET);
} else {
pagination.getStyleClass().remove(Pagination.STYLE_CLASS_BULLET);
}
}
}).build();
outerBox.getChildren().addAll(pagination, styleButton);
root.getChildren().add(outerBox);
}
//Creates the page content
private VBox createAnimalPage(int pageIndex) {
VBox box = new VBox();
ImageView iv = new ImageView(images[pageIndex]);
box.setAlignment(Pos.CENTER);
Label desc = new Label("PAGE " + (pageIndex + 1));
box.getChildren().addAll(iv, desc);
return box;
}
#Override public void start(Stage primaryStage) throws Exception {
init(primaryStage);
primaryStage.show();
}
public static void main(String[] args) { launch(args); }
}
But netbeans show me the error "cannot assign a value to final variable pagination" for the following line:
pagination = PaginationBuilder.create().pageCount(7).pageFactory(new Callback<Integer, Node>() {
Someone can explain me what is going wrong??
A final field can be initialized only once. So the best place to initialize it is when its declared
private final Pagination pagination = new Pagination(...);
or it can be done in the constructor, since the constructor is assured to be called once per instance
private final Pagination pagination;
public PaginationSample() {
pagination = new Pagination(...);
}
final field cannot be initialized in a method because a method can be called multiple times once an instance of that class gets created

Categories