I have TableColumn with CheckBox. I have to do this listener and I wonder why the listener doubles after every click.
selectedColumn.setCellFactory(column -> new CheckBoxTableCell<>());
selectedColumn.setCellValueFactory(cellData -> {
Dir dir = cellData.getValue();
BooleanProperty property = dir.isSelectedProperty();
property.addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) ->{
System.out.println(newValue);
});
return property;
});
First click in checkbox i row return:
True
True
Second unselect return:
False
False
False
False
Thrid select return:
True
True
True
True
True
True
why ? :)
This is happening because controls like TableView (and ListView etc) virtualizes its contents. From TableView Javadoc:
The TableView control is designed to visualize an unlimited number of
rows of data, broken out into columns.
The TableView may hold a large number of items. However, on the screen, you could probably see 10-30 rows (each corresponding to a single item) at any point in time.
Without virtualization, if you have 1 million items, it would create 1 million TableRow objects (we haven't talked about TableCell), with each TableRow maintaining all the states/values. This takes a lot of memory and processing power.
On the other hand, with virtualization, if your View can only display 10 rows, TableView will only create, for example, 12 TableRow instances. As you scroll through the list, some of the items disappear from your sight. These TableRow instances are immediately reused for items that enters your sight.
This is why setCellFactory() and setCellValueFactory() methods are of Callback type. Each time a row was reused, it will call this callback object to update the TableCell. This is why your listener is being added repeatedly, causing this.
Depending on what you need, it may be possible to add a ListChangedListener to the list of items.
ObservableList<Dir> list = FXCollections.observableArrayList(item -> new javafx.beans.Observable[] {item.isSelectedProperty()});
list.addAll(DirList.getDirList());
dirList.setItems(list);
list.addListener(new ListChangeListener<Dir>() {
#Override public void onChanged(javafx.collections.ListChangeListener.Change<? extends Dir> c) {
while (c.next()) {
if (c.wasUpdated()) {
// Do something
}
}
}
});
I see you have a model named Dir with a BooleanProperty named selected
You can simply let the cellValueFactory just:
selectedColumn.setCellValueFactory(cellData -> cellData.getValue().isSelectedProperty());
This would update the model's property if you check/un-check the CheckBox in the table cell.
If you use this selected/deselected state of the checkbox, you can use the model, and add the listener there, you will get the same result. Then the listener will be added just once.
You should have instances of Dir created then you can do the following for ex. in initialize:
dir1.selectedProperty().addListener((observable, oldValue, newValue) -> {
dir2.selectedProperty().set(oldValue);
});
Or anything you want, then you are sure that the listener is added just once.
As #Jai mentioned, the cell data is used many time and every time the CallBack is called, the listener is added to the property, so don't use it inside that method if you want to avoid adding the listener many times to the property.
Related
I am trying to implement a TreeTableView in JavaFX, containing 'MyData' objects, and having two columns. First column should contain a string; this was easy:
column1.setCellValueFactory((TreeTableColumn.CellDataFeatures<MyData, String> entry)
-> new ReadOnlyStringWrapper(entry.getValue().getValue().toString()));
For the second column, I need to use some more complex data within the MyData object, and I want to render basically a sequence of icons that depict that data. So, I tried to create a custom cell renderer:
MyCellRenderer extends TreeTableCell<MyData, MyData> {
#Override
protected void updateItem(MyData item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setGraphic(null);
setText(null);
} else {
// building some ContentPane with an HBox of Images here..
setGraphic(contentPane);
}
}
}
and then set the column CellFactory and CellValueFactory as follows:
column2.setCellValueFactory((TreeTableColumn.CellDataFeatures<MyData, MyData> entry)
-> new ReadOnlyObjectWrapper(entry));
column2.setCellFactory(param -> new MyCellRenderer());
But I get this exception at runtime:
Exception in thread "JavaFX Application Thread"
java.lang.ClassCastException:
javafx.scene.control.TreeTableColumn$CellDataFeatures cannot be cast
to MyData
I am afraid I don't really understand the meaning of the different generic types for all these classes, and also I am not sure about the "ReadOnlyObjectWrapper". I just tried to copy/paste and tweak it from the setup of the first column.
I would be very thankful if someone could shine some light on me. Unfortunately the oracle docs about TreeTableView don't go into that much detail, they just show simple examples.
Thank you
You're passing entry, which is of the type TreeTableColumn.CellDataFeatures<MyData, MyData>, as the initial value to a new ReadOnlyObjectWraper - a raw type - which is expecting a type of MyData at runtime and not TreeTableColumn.CellDataFeatures<MyData, MyData>. As you can see, there is a mismatch of generic types.
Try changing
new ReadOnlyObjectWrapper(entry)
to
new ReadOnlyObjectWrapper<>(entry.getValue().getValue())
The reason for two getValue() calls is because the first entry.getValue() returns a TreeItem<MyData> and the second getValue() returns the actual MyData instance.
This is all assuming that your table is declared TreeTableView<MyData> and your column is declared TreeTableColumn<MyData, MyData>.
Edit: Since you said you don't really understand all the generic signatures here's a brief explanation.
TreeTableView<S>
Here the S is the type of object the TreeTableView displays. AKA, the model class. An example would be a Person class which would make S a Person.
TreeTableColumn<S, T>
The S here is the same as the S in the TreeTableView that the column is destined to be a part of. The T is the type of object that a TreeTableCell in the column will be displaying. This is normally a value contained within a property of the type S. Such as a StringProperty for a name of a Person which would make T a String.
TreeTableCell<S, T>
The S and T will be the same as the TreeTableColumn which the cell will be a part of.
Now, for the value callback:
Callback<TreeTableColumn.CellDataFeatures<S, T>, ObservableValue<T>>
Again, the S and T represent the same types of the TreeTableColumn for which the Callback will belong to. This Callback returns an ObservableValue that contains the type T so that the TreeTableCell can observe the value for changes and update the UI accordingly. In your case, since the type you want to display is not held in a property you return a new ReadOnlyObjectWrapper to satisfy the API requirements. If I continue the name StringProperty example I gave above you could end up with something like:
TreeTableView<Person> table = ..;
TreeTableColumn<Person, String> column = ...;
column.setCellValueFactory(dataFeatures -> {
// This could all be done in one line but I figured I'd
// make it explicit to show all the types used.
TreeItem<Person> item = dataFeatures.getValue();
Person person = item.getValue();
return person.nameProperty(); // returns StringProperty which is an
// ObservableStringValue which in turn
// is an ObservableValue<String>
});
You need
column2.setCellValueFactory((TreeTableColumn.CellDataFeatures<MyData, MyData> entry)
-> new ReadOnlyObjectWrapper<>(entry.getValue().getValue()));
Note that if you don't use raw types (i.e. use ReadOnlyObjectWrapper<> or ReadOnlyObjectWrapper<MyData>, instead of just ReadOnlyObjectWrapper), the compiler will inform you of the error, which is much better than trying to decipher what went wrong at runtime.
As you can see, the parameter type for the cell value factory is a TreeTableColumn.CellDataFeatures (see docs). This is simply a wrapper for the row value from which you're going to extract the data that are shown in the cell; this wrapper just contains the tree item for the row itself (which you get with getValue()), as well as the column to which the cell value factory is attached (getTreeTableColumn()) and the table to which that column belongs (getTreeTableView()).
The latter two, I believe, are designed to enable you to write general, reusable, cell value factories, which you might want to customize on the basis of the column or table to which they're attached. (Use cases for this are hard for me to envisage, but nevertheless I suspect there is some occasion for them...)
The TreeItem containing the row (which you get with entry.getValue()), of course contains the row value itself (you get this with getValue(), which is why you end up with entry.getValue().getValue()), as well as other TreeItem-specific information (is it expanded, selected, etc etc).
I am facing problem in updating values in my table view.
I have a for loop which updates all my rows values. I am using StringProperty as my model class field types. When I update a single row it works fine. But my requirement is I need to stop for 1000 millisec in each row after updation. I tried using Thread.sleep(1000) in my for loop which is not helping :(
Don't try to manipulate your Thread, it will be too complicate.
Simply use a TimeLine like this :
timer = new Timeline(new KeyFrame(Duration.millis(1000), new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
//Pause your timer in order to update your row
timer.pause();
//Do your update (increment your rowIndex inside)
updateRowMethod(rowIndex);
//Then launch the Timer and exit
timer.play();
}
}));
I use the pause method because your said you wanted to wait 1000 milliseconds after the edition of a line. If you just want to edit a line every second, skip the pause/play part.
I am implementing drag and drag between two tree views. When a treeItem is dropped onto another treeView of treeItem a line connection is established between the two treeItems. This is working fine , but to have a connection initially without a drag and drop events is problem to me.
I am using treeCell for the drag and drop events.
final var treeCells = treeView.lookupAll( ".tree-cell" );
final var cells = new ArrayList<>( treeCells );
final var row = treeView.getRow( n );
final var node = cells.get( row );
if( node instanceof TreeCell ) {
#SuppressWarnings("rawtypes")
final var cell = (TreeCell) node;
System.out.println( "TREE CELL: " + cell );
}
As I thought, this turns out to be pretty difficult.
There is, by design, no way to get the TreeCell belonging to a given TreeItem. This is because of the "virtualization" design of the TreeView: a minimal number of TreeCells are created and these are updated with new TreeItems as required. Thus there typically won't be a TreeCell for every TreeItem, and the TreeItem represented by a given TreeCell may change at any point.
To make this work, first create an observable collection storing all the connections between the trees (e.g. an ObservableSet should work well). Represent the connections by some class that exposes start and end points which can be used for the lines.
Create custom cell factories for the trees. The cells they return should:
observe the item they are representing. If the item changes to one that is at an end of one or more connections, then the appropriate point on those connections should be bound to the appropriate transform of the coordinates of the cell.
If the item changes from one that is at the end of one or more connections, then unbind the appropriate end from the cell coordinates
observe the observable collection of connections. If one is added for which this cell's item is one end, then bind the coordinates as above
Note that when you bind the coordinates, you need to take into account the fact that the cells may move (e.g. via scrolling or via other changes in GUI layout). You also need to transform the coordinates from the cell's own coordinate system into the coordinate system of whichever pane is holding the connections (obviously, if these are connecting one tree to another, it must be some common scene graph ancestor of both trees).
And finally, you need some housekeeping. The connections need to make sure they either become invisible, or are removed from the scene if they are no longer bound at one or more ends.
I created an example. I just created some simple controls for generating the connections, but you could easily do this with drag and drop instead. The class encapsulating the view of the connection is AssignmentView; it uses Assignment to represent the actual data that is connected. The ConnectedTrees class is the main application and most of the interesting controller-type work is in there. The remaining classes are just data representation. The example is all Java 8; I think it would be much uglier in JavaFX 2.2.
This solution uses recursion calls to traverse the nodes tree of the tree view.
Normally this recursion shouldn't be dangerous as there is only limited nodes number (TreeCell's instances are reused by TreeView):
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import java.util.List;
public class TreeViewExplore {
/**
* Returns a cell for the tree item given. Or null.
*
* #param treeItem tree item
* #param treeView tree view
* #return tree cell
*/
public static TreeCell findCellByItem(TreeItem treeItem, TreeView treeView) {
return recursiveFindCellByItem(treeItem, treeView);
}
private static TreeCell recursiveFindCellByItem(TreeItem treeItem, Node node) {
if (node.getStyleClass().contains("tree-cell")
&& TreeCell.class.isAssignableFrom(node.getClass())
&& ((TreeCell) node).getTreeItem() == treeItem)
return (TreeCell) node;
if (!Parent.class.isAssignableFrom(node.getClass()))
return null;
List<Node> nodes = ((Parent) node).getChildrenUnmodifiable();
if (nodes == null) return null;
for (Node n : nodes) {
TreeCell cell = recursiveFindCellByItem(treeItem, n);
if (cell != null) return cell;
}
return null;
}
}
Usage:
// TreeItem treeItem = ...
TreeCell cell = TreeViewExplore.findCellByItem(treeItem, treeView);
// Check result:
System.out.println(
cell == null ? "cell is null" :
"(cell.getTreeItem() == treeItem) = "
+ (cell.getTreeItem() == treeItem));
Yet another solution using lookupAll() method. It is only for example here as it looks non very efficient for me because this method collects all nodes with CSS-selector given (and traverses all over the tree in any case):
public static TreeCell findCellByItem(TreeItem treeItem, TreeView treeView) {
return (TreeCell) treeView.lookupAll(".tree-cell").stream()
.filter(n -> ((TreeCell) n).getTreeItem() == treeItem)
.findFirst()
.orElse(null);
}
I am looking for a way to get the selected cell of a TableView control. Note that I don't just want the cell value, I want an actual TableCell object. One would expect that the method:
tableView.getSelectionModel().getSelectedCells().get(0)
does just that, but it returns a TablePosition object, which gives you row and column information, but I don't see a way to get TableCell object from that.
The reason I need this is because I want to respond to a key press, but attaching an event filter to TableCell does not work (probably because it is not editable). So I attach it to TableView, but then I need to get the currently selected cell.
EDIT: For future readers: DO NOT mess with TableCell objects, except in cell factory. Use the TableView the way designers intended, or you will be in lot of trouble. If you need data from multiple sources in single table, it is better to make a new class that aggregates all the data and use that as a TableView source.
I just posted an answer that uses this code to edit a Cell. I don't think you can get a reference to the actual table cell as that's internal to the table view.
tp = tv.getFocusModel().getFocusedCell();
tv.edit(tp.getRow(), tp.getTableColumn());
Your method also returns a TablePosition so you can use that as well.
Here's the link https://stackoverflow.com/a/21988562/2855515
This will probably get downvoted because the OP asked about returning the cell itself, rather than what I'll describe, but a Google search led me here for my issue.
I personally ran into issues trying to retrieve data from an individual cell.
java.is.for.desktop offered buggy code related to this matter, that throws an ArrayIndexOutOfBoundsException, but is on the right track. My goal is to offer a better example of that using a lambda.
To access data from a single TableCell:
tableView.getFocusModel().focusedCellProperty().addListener((ObservableValue<? extends TablePosition> observable, TablePosition oldPos, TablePosition pos) -> {
int row = pos.getRow();
int column = pos.getColumn();
String selectedValue = "";
/* pos.getColumn() can return -1 if the TableView or
* TableColumn instances are null. The JavaDocs state
* this clearly. Failing to check will produce an
* ArrayIndexOutOfBoundsException when underlying data is changed.
*/
if ((pos.getRow() != -1) && (pos.getColumn() != -1))
{
selectedValue = tableView.getItems()
.get(row)
.get(column);
if ((selectedValue != null) && (!selectedValue.isEmpty()))
{
// handling if contains data
}
else
{
// handling if doesn't contain data
}
}
});
Edit:
I meant to say ArrayIndexOutOfBoundsException, rather than NullPointerException, I updated this answer to reflect that. I also cleaned up spelling and grammar.
You want to respond to key press? Better don't.
Instead, you could register a listener for focusing of table cells, which would work with arrow keys and mouse clicks on table cells (and even with touch events, oh my, the future is already there).
table.getFocusModel().focusedCellProperty().addListener(
new ChangeListener<TablePosition>() {
#Override
public void changed(ObservableValue<? extends TablePosition> observable,
TablePosition oldPos, TablePosition pos) {
int row = pos.getRow();
int column = pos.getColumn();
String selectedValue = "";
if (table.getItems().size() > row
&& table.getItems().get(row).size() > column) {
selectedValue = table.getItems().get(row).get(column);
}
label.setText(selectedValue);
}
});
In this example, I am using a "classic" TableView with List<String> as column model. (So, your data type could be different than String.) And, of course, that label is just an example from my code.
I have an AdvancedDataGrid with a ArrayCollection as its dataProvider. For instance i have a CheckBox that allows me to show or hide certain rows in the AdvancedDataGrid.
Any idea how i could do that?
My suggestion would be to use your data provider's filterFunction property. Basically, you can give your data provider a function that will determine whether a given item in the ArrayCollection is excluded or not (if an item is excluded, it won't be displayed in the AdvancedDataGrid, in essence making it "invisible"). The docs for filterFunction can be found here.
What I'd suggest then is that checking the checkbox sets a property on the object in your data provider, which is then used by your filter function to include/exclude rows. Some (very rough) pseudocode follows:
private function checkboxClickHandler( event:MouseEvent ):void
{
/*
Based on the MouseEvent, determine the row
in the data grid you're dealing with
*/
myDataProvider[someIndex].checkboxFlag = myCheckBox.selected;
myDataProvider.refresh(); // calling refresh() re-applies the filter to
// account for changes.
}
private function myDataProviderFilterFunction( item:Object ):Boolean
{
// assuming we want the item to be filtered if checkboxFlag is true
return !item["checkboxFlag"];
}