JavaFX: Bindings with presentation model - java

My JavaFX application should look like this:
Now I want to make sure that the detail view adapts as soon as I select another person from the table view.
My classes so far:
public class Person {
private final StringProperty name = new SimpleStringProperty();
private final StringProperty title = new SimpleStringProperty();
private final IntegerProperty age = new SimpleIntegerProperty();
public Person(String name, String title, int age) {
setName(name);
setTitle(title);
setAge(age);
}
// Getters and Setters
}
public class PresentationModel {
private final ObservableList<Person> persons = FXCollections.observableArrayList();
private final ObjectProperty<Person> selectedPerson = new SimpleObjectProperty<>();
public PresentationModel() {
// add some users to persons list
}
// Getters/Setters
}
In the UI class with the table I have set up a listener like this:
personsTable.getSelectionModel().selectedItemProperty().addListener((observable, oldPerson, newPerson) -> {
model.setSelectedPerson(newPerson);
});
And in the UI class with the details view I have set up a binding:
nameLabel.textProperty().bind(model.getSelectedPerson().nameProperty());
The PresentationModel model attribute is created once with the start of the application and than passed through the constructors to all the UI classes.
But this bind is not working as expected.
What can I change so the binding works correctly and the property changes?

The binding doesn't work, because getSelectedPerson just returns the current selected person, and isn't recomputed if the selected person changes.
Using just the standard API, you can do
nameLabel.textProperty().bind(Bindings.selectString(
model.selectedPersonProperty(), "name"));
This API is a little unsatisfactory in a number of ways. For one, there is no compile-time checking that the selectedPersonProperty() has a nameProperty(), and that it is of the correct type. Secondly, it uses reflection, which doesn't perform well in the case that you call it very frequently (which does not apply here). Finally, if the selected person is null, this will dump lots of superfluous warnings to standard output (despite the fact that the API documentation indicates this is a supported use case!!!).
An alternative is provided by the ReactFX framework:
nameLabel.textProperty().bind(Val.selectVar(
model.selectedPersonProperty(), Person::nameProperty));

Related

Is there a javafx.scene.control.TableColumn function like setOnEditCommit() that can be used for finding the property related to the changed cell?

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.

How to track before & after properties of an object?

How do we properly track changes to an object's properties in Java (specifically JavaFX)?
My application allows users to modify the properties of the underlying data model objects. When the user clicks on a "Save" button, I want to save the new state of the object to a database. However, if the user clicks on "Cancel," I need to revert back to the object's original state.
Consider the following example objects (getters omitted for clarity):
class Person {
private final StringProperty name = new SimpleStringProperty();
private final StringProperty email = new SimpleStringProperty();
private final SimpleListProperty<PhoneNumber> phoneNumbers = new SimpleListProperty<>();
public Person(String name, String email, ObservableList<PhoneNumber> phoneNumbers) {
this.name.set(name);
this.email.set(email);
this.phoneNumbers.set(phoneNumbers);
}
}
class PhoneNumber {
private final IntegerProperty areaCode = new SimpleIntegerProperty();
private final IntegerProperty prefix = new SimpleIntegerProperty();
private final IntegerProperty lineNumber = new SimpleIntegerProperty();
public PhoneNumber(int areaCode, int prefix, int lineNumber) {
this.areaCode.set(areaCode);
this.prefix.set(prefix);
this.lineNumber.set(lineNumber);
}
}
Ideally, I want the GUI to load the original Person object, and make a copy of it to bind to the UI controls (even if I need to write this method myself):
Person editedPerson = copyOf(originalPerson);
Then, if the user clicks "Cancel," do nothing. Upon clicking "Save," however, set originalPerson to be equal to editedPerson.
I have looked into cloning objects, but the general consensus seems to recommend against that as it does not ensure the original object is not changed. Also, unless doing a deep copy, any objects references within Person, for example, would not be properly copied.
The other option I've seen is to use a copy constructor but my real-world application uses much more complex objects than the sample above. There are several levels of objects nested within each object and manually copying the entire hierarchy seems like overkill.
So what is the main question? Is there already an API available (or 3rd party library) that handles this functionality? It seems to be a pretty standard expectation for a user to be able to revert their changes.

Putting data in a TableView [duplicate]

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.

How should I populate my object?

I have an object that I want to populate with information. I retrieve the information from a number of different services. I made a helper class that has one public method and then has a number of private methods that do the work to call the services. What I have written works fine but I'm not sure if it is the correct way to do this.
You may be wondering why I need an object holding all this information. I need it all in one object because I create a json object from this java object and pass that to the javascript layer.
What is wrong with my approach and is there a programming paradigm I should be following to do something like this?
Example:
Person object with getters and setters for firstName, lastName, age, height, weight, list of favourite foods, list of favourite countries, list of comments.
Service 1 gives firstName, lastName, age, height and weight
Service 2
gives list of favourite countries and list of favourite foods
Service
3 gives a list of the comments made by the person
I have a personHelper class that looks like this:
public class PersonHelper{
public Person getPerson(userDetails){
Person person = new Person();
this.setPersonDetails(person, userDetails);
this.setFavourites(person, userDetails);
this.setComments(person, userDetails);
return person;
}
private Person setPersonalDetails(Person person, UserDetails userDetails){
returnedObj = callToService1(userDetails);
person.setFirstName(returnedObj.getFirstName());
person.setLastName(returnedObj.getLastName());
person.setAge(returnedObj.getAge());
person.setHeight(returnedObj.getHeight();
person.setWeight(returnedObj.getWeight());
return person;
}
private Person setFavourites(Person person, UserDetails userDetails){
<List>favsList = callToService2(userDetails);
person.setFavourites(returnedObj.getFavs(favsList));
return person;
}
private Person setComments(Person person, UserDetails userDetails){
<List>commentsList = callToService3(userDetails);
person.setComments(returnedObj.getComments(commentsList));
return person;
}
}
and then in my controller I call
person = personHelper.getPerson(userDetails);
jsonResponse = jsonProcessor.writeAsString(person);
return jsonResponse; // returns the ajax response to js
Thanks in advance for any help or suggestions.
EDIT: After more research I found that the object I am populating is referred to as a Data Transfer Object and I am populating it using the Java Bean method.
There's a trend these days to limit the mutability of objects so your setter-based approach, although workable, is sometimes not seen as the best way to create an object, even a data transfer type of object. One other thing to consider is how many objects know about each other and how much they know - it seems your PersonHelper class needs to know pretty much everything about UserDetails and Person. So if you add a field to Person, you need to add it to UserDetails and also add to PersonHelper to get that field populated.
For your type of object, you might find the Builder pattern useful. A builder is a short-term transient object designed to gather data for construction. Often the builder will have a fluent API, and gets passed to the (private) constructor of the transfer class. That means that all your code responsible for building the object is clear that that is its responsibility because it works with a Builder. Meanwhile, the constructed transfer object is effectively immutable and it becomes significantly easier to reason about the thread-safety of your code and to understand what values something might have at different parts.
public class Person {
private final String firstName;
private final String lastName;
private Person(final PersonBuilder builder) {
this.firstName = builder.firstName;
this.lastName = builder.lastName;
}
... usual getters etc ...
public static class PersonBuilder {
private String firstName;
private String lastName;
private PersonBuilder() {
}
public PersonBuilder withFirstName(final String name) {
this.firstName = name;
return this;
}
public PersonBuilder withLastName(final String name) {
this.lastName = name;
return this;
}
public Person build() {
return new Person(this);
}
}
public static PersonBuilder newPerson() {
return new PersonBuilder();
}
}
In this example the builder is a little over-wieldy, but when you've got twenty or thirty different pieces of data which are somehow optional it can make sense and makes for very easy to read construction code...
Person.newPerson().withFirstName("Sam").withLastName("Spade").build()
It seems to me that your 'UserDetails' object could be turned into a kind of builder. And so your 'PersonHelper' class would end up just calling userDetails.build() rather than knowing all about what fields the Person object (and userDetails object) contains.
There is no general paradigm for your question, but here are a few tips for your design:
It seems that your person data (names, favourites) is distributed among several data stores and you have to gether it all in your PersonHelper class. I don't know if this services are used anywhere else, but from the controller point of view this helper should be a service too.
Since your service invocations are independent, you can execute them in parallel
For some kind of applications it can be even better if you expose these services for UI level. For example, if data is presented in different UI blocks, client can make several asynchronous requests and display the data as soon as responses are received.

java data-structure to simulate a data tree

I need help defining what approach to use. I have a SOAP response giving me an xml file. I need to end-up with 3 correlated lists displayed on screen. When you select one item on the first list, the corresponding choices will appear on the second list etc. I am only interested on how to organize efficiently the data after it is extracted from the xml stream. Here's an xml snippet:
<device>
<manufacturer>Acer</manufacturer>
<model>A1</model>
<platform>Android</platform>
</device>
<device>
<manufacturer>Acer</manufacturer>
<model>A1</model>
<platform>J2ME</platform>
</device>
<device>
<manufacturer>Acer</manufacturer>
<model>A2</model>
<platform>Android</platform>
</device>
<device>
<manufacturer>Samsung</manufacturer>
<model>E400</model>
<platform>Android</platform>
</device>
So, I will have something like manufacturer={"Acer", "Acer", "Acer","Samsung"}, model={"A1","A1", "A2", "E400"}, platform={"Android","J2ME","Android","Android"}.
Here comes the fun part: I need to massage the data so that I can use it to display 3 lists. After selecting Android, Acer and Samsung become available. If Acer is selected, then model A1 and A2 are available. All lists need to be sorted. Currently I'm using Sax to parse the data into a vector of objects, containing manufacturer, model, platform fields. All I can think of is a TreeMap like structure. Any suggestions would be appreciated.
I do not think that hierarchical structure is what you need here. Because user may select first platform or manufacturer. If he selects first Android you want to show 3 devices. If he selects first Acer he will see 2 devices.
So, my suggesting is the following.
create class Device with properties manufacturer, model, platform.
create a plain linked list that contains all these devices.
Create 2 maps: manufaturerIndex and plarformIndex that look like:
Map<String, Collection<Device>> manufacturerIndex;
Iterate once over the list and populate all indexes maps.
Like this:
for(Device d : devices) {
Collection<Device> selected = manufacturerIndex.get(d.getManufacturer());
if (selected == null) {
selected = new ArrayList<Device>();
manufactuerIndex.put(d.getManufacturer(), selected);
}
selected.add(d);
// the same for the second index
}
Now you can use the data structure.
manufactuerIndex.get("Nokia") -> returns all Nokia devices.
Pay attention that this data structure is extendable. You can always add as many indexes as you want.
I'd just use a sortable collection of custom objects and then filter that collection based on predicates. I am using Guava for all of this, but there are of course other (usually more complicated) ways to implement this.
Here's my Product Object:
public class Product implements Comparable<Product>{
private final String manufacturer;
private final String model;
private final String platform;
public Product(final String manufacturer,
final String model,
final String platform){
this.manufacturer = manufacturer;
this.model = model;
this.platform = platform;
}
public String getManufacturer(){
return manufacturer;
}
public String getModel(){
return model;
}
public String getPlatform(){
return platform;
}
#Override
public int hashCode(){
return Objects.hashCode(manufacturer, model, platform);
}
#Override
public boolean equals(final Object obj){
if(obj instanceof Product){
final Product other = (Product) obj;
return Objects.equal(manufacturer, other.manufacturer)
&& Objects.equal(model, other.model)
&& Objects.equal(platform, other.platform);
}
return false;
}
#Override
public int compareTo(final Product o){
return ComparisonChain
.start()
.compare(manufacturer, o.manufacturer)
.compare(model, o.model)
.compare(platform, o.platform)
.result();
}
}
Now I'd just use a TreeSet<Product> and apply views on it. Here's a sample method that returns a live view that is filtered by model:
public static Collection<Product> filterByModel(
final Collection<Product> products,
final String model){
return Collections2.filter(products, new Predicate<Product>(){
#Override
public boolean apply(final Product product){
return product.getModel().equals(model);
}
});
}
Use it like this:
Collection<Product> products = new TreeSet<Product>();
// add some products
Collection<Product> filtered = filterByModel(products, "A1");
Update: We can take it even further, using only one collection, backed by chained predicates that are in turn tied to a model backed by your view. Brain hurts? Check this out:
// this is the collection you sent to your view
final Collection<Product> visibleProducts =
Collections2.filter(products, Predicates.and(Arrays.asList(
new ManufacturerPredicate(yourViewModel),
new ModelPredicate(yourViewModel),
new PlatformModel(yourViewModel)))
);
yourViewModel is an object that is backed by the values returned from your form controller. Each predicate uses a field of this model object to decide whether it applies or not.
e.g. The ModelPredicate checks all products in the collection to see whether their model is among the selected ones. Since this uses and logic, you can make it a hierarchic structure (if the manufacturer predicate returns false, the model and platform predicates are never called).
I use nested maps for something like that. Use TreeMap to get sorted results:
TreeMap<String, TreeMap<String, Model> manufacturerMap;
TreeMap<String, Model> models = manufacturerMap.get( name );
if( models == null ) {
models = new TreeMap<String, Model>();
manufacturerMap.put( name. models );
}
... etc ...

Categories

Resources