JavaFX: BooleanBindings in bind with multiple EasyBind maps
Asked Answered
I

1

1

This question goes further where JavaFX: Disable multiple rows in TableView based on other TableView stops. I want to generate a more general topic, of which other people could also benefit.

I also have the two tableviews. I also want to disable a row in table2 when table1 contains the same object. This is achieved with the code below:

//Check if a row needs to be disabled: this is achieved with a rowfactory
ObservableList<String> listOfSKUs = EasyBind.map(table1.getItems(),Product::getSKU);
table2.setRowFactory(tv -> {

    TableRow<Product> row = new TableRow<>();

    //The row doesnt need to be checked when it is empty, thats the first part
    //The second part is a big OR: the row is a gift card OR the listOfSkus contains the row. In this last one, the amount also needs to be checked.

    row.disableProperty().bind(Bindings.createBooleanBinding( () -> 
        row.getItem() != null && 
        (row.getItem().getProductName().equals("Kadobon") ||
        (listOfSKUs.contains(row.getItem().getSKU()) //&& some part to check for the amount)  
        ), listOfSKUs, row.itemProperty() ));

     return row;

});

Now I only want the row to be disabled when:

  1. The SKU of the product from table1 is also in table2 (this works now)
  2. The amount of the product in table2 is negative

I do not know how to check on the amount, because I need the amount that is associated with the certain SKU. How can I create this with multiple maps in one BooleanBinding?

Any help is greatly appreciated!

Institutionalize answered 29/10, 2014 at 13:59 Comment(2)
You probably need to elaborate on the types you have here. It looks like table1 and table2 are both TableView<Product>s. So isn't the amount of the product in table1 necessarily the same as the amount of the product in table2 that has the same SKU (aren't they the same object..)?Epigastrium
@Epigastrium No there are not the same object, they only have the same SKU. The problem is this: the products in table2 need to be put in table1 with a negative amount. I know how to do this. But there could be a object with the same SKU in table1 with a positive amount, that does not come from table2, but from another source. With the solution above, the row gets disabled while it should not be, because the amount in table 1 is positive and not negative. Do you understand it?Institutionalize
E
1

This sounds to me that you might benefit from reconsidering your object model. It seems that your Product has two different values you refer to as amount: one that's displayed in table1, and one that's displayed in table2. If you make those two different fields of the Product class, then all this becomes much easier, as you can represent the items in both tables with the same actual objects (just with different properties displayed in the columns); and then the criteria for disabling a row becomes just a function of the object you're looking at (along with table2.getItems().contains(...)).

Another option might be an SKU class:

public class SKU {

    private final StringProperty sku = ... ;

    // Obviously use more meaningful names for these:
    private final ObjectProperty<Product> table1Product = ... ;
    private final ObjectProperty<Product> table2Product = ... ;

    // getters, setters etc...

}

Then both tables can be TableView<SKU>s with the cell value factories on the columns just looking at the properties from the correct product. That way row.getItem() in table1's row factory returns the SKU object, and can look at the table2 properties as needed.

If you really can't do that, then one way to do this would be to maintain an ObservableMap<String, Double> which maps SKUs to the amount displayed in table2. This is a bit of work, but not too bad:

ObservableMap<String, Double> skuAmountLookup = table2.getItems().stream().collect(
    Collectors.toMap(Product::getSKU, Product::getAmount,
    // the next argument is a merge function, which is used if there are two (or more) items
    // in table2 with the same SKU. This function would replace the first value by the second.
    // If the SKUs are unique it doesn't really matter what you have here...
    (x, y) -> y,
    () -> FXCollections.observableHashMap()));

Now you need to keep the map updated when the table contents change:

table2.getItems().addListener((ListChangeListener.Change<? extends Product> change) -> {
    // can just do skuAmountLookup.clear(); followed by skuAmountLookup.putAll(...)
    // passing in the same data used above to initialize it
    // That's pretty inefficient though, the following will just update what's needed.
    // This assumes SKUs are unique in table 2:
    while (change.next()) {
        if (change.wasAdded()) {
            change.getAddedSubList().forEach(product -> 
                skuAmountLookup.put(product.getSKU(), product.getAmount()));
        }
        if (change.wasRemoved()) {
            change.getRemoved().forEach(product -> skuAmountLookup.remove(product.getSKU()));
        }
    }
});

And then the disable criteria can be something like

row.disableProperty().bind(Bindings.createBooleanBinding(
    () -> row.getItem() != null &&
          skuAmountLookup.containsKey(row.getItem().getSKU()) &&
          skuAmountLookup.get(row.getItem().getSKU()) < 0, 
    skuAmountLookup,
    row.itemProperty()));

I haven't tried any of this, but it should work. There are two assumptions here:

  1. SKUs are unique in table 2
  2. The amount in table 2 isn't going to change, other than by items being added or removed from the table. This can be worked around if needed, but it adds one more layer of complexity to the listener on table2's items.
Epigastrium answered 29/10, 2014 at 15:15 Comment(6)
Could you please explain what you mean? Your answer seems to be incomplete.Institutionalize
Sorry, accidentally hit "Post" too soon.Epigastrium
Thanks for this answer! When I try to create the ObservableMap, I get the error: No suitable method found for toMap. What could this be?Institutionalize
Last parameter should be () -> FXCollections.observableHashMap(). Sorry: I fixed it there.Epigastrium
Thanks James_D for your answer. I have chosen to not change my object model, because that would give me a lot of work, and I would only benefit from it in this minor part. The other solution you gave, worked perfectly for me!!Institutionalize
Sorry but I have one question about this. I would like to extend the row factory a bit further: In table 1 the products are presented with 1 piece a row, so there could be multiple rows for 1 product object. Now I only want to disable a row, if the amount in table 2 is equal to the summed amounts of the rows. For example: Product(SKU: P1) has three lines in table1 (all representing amount 1). If Product(SKU: P1) had amount = -2 in table2, 2 rows of this product object need to be disabled. One still needs to be enabled, doesn't matter which one of the three. How can I do this?Institutionalize

© 2022 - 2024 — McMap. All rights reserved.