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.
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 have a simple JavaFX Table with a column like this:
#FXML
private TableColumn<PropertyModel, String> columnPropertyProdValue;
Within the initialize method I used this
columnPropertyProdValue.setCellFactory(TextFieldTableCell.forTableColumn());
in order to get an textarea at a double click event on this column. This is working fine.
The column should store different property values but the datatype depends on a different column in the table. The first column "datatype" defines the datatype like boolean, string or integer and the property column should store its value. But in the moment it is always a String.
Its fine for me if I store the property value as a String in the database but the application should check for correctness of the datatype at runtime.
Does anyone has a good idea how to realize that?
Thanks a lot
Hauke
For TextInputControl and subclasses you can apply TextFormatter. This class allows you to control input. For example (for float number):
setTextFormatter(new TextFormatter<String>(
s -> {
if (s.getControlNewText().isEmpty())
return s;
try{
Float.parseFloat(s.getControlNewText());
return s;
} catch (NumberFormatException e) {
return 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.
There are a few posts about this, but after hours of searching I still can't find what I need.
The answer in the following post almost gets me what I want:
Combobox for Foreign Key in DataGridView
Question 1:
Going off that example where a Product has many Licenses, my database mappings are all many-to-one relationships which means my License class holds a reference to the Product class. The License class does not have a property for the ProductId since that can be retrieved via the Product reference. I don't want to muck up the License class with both a reference to Product and a ProductId property just to make binding in the UI easier.
Because of this I can't set the DataPropertyName to an Id field. It needs to be the class reference name like so:
DataGridViewComboBoxColumn dataGridViewComboBoxColumn =
(DataGridViewComboBoxColumn)myDataGridView.Columns("LicenseComboBoxColumn");
dataGridViewComboBoxColumn.DataPropertyName = "License"; // not LicenseID
****Update****
I was able to get this to partially work without creating the ProductId property by specifying the Product.Id as the DataPropertyName like so:
dataGridViewComboBoxColumn.DataPropertyName = "License.Id";
However, when doing so, it broke databinding which caused me to manually get and set the cell value.
I've also seen posts about binding to the DataGridView cell, but databinding breaks when I do that and the datasource itself is never updated:
// populate the combo box with Products for each License
foreach (DataGridViewRow row in myDataGridViewProducts.Rows)
{
IProduct myProduct = row.DataBoundItem as IProduct;
DataGridViewComboBoxCell cell = (DataGridViewComboBoxCell)row.Cells("myProductCol");
cell.DataSource = getListOfILicenseObjectsFromDao(myProduct.Id);
cell.Value = myProduct.License.Id;
}
Maybe I'm doing something wrong, or maybe there's a different way. Can anyone help here?
Question 2:
How do I display a different list of Licenses for each Product?
In other words, the combobox list of Licenses will be different for each Product in the grid. I'd like to do this using databinding so I don't have to get and set the values myself.
I found the answer myself. I had this same issue a while ago and found the solution in some old code I dug up. The solution was to add a Self property to the object I wanted to databind to in the combobox (in the example above it would be the License class) and use that property as the ValueMember like so:
foreach (DataGridViewRow row in myDataGridViewProducts.Rows)
{
IProduct myProduct = row.DataBoundItem as IProduct;
DataGridViewComboBoxCell cell = (DataGridViewComboBoxCell)row.Cells("myProductCol");
cell.DataSource = getListOfILicenseObjectsFromDao(myProduct.Id);
cell.DataPropertyName = "License";
cell.DisplayMember = "Name";
cell.ValueMember = "Self"; // key to getting the databinding to work
// no need to set cell.Value anymore!
}
The License class now looks like this:
Public class License
{
public string Name
{
get; set;
}
public ILicense Self
{
get { return this; }
}
// ... more properties
}
Granted I had to "muck" up the Business classes with a property named Self, but that's much better (less confusing to the programmer) than having both a reference to License and a LicenseId property in the Product class IMO. Plus it keeps the UI code much much simpler as there's no need to manually get and set the values - just databind and done.
When sorting on a column in the GridView bound to an Enum, it sorts by the order of the Enum. I need it to sort by the string representation of the Enum. Here are the options I have seen, of which I like none of them.
Reorder the Enum in alphabetical order - Bad because now the presentation is relying on the Business and Data Access Layer to "pre-sort" the data.
Create a new object (datatable, new list, whatever) with myEnum.ToString() and bind this to the GridView - This one is not bad, but I would rather not.
In my search, check to see if the column sorted is an Enum, then sort by the string representation of the column - Do I have to say why this is bad?
Number 2 would be my favorite so far, but like I said, I don't like it.
More info just in case - I am binding a List of IWhatever to the grid, and 2 columns are enums that need to be sorted by strings. There are also guid-type, string, and decimal columns in the grid that need to be sorted.
Try to use ViewModels. Basically you create ViewModel Objects that contains your Model Object (your IWhatever). This ViewModel exposes then new Properties and Methods which are used in your View. In Your case you would expose a property with the string representation of your Enum. The advantage is, that you could do any transformation logic you want.
See MVVM Pattern. http://en.wikipedia.org/wiki/Model_View_ViewModel
EDIT: Little Example:
public class WhateverViewModel
{
public WhateverViewModel(IWhatever model)
{
this.model = model;
}
...
public string MyEnumView
{
get
{
return model.MyEnum.ToString();
}
}
public string MyEnumView2
{
get
{
switch(model.MyEnum)
{
case MyEnumType.A: return "Hello";
case MyEnumType.B: return "World";
}
}
}
}