I am currently trying to show the index of my ObservableList to a TableView column. The only solutions I have found are for creating superficial indexes. But I need the actual index so that I may sort the TableView accordingly.
cargoTable.setItems(um.getCargoList());
cargoTypeColumn.setCellValueFactory(new PropertyValueFactory<Cargo, CargoType>("cargoType"));
ownerColumn.setCellValueFactory(new PropertyValueFactory<Cargo, Customer>("owner"));
ownerColumn.setComparator(new Comparator<Customer>() {
#Override
public int compare(Customer o1, Customer o2) {
return o1.getName().compareToIgnoreCase(o2.getName());
}
});
sizeColumn.setCellValueFactory(new PropertyValueFactory<Cargo, Integer>("size"));
propertiesColumn.setCellValueFactory(new PropertyValueFactory<Cargo, String>("properties"));
hazardsColumn.setCellValueFactory(new PropertyValueFactory<Cargo, Hazard>("hazards"));
depositDateColumn.setCellValueFactory(new PropertyValueFactory<Cargo, Date>("date"));
My TableView Observes a List with Cargoes and every column in the TableView represents a property of the Cargo inside the List. Everything works perfectly but I just cant seem to be able to figure a way to use a column showing the index of this list.
Any help would be appreciated! Thanks in Advance!
This is untested.
You can use a custom cellValueFactory for your column and have some different logic in it. Something similar to:
indexCol.setCellValueFactory(data -> {
Cargo item = data.getValue();
int index = table.getItems().indexOf(item);
return new SimpleStringProperty(Integer.toString(index));
});
This basically says for this column, give me the item this cell needs to look at, and look up its index based on the list set to the table, then return it.
This should work, and of course you will need to edit for class and list correctness, etc. but the idea still stands.
Hope this works for you.
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 found a "Simple alternate solution" in auto numbered table rows (javafx) for auto-incrementing values
This works well. However, if I sort the column in the table, this is not getting sorted. Any fix for this?
Also, according the solution if I delete a row in the middle and write the table contents to a file, the row number is adjusted in the table view but not in the file. The row will be deleted in the file but the auto-incremented value does not get adjusted. Please help.
If you want a persistent numbering which is attached to the data objects so that the numbers get sorted with the data objects, then you should create a data item which includes the numbering.
For example:
public class IdentifiedPerson {
private IntegerProperty id;
private ObjectProperty<Person> person;
public IdentifiedPerson(int id, Person person) {
this.id = new SimpleIntegerProperty(id);
this.person = new SimpleObjectProperty<>(person);
}
public IntegerProperty idProperty() {
return id;
}
public ObjectProperty<Person> personProperty() {
return person;
}
}
Then your TableView is defined as:
private TableView<IdentifiedPerson> table = new TableView<IdentifiedPerson>();
And you provide cell value factories for each of your table columns to supply the relevant data (either the person id or some person attribute).
So the id/row number essentially becomes part of your object model. If you can modify the Person object directly, then you can stick the id in there rather than having the wrapper class for the person id + person object.
Anyway, I think the above info will help you solve your issues. If not, or if you can't come up with a working example yourself, add some further comments or edit the question and maybe somebody can put an example together for you.
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 a working Qtableview with custom model subclassed QAbstractTableModel and QAbstractItemModel.
I have a Qlineedit, onclicked it will filter the view:
// model.cpp
setFilter(QString strFilter) function searches trough my intern QList (this Qlist is actually attached to model) and if match found then: m_filterSet.insert(i);
This all works great. Problem is, i have CRUD operations for the tableview (insert row, delete row..) which also work great! But when selecting a row from a filtered set, i need to somehow know where in my QList exactly is this selected row from the filtered set (QSet ).
ui.myView->selectionModel()->currentIndex().row();
obvious gives the wrong indexes counting for the current view.
How can i somehow extract the value (int) from the selected row in the QSet?
Because when i added this function to model:
foreach (const int &value, m_filterSet)
qDebug() << value;
It has printed out successfully all the i values, e.g: 3410, 3411, 3412 (those are my client id's)
If i could extract this ID for the selected row in Qset, i could write a function that iterates my intern QList, and find a matching, so to speak:
if(m_Intern[i].nClientID == nId){ // nId = value inside Qset for selected row in view
return nIdx;
}
Qt has a solution for your problem - just use QSortFilterProxyModel. You will need to:
Subclass it and write your own filtering function (filterAccpetsRow)
Proxy your original model through filtering one
Attach filtering model to a view
use QSortFilterProxyModel::mapToSource() to convert between indexes in filtered and original model.
This allows you to have more than one view with just one source data model, each view may have different filters.
I solved it after a while re thinking, i just needed to implement another function inside my model:
int myClass::screenIndex2DataIndex(int nIdxScreen)
{
if(m_bUseFilter)
{
int nIdx =-1;
for(int i=0;i<m_lstIntern.size();i++)
{
if(m_filterSet.contains(i))
{
nIdx++;
if(nIdx == nIdxScreen){
return i;
}
}
}
return -1; //not found
}
else{
return nIdxScreen;
}
}
This way i can find out for the present index on the filtered view, where it is in my intern list.
After this it's easy to get my nClientID trough a return: return m_lstIntern[idx].nClientId
I am new to vaadin and have a databinding problem. I have posted allready in the vaadin forum, but no answer up to now.
if you answer here, I will of course reward it anyway.
https://vaadin.com/forum/-/message_boards/view_message/1057226
thanks in advance.
greets,
Andreas
Additional information: I tried allready to iterate over the items in the container, after pressing a save button. After deleting all original elements in the model collection, and adding copies from the container, the GUI breaks. Some other GUI elements do not respond anymore.
I have personally never used ListSelect, but I found this from the API docs:
This is a simple list select without, for instance, support for new items, lazyloading, and other advanced features.
I'd recommend BeanItemContainer. You can use it like this:
// Create a list of Strings
List<String> strings = new ArrayList<String>();
strings.add("Hello");
// Create a BeanItemContainer and include strings list
final BeanItemContainer<String> container = new BeanItemContainer<String>(strings);
container.addBean("World");
// Create a ListSelect and make BeanItemContainer its data container
ListSelect select = new ListSelect("", container);
// Create a button that adds "!" to the list
Button button = new Button("Add to list", new Button.ClickListener() {
public void buttonClick(ClickEvent event) {
container.addBean("!");
}
}
// Add the components to a layout
myLayout.addComponent(button);
myLayout.addComponent(select);
The downside (or benefit, it depends :) of this is that you can't add duplicate entries to a BeanItemContainer. In the example above the exclamation mark gets only added once.
You can get a Collection of Strings by calling:
Collection<String> strings = container.getItemIds();
If you need to support duplicate entries, take a look at IndexedContainer. With IndexedContainer you can add a String property by calling myIndexedContainer.addContainerProperty("caption", String.class, ""); and give each Item a unique itemId (or let the container generate the id's automatically).
Im not sure I understand your problem but I belive that it might be that you haven't told the controller to repaint. You do this be setting the datasource like this after the save event has occured.
listSelect.setContainerDataSource(listSelect.getContainerDataSource());