JavaFX CheckBoxTableCell catch ClickEvent - java

Using the CheckBoxTableCell in a TableView I wan't to perform some code when the user clicks the checkbox.
This code needs to be performed before the CheckBox get's checked and before the associated property is changed.
Since CheckBoxTableCell does not trigger startEdit(), commitEdit() and so on I need another point where I can put my code.
updateItem() would be too late, since the value has already been changed there.
Does anybody know any other place where I can insert my code? Or am I better of just writing my own CheckBoxTableCell?
For those who want to think further: When the user checks the checkBox I want to run a code that checks if his decision may cause him problems later on. When the property changes I have a changeListener that uploads the new value to a database. If there may be any problems (my code returns true) then there should be the obligatory error message: "Do you really want to do this...blabla" and only if he confirms the check box should be actually checked, the value changed and uploaded to the database.
This is how I set up my CheckBoxTableCell:
//init cell factories
Callback<TableColumn<PublicMovie, Boolean>, TableCell<PublicMovie, Boolean>> checkBoxCellFactory
= (TableColumn<PublicMovie, Boolean> p) -> new CheckBoxTableCell<>();
//Assign Column
allMoviesCheckBoxColumn = (TableColumn<PublicMovie, Boolean>) allMoviesTableView.getColumns().get(0);
//Set CellValueFactory
allMoviesCheckBoxColumn.setCellValueFactory(cellData -> cellData.getValue().getSomeBooleanProperty();
someBooleanProperty has a listener. When changed to true, the statusProperty changes to 1.
This code snippet is in the constructor of the TableViews underlying class. Whenever the user clicks the CheckBox, it is executed.
this.status.addListener((ObservableValue<? extends Number> observable, Number oldValue, Number newValue) -> {
DataHandler.fireOnChangedEvent(PublicMovieChangeListener.STATUS_CHANGED, getMovie(), newValue, oldValue);
});
DataHandler holds a list of Listeners (so far only one). The following code is executed when the onChange event is called by the DataHandler:
#Override
public void onChange(int changeID, PublicMovie changedPublicMovie, Object newValue, Object oldValue) {
switch (changeID) {
case STATUS_CHANGED:
boolean mayUpdate = check();
//check is a placeholder for the code that checks for problems.
//It will return false if there might be a problem.
if(!mayUpdate){
mayUpdate = ErrorDialogs.getConfirmationDialog("Überfüllung!", header, content);
}
if (mayUpdate) {
updateMovie(changedPublicMovie);
} else {
changedPublicMovie.getStatusProperty().set((int) oldValue);
refreshAllMoviesTable();
}
break;
case THREE_DIM_CHANGED:
updateMovie(changedPublicMovie);
break;
case CHILDREN_CHANGED:
updateMovie(changedPublicMovie);
break;
}
What you can see here is my attempt to reverse the changed Property... but the checkbox remains checked. Thus I'm looking for a way to perform the checking prior to changing the Property.

Based on the documentation of CheckBoxTableCell:
If you want to be notified of changes, it is recommended to directly
observe the boolean properties that are manipulated by the CheckBox.
I don't say that it is impossible with CheckBoxTableCell, but most probably it is not so straightforward.
This kind of "pre-select" events can be managed in most of the cases with EventFilters.
Therefore you could try to use a normal TableColumn with the appropriate cellFactory that utilizes these EventFilters.
My example
The used cellFactory:
tableCol.setCellFactory(p -> {
CheckBox checkBox = new CheckBox();
TableCell<Person, Boolean> tableCell = new TableCell<Person, Boolean>() {
#Override
protected void updateItem(Boolean item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null)
setGraphic(null);
else {
setGraphic(checkBox);
checkBox.setSelected(item);
}
}
};
checkBox.addEventFilter(MouseEvent.MOUSE_PRESSED, event ->
validate(checkBox, (Person) cell.getTableRow().getItem(), event));
checkBox.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
if(event.getCode() == KeyCode.SPACE)
validate(checkBox, (Person) cell.getTableRow().getItem(), event);
});
tableCell.setAlignment(Pos.CENTER);
tableCell.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
return tableCell;
});
and the used validate method:
private void validate(CheckBox checkBox, Person item, Event event){
// Validate here
event.consume();
Alert alert = new Alert(AlertType.CONFIRMATION);
alert.setTitle("Confirmation Dialog");
alert.setHeaderText("Look, a Confirmation Dialog");
alert.setContentText("Are you ok with this?");
// Set the checkbox if the user want to continue
Optional<ButtonType> result = alert.showAndWait();
if (result.get() == ButtonType.OK)
checkBox.setSelected(!checkBox.isSelected());
}
What is does:
It renders a simple CheckBox and adds two EventFilters to this CheckBox. The first one is executed on mouse press, the second one on keypress (here the KeyCode.SPACE is used). Both of them call the validate method. The validate method consumes the event (ensures that the selection state of the CheckBox is not modified), "validates" the input (no validation on my side) then shows a confirmation dialog. If the user agrees, the selectedProperty of the CheckBox is set.

Related

How to make one action event for many chechbox'es?

I create an application in JavaFX where there is a lot of choicebox'es (around 100). Clicking each of them changes the status of one Boolean variable (selected - true, unselected - false). I have ActionEvent for each of choicebox, but I would like to make action event which suport all of them.
One of ActionEvent looks like:
public void onActionClick(ActionEvent actionEvent) {
if(firstCheckbox.isSelected()){
firstBooleanValue=true;
} else {
firstBooleanValue=false;
}
}
Second looks similar:
public void onActionClick(ActionEvent actionEvent) {
if(secondCheckbox.isSelected()){
secondBooleanValue=true;
} else {
secondBooleanValue=false;
}
}
I heard from my friend that I should create class with EventHandler and pass parameters (Checkbox and Boolean variable) but I don't know how. Any solutions?
I heard from my friend that I should create class with EventHandler and pass parameters (Checkbox and Boolean variable)
Unless you want to use a container class for the boolean variable (e.g. BooleanProperty), it's not really possible to pass a variable in a way that allows you to write it. You could of course pass a Consumer<Boolean>.
(Theoretically it would be possible to access fields via reflection to write a value, but I strongly recommend not doing this.)
In the event handler you could use the source property to get the object that triggered the change (the CheckBox). This fact would allow you to create a Map<CheckBox, Consumer<Boolean>> to handle the event with the same event handler without testing for reference equality with 100 CheckBoxes.
private boolean a;
private boolean b;
private boolean c;
private Map<CheckBox, Consumer<Boolean>> eventMap = new HashMap<>();
private void comboAction(ActionEvent event) {
CheckBox cb = (CheckBox) event.getSource();
eventMap.get(cb).accept(cb.isSelected());
}
#Override
public void start(Stage primaryStage) {
CheckBox cb1 = new CheckBox("a");
CheckBox cb2 = new CheckBox("b");
CheckBox cb3 = new CheckBox("c");
// tell event handler what to do with the booleans
eventMap.put(cb1, v -> a = v);
eventMap.put(cb2, v -> b = v);
eventMap.put(cb3, v -> c = v);
// register event handlers
EventHandler<ActionEvent> onAction = this::comboAction;
for (CheckBox cb : eventMap.keySet()) {
cb.setOnAction(onAction);
}
Button button = new Button("print");
button.setOnAction(evt -> {
System.out.println(a);
System.out.println(b);
System.out.println(c);
System.out.println("-------");
});
Scene scene = new Scene(new VBox(cb1, cb2, cb3, button));
primaryStage.setScene(scene);
primaryStage.show();
}
However the fact that there are 100 boolean fields in a single class indicates a design issue. Consider storing the data in a different data structure, like List, Map or similar data structures. You could also store the CheckBoxes in such a data structure which would make the use of an onAction event handler unnecessary; you could simply retrieve the CheckBox responsible for the property and use isSelected when you need the value...

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;
}

JavaFx TableView itemProperty does not notifies ChangeListener

I have problem with my TableView element. I adding listener like that:
HardwareIdTableView.getItems().addListener(
(ListChangeListener.Change<? extends FirmwareData.HardwareIdWrapper> change) -> {
checker.hardwareIdCompleted.setValue(change.getList().size() > 0);
});
checker.hardwareIdCompleted is BooleanProperty.
I checked in debugger and new items was added to the TableView, but hardwareIdCompleted still resist 'false'.
P.S.
I add items to TableView like this:
public void addHardwareKey(HardwareIdKeyT key) {
ObservableList<FirmwareData.HardwareIdWrapper> idKeys = HardwareIdTableView.getItems();
if (idKeyEditSelected != null) {
fwData.removeHardwareIdKey(idKeyEditSelected.getIdPattern());
idKeys.remove(idKeyEditSelected);
}
if (!idKeys.contains(key)) {
HardwareIdTitledPane.pseudoClassStateChanged(PseudoClass.getPseudoClass("pane-error"), false);
idKeys.add(new FirmwareData.HardwareIdWrapper(key));
fwData.addHardwareIdKey(key);
}
}
The fault was straight - my reset function assigned new list to TableView. Since listener was assigned to old item list, it doesn't get notification, when I was expecting.

Prevent some values of SmartGWT IPickTreeItem to be selected

I'm trying to clear the user selected value on a IPickTreeItem.
This is the only solution I've found to restrict the user from selecting some of the Tree root values (not all).
To be more clear, it seems that calling event.cancel() do not stop the event from bubbling.
Am I doing something wrong ?
TreeNode treenode = new TreeNode("root", new TreeNode("Operation A"),
new TreeNode("Operation B"));
final DynamicForm dynamicForm = new DynamicForm();
Tree tree = new Tree();
tree.setRoot(treenode);
final IPickTreeItem pickTreeItem = new IPickTreeItem();
pickTreeItem.setValueTree(tree);
pickTreeItem.addChangeHandler(new ChangeHandler()
{
#Override
public void onChange(ChangeEvent event)
{
pickTreeItem.clearValue() // Not clearing the value
pickTreeItem.setValue((String)null) // Not working neither
event.cancel() // Not seeming to work...
}
});
dynamicForm.setItems(pickTreeItem);
dynamicForm.draw();
This is not working either :
pickTreeItem.setInputTransformer(new FormItemInputTransformer()
{
#Override
public Object transformInput(DynamicForm form, FormItem item,
Object value, Object oldValue)
{
return "Desired New Value (not working)...";
}
});
This is weird because it works using an external Button to clear the value (outside the picktreeitem handler)
Button bt = new Button("click");
bt.addClickHandler(new ClickHandler()
{
#Override
public void onClick(ClickEvent event)
{
pickTreeItem.setValue((Object) null);
}
});
Expected behavior
My Tree :
-aaaa
----bbbb
----cccc
-dddd
----eeee
----ffff
If the user selects "aaaa" the PickTreeItem value should be reverted to the defaultValue ("Choose a value"), optionally inform the user that he cannot pick "aaaa".
The PickTreeItem should accept "dddd" as a valid choosen value.
As with all FormItems, event.cancel() is the correct way to disallow the change. There was a framework level bug that was preventing this from behaving correctly that has now been corrected.
See this thread on the Isomorphic forums
I understand it is not exactly the same with what you are trying to achieve, but you could consider to define a CustomValidator, that reads the selected values and returns false and an appropriate message, when one of the parent values that shouldn't be, is selected. For this to work, you must set pickTreeItem.setCanSelectParentItems(Boolean.TRUE), to allow for parent items to be selected, and pickTreeItem.setValidateOnChange(Boolean.TRUE), to validate the selected values upon selection.

Walk-around to actionPerformed() returning void

I am trying to use the MVC design.
In the model I wanted a method like this
public boolean changeSomeData(...){
boolean b;
//create a dialog with an OK button
return b;
}
I want the method to return TRUE if the changes were actually made. The changes are done inside the actionPerformed method of the OK button.
My problem is that I can't write b=true; inside the actionPerform of the OK button, because I have to declare b as final in order to use it in the actionPerformed().
What I did is creating a class
private class MyBoolean {
boolean b;
}
and then
public boolean changeSomeData(...){
MyBoolean myBoolean;
//create a dialog with an OK button
actionPerformed(){
//make changes in the data
myBoolean.b=true;
}
boolean b = myBoolean.b;
return b;
}
But I don't feel good about this solution and I wanted to know if it is correct what I did and if there is a better solution.
Should I better throw an exception if the changes aren't made? (for example, if the user clicks "cancel" instead of "ok")
In the model I wanted a method like this ... //create a dialog with an OK button
I'd say this is a flaw already, since the model should not do anything with views directly.
A better approach would be to open the dialog (using the controller), register the controller for the ActionEvent of "OK" (and thus actionPerformed) and then do whatever changes should be done in that method.
Edit:
You might want to consider the following rough approach:
The views register themselves or associated classes to the model as listeners. Whenever the model is changed it fires events to notify the views of the change.
The controller registers itself on the views and is notified when the views change. If a user changes data, the controller then might open the dialog and only commit the changes of the user signals "OK". Thus the model has never to check itself if data needs to be changed. That is actually the controller's task and if the controller passes changes to the model, it should apply them.
A better way to achieve your task is to keep a variable on the dialog that indicates if a successful change was made. Then have a method that your model class calls to retrieve the value and return it.
Something like:
public boolean changeSomeData(...){
//create a dialog with an OK button
return dialog.isSuccess();
}
One way you could make this code a bit cleaner...
public boolean changeSomeData() {
// note that this is not a class boolean, no need to do extra autoboxing.
boolean dataChanged = false;
// check the old value against the new value
// for classes
if (oldvalue.equals(newValue)) {
oldValue = newValue;
dataChanged = true;
}
// for pimitives (built-ins)
if (oldvalue == newValue) {
oldValue = newValue;
dataChanged = true;
}
// odds are good that the above action performed was supposed to call this
// changeSomeData() and not the other way around.
// if you must fire actionPerformed() when data has changed, then do so
// like this, otherwise if it was added as part of the "solution" you can
// skip it.
if (dataChanged) {
actionPeformed();
}
return dataChanged;
}
Note that this code is Controller code, as it manipulates the model directly, and (possibly) updates views.

Categories