How to extend JavaFX Pagination navigation to display additional controls? - java

I would like to use Pagination to show a table page wise. This works in principle but I would like to add additional controls that are in the same line as the default pagination navigation:
button for going to first page ("<<")
text field for jumping to specified page index
button for going to last page (">>")
text field for number of entries per page
search text field for jumping to a page that contains the row entry with a given ID.
I am able to customize the page above the pagination control with the method setPageFactory() but I am not able to customize the navigation control itself. How to do that? If I add my additional controls above or below the default navigation I waste some space:
Related article:
JavaFX Pagination, adding << and >>> options
Filed an enhancement request

Custom navigation controls are not supported. While waiting for the enhancement request to be fixed, we could apply a hack (if QA guidelines allow) as outlined below. It's a hack because everything related to the navigation control is package private in PaginationSkin which itself is not (yet) public api.
The basic idea is to insert additional nodes into core navigation control, which obviously implies relying on implementation details (dont, dont, dont :-). We do so on-the-fly at instantiation and whenever the next button is inserted again - core clears out all its children quite often during layout and state changes on the pagination. This involves:
lookup the pane that contains the buttons, it's selector is control-box
keep a reference to its last child, which is the next button
add a listener to the pane's children to be able to insert custom controls
Code example for a custom skin, here simply two button for first/last:
public static class CustomPaginationSkin extends PaginationSkin {
private HBox controlBox;
private Button prev;
private Button next;
private Button first;
private Button last;
private void patchNavigation() {
Pagination pagination = getSkinnable();
Node control = pagination.lookup(".control-box");
if (!(control instanceof HBox))
return;
controlBox = (HBox) control;
prev = (Button) controlBox.getChildren().get(0);
next = (Button) controlBox.getChildren().get(controlBox.getChildren().size() - 1);
first = new Button("A");
first.setOnAction(e -> {
pagination.setCurrentPageIndex(0);
});
first.disableProperty().bind(
pagination.currentPageIndexProperty().isEqualTo(0));
last = new Button("Z");
last.setOnAction(e -> {
pagination.setCurrentPageIndex(pagination.getPageCount());
});
last.disableProperty().bind(
pagination.currentPageIndexProperty().isEqualTo(
pagination.getPageCount() - 1));
ListChangeListener childrenListener = c -> {
while (c.next()) {
// implementation detail: when nextButton is added, the setup is complete
if (c.wasAdded() && !c.wasRemoved() // real addition
&& c.getAddedSize() == 1 // single addition
&& c.getAddedSubList().get(0) == next) {
addCustomNodes();
}
}
};
controlBox.getChildren().addListener(childrenListener);
addCustomNodes();
}
protected void addCustomNodes() {
// guarding against duplicate child exception
// (some weird internals that I don't fully understand...)
if (first.getParent() == controlBox) return;
controlBox.getChildren().add(0, first);
controlBox.getChildren().add(last);
}
/**
* #param pagination
*/
public CustomPaginationSkin(Pagination pagination) {
super(pagination);
patchNavigation();
}
}

Related

How to manually call TreeCell#updateItem for a CheckBoxTreeItem so we can apply CSS?

I am having a standard TreeView in JavaFX with CheckBoxTreeItem in it. I've installed a listener to see when someone checks/ unchecks a checkbox. But I want that when someone check/unchecks a checkbox I trigger that checkboxitem's parent updateItem method and change his CSS ( for example if 3 or more childs are selected for a parent then change his color to red, otherwise green).
How can I do that?
rootItem.addEventHandler(CheckBoxTreeItem.checkBoxSelectionChangedEvent(), e -> {
if (e.getTreeItem().isLeaf()) {
TreeItem<String> treeItem = (TreeItem) e.getTreeItem();
CheckBoxTreeItem<String> parentItem = (CheckBoxTreeItem<String>) treeItem.getParent();
// how to call repaint for the parentItem????
}
});
treeView.setCellFactory(p -> new CheckBoxTreeCell<>() {
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
// toggle the parent's CSS here
}
});
I agree with the answer by M. S. regarding the use of PseudoClass. However, you should not be trying to manually invoke updateItem. Instead, just add an EventHandler to listen for "check box selection changed" events. When an event occurs in a direct child, the parent should update the pseudo-class based on (using your example) whether or not 3+ children are selected.
Here's an example which also includes a "branch" PseudoClass so you can distinguish between a branch and a leaf in the CSS file:
import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
import javafx.css.PseudoClass;
import javafx.event.EventHandler;
import javafx.event.WeakEventHandler;
import javafx.scene.control.CheckBoxTreeItem;
import javafx.scene.control.CheckBoxTreeItem.TreeModificationEvent;
import javafx.scene.control.cell.CheckBoxTreeCell;
public class MyCheckBoxTreeCell<T> extends CheckBoxTreeCell<T> {
private static final PseudoClass BRANCH = PseudoClass.getPseudoClass("branch");
private static final PseudoClass THREE_CHILDREN_SELECTED = PseudoClass.getPseudoClass("three-children-selected");
// event handler to listen for selection changes in direct children
private final EventHandler<TreeModificationEvent<T>> handler = event -> {
/*
* Event starts from the source TreeItem and bubbles up the to the root. This means
* the first time getTreeItem() != event.getTreeItem() will be the source TreeItem's
* parent. We then consume the event to stop it propagating to the next parent.
*/
if (getTreeItem() != event.getTreeItem()) {
event.consume();
updatePseudoClasses();
}
};
private final WeakEventHandler<TreeModificationEvent<T>> weakHandler = new WeakEventHandler<>(handler);
// Used to listen for the "leaf" property of the TreeItem and update the BRANCH pseudo-class
private final InvalidationListener leafListener = observable -> updatePseudoClasses();
private final WeakInvalidationListener weakLeafListener = new WeakInvalidationListener(leafListener);
public MyCheckBoxTreeCell() {
getStyleClass().add("my-check-box-tree-cell");
// add listener to "treeItem" property to properly register and unregister
// the "leafListener" and "handler" instances.
treeItemProperty().addListener((observable, oldValue, newValue) -> {
if (oldValue != null) {
oldValue.leafProperty().removeListener(weakLeafListener);
oldValue.removeEventHandler(CheckBoxTreeItem.checkBoxSelectionChangedEvent(), weakHandler);
}
if (newValue != null) {
newValue.leafProperty().addListener(weakLeafListener);
newValue.addEventHandler(CheckBoxTreeItem.checkBoxSelectionChangedEvent(), weakHandler);
}
updatePseudoClasses();
});
}
private void updatePseudoClasses() {
/*
* Assumes the use of CheckBoxTreeItem for each TreeItem in the TreeView.
*
* This code is not the most efficient as it will recalculate both the BRANCH and
* THREE_CHILDREN_SELECTED pseudo-classes each time either possibly changes.
*/
var item = (CheckBoxTreeItem<T>) getTreeItem();
if (item == null) {
pseudoClassStateChanged(BRANCH, false);
pseudoClassStateChanged(THREE_CHILDREN_SELECTED, false);
} else {
pseudoClassStateChanged(BRANCH, !item.isLeaf());
int selected = 0;
for (var child : item.getChildren()) {
// only need to know if *at least* 3 children are selected
if (((CheckBoxTreeItem<T>) child).isSelected() && ++selected >= 3) {
break;
}
}
pseudoClassStateChanged(THREE_CHILDREN_SELECTED, selected >= 3);
}
}
// No need to override "updateItem(T,boolean)" as CheckBoxTreeCell provides
// the necessary implementation which can be customized via the StringConverter
// property.
}
And then your CSS file could look like:
.my-check-box-tree-cell:branch {
-fx-background-color: green;
-fx-text-fill: white;
}
.my-check-box-tree-cell:branch:three-children-selected {
-fx-background-color: red;
-fx-text-fill: white;
}
Addressing questions in comments:
Why wrapping every listener inside a weak one if we take care to unsubscribe it?
To decrease the chance of memory leaks. For instance, if you throw away the TreeView (without having cleared the root property) but maintain references to the TreeItems somewhere, then a non-weak handler/listener would hold the TreeCells and the TreeView in memory.
Why are you listening for leaf changes and when does it gets called?
To handle the case where TreeItems are dynamically added and/or removed. A TreeItem is a leaf if and only if its children list is empty. If an item is added, and the leaf now becomes a branch, we need to update the BRANCH pseudo-class in order to have the proper CSS applied. Same if an item is removed and a branch becomes a leaf.
This may or may not be relevant to your use case. If not, then feel free to remove this part of the implementation.
You check getTreeItem() != event.getTreeItem()) in the checkbox checked handler. Why? This will be called when a checkbox gets checked/ unchecked.
When you (un)check a CheckBoxTreeItem it fires an event. This event begins its journey at the CheckBoxTreeItem that was (un)checked. From there, it travels up (i.e. bubbles) the item hierarchy all the way to the root. At each item, any registered handlers will be invoked. Though if the event is consumed it does not proceed to the next parent item.
The reason we're adding the handler is to listen for any children being (un)checked—but only direct children. We don't care about changes in arbitrarily deep descendants nor the item the handler was registered to.
Since we only care about changes in direct children, we need to make sure we only react to events fired by said children. As the event is processed first by the item that was (un)checked, we need to not do anything in that first handler. We can do this by testing if the TreeItem of the containing TreeCell is the same one that fired the event, and the TreeModificationEvent#getTreeItem() method returns the item that caused the event to be fired (i.e. the item that was (un)checked). If they are the same instance, do nothing and let the event bubble up to the parent.
Now the parent item's handler is processing the event. This means getTreeItem() != event.getTreeItem() will return true and we enter the if block. This causes the update, if necessary, of the pseudo-classes' state. We then consume the event so it doesn't bubble up to the next parent; this effectively makes the handler only listen to events from direct children.
Note that if the parent item is not currently being displayed in the tree, then it will not be part of a cell. If its not part of a cell, it won't have had the handler added to it. Thus any non-displaying items won't be affected by any of this. This is okay since everything we're updating is purely visual; if an item isn't being displayed then there's no visuals to update.
You don't need to change it manually, you can use a PseudoClass:
private PseudoClass threeChildrenClass = PseudoClass.getPseudoClass("three-children");
tree.setCellFactory(param -> new CheckBoxTreeCell<String>() {
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
setGraphic(null);
} else {
setText(item);
// Change the class based on the number of parent items
pseudoClassStateChanged(threeChildrenClass, hasThreeChildren(item));
}
}
});
In your CSS file:
.check-box-tree-cell:three-children {
-fx-background-color: red;
}
It looks like CheckBoxTreeCell doesn't have a built-in "checked" pseudo-class, you can add a "checked" PseudoClass and apply it when the tree cell is checked. Then you can call it like this:
.check-box-tree-cell:three-children:checked {
-fx-background-color: green;
}

TestFX clickOn() on particular text on combobox/choicebox

I‘m new to TestFX GUI-testing with fxrobot (javafx).
My current task is about clicking on a choice on a drop down menu created with combobox. I didn‘t find any tutorials mentioning this issue.
Is it really possible to implement the clickOn() method selecting a text in a combobox/drop down menu? Is there an example how to do it?
Thanks a million!
This is an example of a way where a user selects the given text in the given combobox.
void user_selects_combo_item(String comboBoxId, String itemToSelect) {
ComboBox<?> actualComboBox = lookupControl(comboBoxId);
// Find and click only on arrow button. This is important for editable combo-boxes.
for (Node child : actualComboBox.getChildrenUnmodifiable()) {
if (child.getStyleClass().contains("arrow-button")) {
Node arrowRegion = ((Pane) child).getChildren().get(0);
robot.clickOn(arrowRegion);
Thread.sleep(100); // try/catch were skipped for shorter code.
robot.clickOn(itemToSelect);
}
}
Assert.fail("Couldn't find an arrow-button.");
}
private <T extends Node> T lookupControl(String controlId) {
T actualControl = robot.lookup(controlId).query();
assertNotNull("Could not find a control by id = " + controlId, actualControl);
return actualControl;
}

Items changed event for JavaFX ListView control

I am looking for something like an Item Changed Event or Item Count Changed Event for JavaFX ListView control or lets just say in general for any collection type control.
It is because, I have some Buttons, that I want to be enabled only when there is at least one item in ListView otherwise that Button should be in disabled state.
It is my guess that perhaps adding a ChangeListener to the ListView control. would that be a right approach.
Any suggestions how can we achieve this.
The JavaFX Listview provides a method with the signature
public final ObservableList<T> getItems()
You can add a listener to the observable list which will be called whenever items are added to or removed from the ListView.
aListView.getItems().addListener(new ListChangeListener() {
#Override
public void onChanged(ListChangeListener.Change change) {
System.out.println("Detected a change! ");
}
});
Similar functionality is also provided by the other 'collection' controls.
Add a binding based on the ListView items using Bindings.isEmpty
button.disableProperty().bind(Bindings.isEmpty(listView.getItems()));
This will also work when someone calls ListView#setItems and completely changes the observable list:
InvalidationListener updateScrollBarListener = ...
aListView.itemsProperty().addListener((obs, old, current) -> {
if(old != null) {
old.removeListener(updateScrollBarListener);
}
if(current != null) {
current.addListener(updateScrollBarListener);
}
});
I've been looking for a way to to make this a bit shorter (perhaps with Bindings) but haven't found it so far.

Updating a Wicket WebMarkupContainer

Inside my Wicket webpage, I have a WebMarkupContainer which contains a ListView:
notifications = new ArrayList<Notification>(...);
ListView listView = new ListView("notification", notifications) {
#Override
protected void populateItem(ListItem item) {
...
}
};
container = new WebMarkupContainer("container");
container.setOutputMarkupId(true);
container.add(listView);
this.add(container);
The WebMarkupContainer is in place in order to let me dynamically update the list of items shown to the user onscreen. This is possible when the user clicks on a link or by adding the container to incoming AjaxRequestTarget.
Now I'm required to update the list without having an Ajax request:
public void refresh() {
List<Notification> newNotifications = ...
notifications.addAll(0, newNotifications);
}
This method is called in a run-time environment and the list of notifications, which is a private field of my webpage (same one as last code), will contain new objects. I want these new items displayed to the user. Is it possible to update (or re-render) the container?
I'm new to Wicket so if you have a better way to achieve the same results, I would appreciate if you could share it with me.
You would have to do it on a timer. Use AjaxSelfUpdatingTimerBehavior to do so. Just set some sensible duration and add your container to target in 'onTimer()' method.
EDIT:
If your 'refresh()' function is only called when new notifications appear, you could set a flag on your page (define boolean variable on page and change it to true when new notification appears and to false once listView is refreshed). Then you can set short duration on the behavior and 'onTimer()' would look something like that:
onTimer(AjaxRequestTarget target) {
if(newNotifications) {
target.add(container);
newNotifications = false;
}
}
And refresh
public void refresh() {
List<Notification> newNotifications = ...
notifications.addAll(0, newNotifications);
newNotifiactions = true;
}
That way container won't be refreshed too often (which might cause strange effects) and will refresh every time new notification appears.

Wicket - updating ListView using AJAX and Wicket Model

I have a :
Client Class
ListView
TextField
I need to populate my ListView in order to form a table:
WORKING CODE:
clientModel = new LoadableDetachableModel() {
#Override
protected Object load() {
return Client.getClientListByCompanyName(searchClientInput.getValue());
}
};
searchClientInput.setModel(new Model<String>());
searchClientInput.add(new AjaxFormComponentUpdatingBehavior("onkeyup") {
#Override
protected void onUpdate(AjaxRequestTarget target) {
target.add(clientListViewContainer);
}
});
clientListView = new ListView<Client>(CLIENT_ROW_LIST_ID, clientModel) {
#Override
protected void populateItem(ListItem<Client> item) {
Client client = item.getModelObject();
item.add(new Label(CLIENT_ROW_COMPANY_CNPJ_ID, client.getCompanyName()));
item.add(new Label(CLIENT_ROW_COMPANY_NAME_ID, client.getCompanyCnpj()));
}
};
clientListViewContainer.setOutputMarkupId(true);
clientListViewContainer.add(clientListView);
add(clientListViewContainer);
Now, in my HTML, I have a TextField. Whenever an user types something in this TextField, a select will be made in the database with whatever he typed. So for each word, a select is made, and the table needs to be updated. I am guessing I will need to use AJAX and possibly a Model. I'm kind of lost about how I can do this, if someone can provide me examples I would be very grateful.
EDIT: New code that is throwing exception: Last cause: Attempt to set model object on null model of component: searchClientForm:searchClientInput
EDIT 2: Ok so the exception was that my TextField didn't had a model to bind data to. So what I did was: searchClientInput.setModel(new Model<String>());
I also had a problem with the event. Using onkeydown was working, but not as intended. I had Company Name 1-4. If I typed Company Name 1, I would need to press one key again so the table would get updated. With onkeyup this don't happens. Thanks for the help.
You could give the ListView a LoadableDetachableModel which provides the selected clients matching your TextField's value.
Use an AjaxFormComponentUpdatingBehavior on your TextField which add a parent of the ListView to the request target (don't forget #setOutputMarkupId().
I believe the best way to perform what you want (which is repainting a table/list at each input change --> DB access) is with a DataView and a DataProvider.
A DataView is just like the ListView component except it uses an IDataProvider to get the data you want to present. You are able to implement the DataProvider so it accesses your DB, and you can add restrictions (where clauses) to the DataProvider.
[this is more like pseudo-code]
public final class MyDataProvider<T> extends SortableDataProvider<T> {
// ...
Set filters;
// filters is the set where the restrictions you want to apply are stored
...
#Override
public Iterator<T> iterator(int first, int count) {
// DAO (Data Access Object) access to DB
// ...
return dao.findByRestrictions(filters).iterator();
}
...
}
Now on the ajax event on your input component you are able to update the filter being used in the DataProvider, and in the the next repaint of the DataView, the provider will "pull" the data matching the restrictions defined in the filter.
Hope it helps. Best regards.

Categories