This has baffled me for a while now and I cannot seem to get the grasp of it. I'm using Cell Value Factory to populate a simple one column table and it does not populate in the table.
It does and I click the rows that are populated but I do not see any values in them- in this case String values. [I just edited this to make it clearer]
I have a different project under which it works under the same kind of data model. What am I doing wrong?
Here's the code. The commented code at the end seems to work though. I've checked to see if the usual mistakes- creating a new column instance or a new tableview instance, are there. Nothing. Please help!
//Simple Data Model
Stock.java
public class Stock {
private SimpleStringProperty stockTicker;
public Stock(String stockTicker) {
this.stockTicker = new SimpleStringProperty(stockTicker);
}
public String getstockTicker() {
return stockTicker.get();
}
public void setstockTicker(String stockticker) {
stockTicker.set(stockticker);
}
}
//Controller class
MainGuiController.java
private ObservableList<Stock> data;
#FXML
private TableView<Stock> stockTableView;// = new TableView<>(data);
#FXML
private TableColumn<Stock, String> tickerCol;
private void setTickersToCol() {
try {
Statement stmt = conn.createStatement();//conn is defined and works
ResultSet rsltset = stmt.executeQuery("SELECT ticker FROM tickerlist order by ticker");
data = FXCollections.observableArrayList();
Stock stockInstance;
while (rsltset.next()) {
stockInstance = new Stock(rsltset.getString(1).toUpperCase());
data.add(stockInstance);
}
} catch (SQLException ex) {
Logger.getLogger(WriteToFile.class.getName()).log(Level.SEVERE, null, ex);
System.out.println("Connection Failed! Check output console");
}
tickerCol.setCellValueFactory(new PropertyValueFactory<Stock,String>("stockTicker"));
stockTableView.setItems(data);
}
/*THIS, ON THE OTHER HAND, WORKS*/
/*Callback<CellDataFeatures<Stock, String>, ObservableValue<String>> cellDataFeat =
new Callback<CellDataFeatures<Stock, String>, ObservableValue<String>>() {
#Override
public ObservableValue<String> call(CellDataFeatures<Stock, String> p) {
return new SimpleStringProperty(p.getValue().getstockTicker());
}
};*/
Suggested solution (use a Lambda, not a PropertyValueFactory)
Instead of:
aColumn.setCellValueFactory(new PropertyValueFactory<Appointment,LocalDate>("date"));
Write:
aColumn.setCellValueFactory(cellData -> cellData.getValue().dateProperty());
For more information, see this answer:
Java: setCellValuefactory; Lambda vs. PropertyValueFactory; advantages/disadvantages
Solution using PropertyValueFactory
The lambda solution outlined above is preferred, but if you wish to use PropertyValueFactory, this alternate solution provides information on that.
How to Fix It
The case of your getter and setter methods are wrong.
getstockTicker should be getStockTicker
setstockTicker should be setStockTicker
Some Background Information
Your PropertyValueFactory remains the same with:
new PropertyValueFactory<Stock,String>("stockTicker")
The naming convention will seem more obvious when you also add a property accessor to your Stock class:
public class Stock {
private SimpleStringProperty stockTicker;
public Stock(String stockTicker) {
this.stockTicker = new SimpleStringProperty(stockTicker);
}
public String getStockTicker() {
return stockTicker.get();
}
public void setStockTicker(String stockticker) {
stockTicker.set(stockticker);
}
public StringProperty stockTickerProperty() {
return stockTicker;
}
}
The PropertyValueFactory uses reflection to find the relevant accessors (these should be public). First, it will try to use the stockTickerProperty accessor and, if that is not present fall back to getters and setters. Providing a property accessor is recommended as then you will automatically enable your table to observe the property in the underlying model, dynamically updating its data as the underlying model changes.
put the Getter and Setter method in you data class for all the elements.
Related
I'm using JavaFX 17 to make an editable table. The table data comes from an observable list of MyCustomClass objects. I then made all cells editable by setting the cell factory of each column to TextFieldTableCell. So far so good. Setter function receives a CellEditEvent as expected; I can get the object that the row's data originated from, the column that was changed, the values that were changed.
#FXML
private void onEdit(TableColumn.CellEditEvent<MyCustomClass, String> editedCell) {
MyCustomClass object = cell.getRowValue();
String ValueBeforeUserMadeEdit = cell.getOldValue();
String valueThatIsNowShowing = cell.getNewValue();
}
Now the bad news. The event object does not have a function for indicating which property (or ideally, which property setter) should be used to update the value inputted by the user (i.e. the property that relates to the changed column). I originally gave the property name to the cell in a PropertyValueFactory, which has a function for getting that String. However, I can't find a way to get the property value factory from the cell, and even if I did it seems like too much work to then find the property setter from that string.
It would be easier to create a subclass of TextFieldTableCell that stores a reference to the correct setter, but I am hoping someone can tell me if there is built in functionality for this. Seems like there should have been, even at version 17. I'm a student, and really trying to understand this stuff, so any help at all is really appreciated!
Handler per Column
There's another approach, if you really need to define your own on-edit-commit handlers. It would look something like this:
import javafx.fxml.FXML;
import javafx.scene.control.TableColumn;
public class Controller {
#FXML private TableColumn<Foo, String> firstNameCol;
#FXML private TableColumn<Foo, String> lastNameCol;
#FXML
private void initialize() {
firstNameCol.setOnEditCommit(e -> e.getRowValue().setFirstName(e.getNewValue()));
lastNameCol.setOnEditCommit(e -> e.getRowValue().setLastName(e.getNewValue()));
}
}
When you do it this way, you know exactly which setter to call because each column gets its own on-edit-commit handler (and columns are associated with a specific property). I personally would prefer this approach.
Get Cell's ObservableValue
Given this method is annotated with #FXML, I assume you're trying to use this one method as the implementation for the on-edit-commit handler of multiple columns. This can complicate things, but what you want is possible:
#FXML
private void onEditCommit(TableColumn.CellEditEvent<MyCustomClass, String> event) {
TableColumn<MyCustomClass, String> column = event.getTableColumn();
MyCustomClass item = event.getRowValue();
ObservableValue<String> observable = column.getCellObservableValue(item);
if (observable instanceof WritableValue<String> writable) {
writable.setValue(event.getNewValue());
}
}
Note: I did not write this in an IDE, so there may be some slight syntax errors. But it should compile, at least on newer versions of Java.
But note this is essentially what the default implementation does. And note that the existence of this default on-edit-commit handler is documented:
By default the TableColumn edit commit handler is non-null, with a default handler that attempts to overwrite the property value for the item in the currently-being-edited row.
So, unless you need to change the default behavior, you likely don't need to worry about implementing your own on-edit-commit handler.
Potential Issues
The above requires that the cellValueFactory returns an instance of WritableValue. And this WritableValue must be linked to the model's property. This should be no problem if your model class exposes JavaFX properties like so:
public class Person {
private final StringProperty name = new SimpleStringProperty(this, "name");
public final void setName(String name) { this.name.set(name); }
public final String getName() { return name.get(); }
public final StringProperty nameProperty() { return name; }
}
Note: If your model uses JavaFX properties then I suggest using lambda expressions instead of PropertyValueFactory. Check out Why should I avoid using PropertyValueFactory in JavaFX?.
Otherwise, PropertyValueFactory will return a ReadOnlyObjectWrapper that is divorced from the model's property after getting the current value. In other words, even though ReadOnlyObjectWrapper does implement WritableValue, setting the property will not forward the new value to the model item.
If you cannot or are unwilling to modify your model to use JavaFX properties, then you can use a different cell-value factory implementation than PropertyValueFactory. For example:
import javafx.beans.property.adapter.JavaBeanObjectPropertyBuilder;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.TableColumn;
import javafx.util.Callback;
public class JavaBeanValueFactory<S, T> implements Callback<TableColumn.CellDataFeatures<S, T>, ObservableValue<T>> {
private final String propertyName;
public JavaBeanValueFactory(String propertyName) {
this.propertyName = propertyName;
}
#Override
#SuppressWarnings("unchecked")
public ObservableValue<T> call(TableColumn.CellDataFeatures<S, T> param) {
try {
return JavaBeanObjectPropertyBuilder.create().bean(param.getValue()).name(propertyName).build();
} catch (NoSuchMethodException ex) {
throw new RuntimeException(ex);
}
}
}
Then replace new PropertyValueFactory<>("foo") with new JavaBeanValueFactory<>("foo").
Or you could do something like this:
// where 'firstNameCol' is e.g., a TableColumn<Person, String>
firstNameCol.setCellValueFactory(data -> new SimpleStringProperty(data.getValue().getFirstName()) {
final Person item = data.getValue();
#Override
protected void invalidated() {
item.setFirstName(get());
}
});
Or anything you can think of where the property will forward new values to the model.
my problem is that I can't find the way to bind a combobox, my goal is to autocomplete a combobox after clicking on a table (Grid):
The combobox data is taken from the BD Acception table, but the table data is taken from the WordAcception class
These are the errors it shows me:
[enter image description here][1]
[enter image description here][2]
WORDACCEPTION.java
public class WordAcception implements Serializable, Cloneable {
private String idacception = "";
public String getIdacception() {
return idacception;
}
public void setIdacception(String idacception) {
this.idacception = idacception;
}
}
VISTA.java
private ComboBox<Acception> WordAcceptioncombo = new ComboBox<>("idacception");
WordAcceptioncombo.setItemLabelGenerator(Acception::getIdAcception);
WordAcceptioncombo.setItems(AcceptionPersistence.getInstance().findAllIdAcception());
add(WordAcceptioncombo);
wordacceptionGrid.asSingleSelect().addValueChangeListener(event ->
formword.setWordAcception(wordacceptionGrid.asSingleSelect().getValue()));
WORDACCEPTIONFORM.java
binder.bind(WordAcceptioncombo, Acception::getIdAcception,
Acception::setIdAcception);
public void setWordAcception(WordAcception wordAcception) {
if(wordAcception != null) {
System.out.println("setWordAcception= "+wordAcception.getIdacception());
WordAcceptioncombo.setValue(wordAcception.getIdacception());
}
binder.setBean(wordAcception);
if (binder.getBean() == null) {
setVisible(false);
} else {
setVisible(true);
idacception.focus();
}
}
Aception.java
public class Acception implements Serializable, Cloneable {
private String idAcception = "";
public String getIdAcception() {
return idAcception;
}
public void setIdAcception(String idAcception) {
this.idAcception = idAcception;
}
}
/////////////UPDATE///////////
The combobox is completed thanks to the Acception class, for that reason I had to create an Acception instance in the WordAcception class in order to obtain that instance of the class (this has hidden the problems from me, but the combobox still does not autofill)
ACCEPTION.JAVA
public class Acception implements Serializable, Cloneable {
private Clase clase;
public Clase getClase() {
return clase;
}
public void setClase(Clase clase) {
this.clase = clase;
}
WORDACCEPTIONFORM.JAVA
binder.bind(WordAcceptioncombo, WordAcception::getAcception,
WordAcception::setAcception);
BUT I DON'T NOTICE ANY CHANGE, THE COMBOBOX STILL NOT FILLED
You usually get that kind of is not applicable for the arguments error from .bind when you are using a getter and setter for a value that is a different type than your field. In your case WordAcception.idAcception is type String but your ComboBox is set to take in items of type Acception.
If you want a ComboBox that gives you the option of selecting which particular id belongs to this specific WordAcception and for changing that id (without changing the WordAcception instance), the type of the ComboBox should be String. If you want a ComboBox that selects a specific Acception for you, you need to use a getter and setter for a field of that type.
Unrelated to the error, I'm not quite certain if there is supposed to be some connection between Acception and WordAcception, but based on the examples currently there is none. Did you mean for one of them to extend the other?
I am trying to use the "Grid" vaadin component to simply display a list of POJO. To populate the POJO list I use the DataProvider.fromCallbacks and set a Spring service to it. The grid is correctly displayed but the content is empty. When I run the app in debug mode I can see that the callback method is never called, that's why the grid is empty.
It's a basic use of grid so i really don't understand why the callback isn't used.
Here is my layout :
#SpringComponent
#Route("")
#PWA(name = "Callcenter dashboard", shortName = "callcenter")
public class CallcenterConfigurationView extends VerticalLayout {
#Autowired
private ConfigurationController configurationControler;
private Grid<CallCenterModel> grid;
public CallcenterConfigurationView() {
this.grid = new Grid<CallCenterModel>();
grid.addColumn(CallCenterModel::getDescription).setHeader("Description");
add(grid);
setSizeFull();
}
#PostConstruct
public void initDataProvider() {
CallbackDataProvider<CallCenterModel, Void> dataProvider = DataProvider.fromCallbacks(
query -> configurationControler.findAllcenters().stream(),
query -> configurationControler.countAllcallcenters()
);
grid.setDataProvider(dataProvider);
}
I cannot try out your code as-is since I lack the implementations of ConfigurationController and CallCenterModel. When I fill in the blanks based on my own assumptions, I end up with something where the callback is indeed called. That causes another error, but that's a different story.
Some wild guesses for why the query method won't be called in your case:
Maybe the count callback returns 0? In that case, there's no need to fetch any items.
Maybe there's something that prevents initDataProvider from being run at all so that the grid doesn't use your data provider?
Maybe there's something else that assigns a different data provider to the grid after your data provider has been assigned?
Testing whether any of those things happen should be quite straightforward either by setting some breakpoints or by adding some logging.
Everything works works for me if I slightly adapt your code to abstract away the ConfigurationController part so that I don't need a database to run the example. (I've also done a couple of other small tweaks just to make the example a couple of lines shorter)
#SpringComponent
#Route("dashboard")
public class CallcenterConfigurationView extends VerticalLayout {
// My approximation of the relevant parts of CallCenterModel
public static class CallCenterModel {
private String description;
public CallCenterModel(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
// A fake "database" containing 100 instances
private static List<CallCenterModel> callCenterModels = IntStream.range(0, 100)
.mapToObj(index -> new CallCenterModel("Call center " + index))
.collect(Collectors.toList());
private Grid<CallCenterModel> grid = new Grid<>();
public CallcenterConfigurationView() {
grid.addColumn(CallCenterModel::getDescription).setHeader("Description");
add(grid);
setSizeFull();
}
#PostConstruct
public void initDataProvider() {
CallbackDataProvider<CallCenterModel, Void> dataProvider = DataProvider.fromCallbacks(
query -> callCenterModels
.subList(query.getOffset(), query.getOffset() + query.getLimit())
.stream(),
query -> callCenterModels.size());
grid.setDataProvider(dataProvider);
}
}
Well, I changed the description, I think it would be clearer.
My application is divided into two parts (information on the right (1) and information on the left (2))
(2) - Here I have a table where I choose a contract.
(1) - The information that relates to the selected contract is displayed here, all shortcuts work correctly, but the information in the second table, which is in this part of the application (on the right), is not displayed, in this table I want to write down the links to the files that relate to the document which I chose on the left side
(Now while I use the type String, then I will do it under local links)
//I removed all unnecessary variables in my opinion that are displayed correctly.
public class MainData {
private final List<StringProperty> nameLink;
public MainData() {
this.nameLink = FXCollections.observableArrayList();
}
public List<StringProperty> getNameLink() {
return nameLink;
}
public List<StringProperty> setNameLink(StringProperty nameLink) {
this.nameLink.add(nameLink);
return getNameLink();
}
}
public class MainController {
private TableColumn<MainData, String> contractColumn;
public MainController() {
}
#FXML
private void initialize() {
contractColumn.setCellValueFactory(cellData -> cellData.getValue().getNameLink());
}
}
The problem is Initialize, lambda is waiting for the value ObservableValue, but I am passing List<String>, but if I change the value to List<String>, then the method getNameLink does not work for me how to fix it? How to make a wrapper?
I have an object which has a few arrays as fields. It's class roughly looks like this:
public class Helper {
InsuranceInvoices[] insuranceInvoices;
InsuranceCollectiveInvoices[] insuranceCollectiveInvoices
BankInvoices[] bankInvoices;
BankCollectiveInvoices[] bankCollectiveInvoices;
}
All of the invoice types have a mutual marker interface Invoices.
I need to get all of the invoices to invoke another method on them.
Helper helperObject = new Helper();
// ...
for (InsuranceInvoices invoice : helperObject.getInsuranceInvoices()) {
Integer customerId = invoice.getCustomerId();
// ...
}
for (BankInvoices invoice : helperObject.getBankInvoices()) {
Integer customerId = invoice.getCustomerId();
// ...
}
// repeat with all array fields
The problem is that all invoices only have the marker interface in common. The method getCustomerID() is not defined by a mutual interface or class. This is a behaviour I cannot change due to a given specification.
The code repetition inside the for-each-loop is something that bugs me. I have to do the exact same thing on all invoice objects in the four different arrays. Hence four for-each-loops that unecessary bloat the code.
Is there a way that I can write a general (private) method? One idea was:
private void generalMethod(Invoice[] invoiceArray){
// ...
}
But this would require four instanceof checks because the class Invoice doesn't know the method getCusomterId(). Therefore I would gain nothing; the method would still contain repetitions.
I'm thankful for every possible solution to generalize this problem!
Possible solutions to generalize the problem (ordered from best to worst):
Using wrapper class
public class InvoiceWrapper {
private String customerID;
public String getCustomerID() {
return customerID;
}
public InvoiceWrapper(BankInvoices invoice) {
this.customerID = invoice.getCustomerID();
}
public InvoiceWrapper(InsuranceInvoices invoice) {
this.customerID = invoice.getCustomerID();
}
// other constructors
}
Upd If I understood correctly, you need to do something with IDs in all arrays. To use InvoiceWrapper, you also need to implement iterator in Helper class, that will walk through arrays and return a wrapper for each entry. So, you will have code that works with 4 arrays anyway.
Using instance of casts
public class CustomerIdHelper {
public static String getID(Invoice invoice) {
if (invoice instanceof InsuranceInvoices) {
return ((InsuranceInvoices) invoices).getCustomerID();
} else if ...
}
}
Calling methods by name via Reflection
public class CustomerIdHelper {
public static String getID(Invoice invoice) {
Method method = invoice.getClass().getDeclaredMethod("getCustomerId");
return (String) method.invoke(invoice);
}
}
It's not pretty, but you could use reflection to look up the getCustomerId Method and then invoke() it, cf. Class.getDeclaredMethod().
private void generalMethod(Invoice[] invoiceArray){
try {
for (Invoice invoice : invoiceArray) {
Method getCustomerId = invoice.getClass().getDeclaredMethod("getCustomerId");
getCustomerId.invoke(invoice);
}
} catch (Exception e) {
// ...
}
}
Do note that this is untested.
If you are not allowed to change the classes you are handling by adding a custom interface to them. The best thing you can do is wrap them with a custom class that does have the desired properties.
This way you will have one class with all 'not so nice' code that converts the classes you can not touch to nice classes that match a proper and useful design.
For instance you could have a class WrappedInsuranceInvoice that extends WrappedInsurace and contains a member field InsuranceInvoice. If you don't need to keep the original class you would be off even better by copying the data. This way you could for instance lose the arrays and use lists instead.