跨 类 的 javafx 触发器计算与绑定
javafx trigger calculation across classes with bindings
我有点卡住了,也许有人可以帮忙。我希望在某些字段发生更改时计算“总计”。
我在excel中做了一个原型来说明:
因此,每当价格或数量发生变化时,都会重新计算总成本,并且还应更新摘要,包括“最终总计”。我使用了绑定,但它们不起作用,除非首次创建摘要项,随后 finalTotal 字段不会重新计算。
我有两个 class,一个叫做 Product,它包含属性 productName
、priceEach
、quantity
、productTotal
。每当设置 priceEach
或 quantity
属性时,都会调用 calculateTotal()
方法来更新 productTotal
。
这是代码:
import javafx.beans.property.*;
import javafx.beans.property.SimpleFloatProperty;
import javafx.beans.property.StringProperty;
public class Product {
private final StringProperty productName;
private final FloatProperty priceEach;
private final IntegerProperty quantity;
private FloatProperty productTotal;
public Product(){
this.productName = new SimpleStringProperty("Product Name");
this.priceEach = new SimpleFloatProperty(0);
this.quantity = new SimpleIntegerProperty(0);
this.productTotal = new SimpleFloatProperty(0);
}
public Product(String name, float priceEach, int quantity){
this.productName = new SimpleStringProperty(name);
this.priceEach = new SimpleFloatProperty(priceEach);
this.quantity = new SimpleIntegerProperty(quantity);
this.productTotal = calculateProductTotal();
}
private FloatProperty calculateProductTotal() {
return new SimpleFloatProperty(priceEach.get()*quantity.get());
}
public void setProductName(String name){
productName.set(name);
}
public void setPriceEach(float price){
priceEach.set(price);
productTotal = calculateProductTotal();
}
public void setQuantity(int number){
quantity.set(number);
productTotal = calculateProductTotal();
}
public String getProductName(){ return productName.get(); }
public float getPriceEach(){ return priceEach.get(); }
public int getQuantity(){ return quantity.get(); }
public float getProductTotal(){ return productTotal.get(); }
public StringProperty productNameProperty() { return productName;}
public FloatProperty priceEachProperty() { return priceEach;}
public IntegerProperty quantityProperty() { return quantity;}
public FloatProperty productTotalProperty() { return productTotal;}
@Override
public String toString(){
return " Product: " + getProductName() +
", Price: " + getPriceEach() +
", Quantity: " + getQuantity() +
", Total cost: " + getProductTotal();
}
}
我还有一个摘要 class,其中有两个属性单向绑定到产品 class 中的相应属性。这就是问题所在。
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
public class Summary {
private FloatProperty finalTotal;
public ObservableList<Item> itemsSummary = FXCollections.observableArrayList();
public Summary(){
finalTotal=new SimpleFloatProperty(0);
}
public Item add()
{
Item newItem = new Item();
itemsSummary.add(newItem);
newItem.productTotalProperty().addListener((obs, oldAmount, newAmount) -> {
System.out.println("Item total changed from "+oldAmount+" to "+newAmount);
calculateTotal();
System.out.println("Final total: "+finalTotal);
});
return newItem;
}
public class Item{
private final StringProperty productName;
private FloatProperty productTotal;
public Item(){
this.productName = new SimpleStringProperty("");
this.productTotal = new SimpleFloatProperty(-1);
}
public void setProductName(String name) { productName.set(name); }
public void setProductTotal(float total) { productTotal.set(total); }
public String getProductName() { return productName.get(); }
public float getProductTotal() { return productTotal.get(); }
public StringProperty productNameProperty() { return productName;}
public FloatProperty productTotalProperty() { return productTotal;}
}
private void calculateTotal(){
float runningTotal=0;
for (Item i: itemsSummary )
runningTotal+= i.getProductTotal();
finalTotal.set(runningTotal);
}
@Override
public String toString(){
String output= "";
for(Item i: itemsSummary){
String pn = i.getProductName();
float pt = i.getProductTotal();
output = output +"\n"+ String.format("Product: %s, Product cost: %.2f", pn, pt);
}
return output+
"\nFinal total: " + finalTotal.getValue();
}
}
如您所见,我有一个包含数据(名称和小计)的内部 class 项目。我已经向 newItem.productTotalProperty()
添加了一个侦听器,但这似乎只在构造新项目时有效:当产品的字段发生更改时,其实例的总计会更新,但到 Summary 对象的绑定会停止工作,所以它的 none 个字段被更改了。
测试驱动代码如下:
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
public class BindingsPrototypeTestDriver {
public static void main(String[] args)
{
Product A = new Product("Pencil", 1.50f, 300);
Product B = new Product("Eraser", 0.50f, 200);
Product C = new Product("Paper", 4.95f, 100);
ObservableList<Product> products = FXCollections.observableArrayList();
products.add(A);
products.add(B);
products.add(C);
Summary summary = new Summary();
for(Product p: products)
{
Summary.Item summaryItem = summary.add();
summaryItem.productNameProperty().bind(p.productNameProperty());
summaryItem.productTotalProperty().bind(p.productTotalProperty());
}
for(Product p: products){ System.out.println(p.toString()); }
System.out.println(summary.toString());
C.setQuantity(200);
for(Product p: products){ System.out.println(p.toString()); }
System.out.println(summary.toString());
}
}
这是输出:
run:
Item total changed from -1.0 to 450.0
Final total: FloatProperty [value: 450.0]
Item total changed from -1.0 to 100.0
Final total: FloatProperty [value: 550.0]
Item total changed from -1.0 to 494.99997
Final total: FloatProperty [value: 1045.0]
Product: Pencil, Price: 1.50, Quantity: 300, Total cost: 450.00
Product: Eraser, Price: 0.50, Quantity: 200, Total cost: 100.00
Product: Paper, Price: 4.95, Quantity: 100, Total cost: 495.00
Product: Pencil, Product cost: 450.00
Product: Eraser, Product cost: 100.00
Product: Paper, Product cost: 495.00
Final total: 1045.0
Product: Pencil, Price: 1.50, Quantity: 300, Total cost: 450.00
Product: Eraser, Price: 0.50, Quantity: 200, Total cost: 100.00
Product: Paper, Price: 4.95, Quantity: 200, Total cost: 990.00
Product: Pencil, Product cost: 450.00
Product: Eraser, Product cost: 100.00
Product: Paper, Product cost: 495.00
Final total: 1045.0
如您所见,当“Paper”实例的数量更改为 200 时,Product 实例中的所有内容都会正确更新,但 Summary 中的内容不会...
我做错了什么?
您的代码有一些问题。
您遇到的问题是您对 productTotal
使用了一个 属性 并且您没有更新,而是在每次值更新时替换为一个新的 属性 对象。您的 Product
class 的用户不可能知道这一点。事实上,用户希望 属性 getter 总是 return 相同的实例(你也确实希望如此)。
与其创建新的 属性,不如为旧的 属性 分配一个新值,或者为简单起见,只使用 Bindings
class:
public class Product {
private final StringProperty productName;
private final FloatProperty priceEach;
private final IntegerProperty quantity;
private final FloatProperty productTotal;
public Product(){
this("Product Name", 0, 0);
}
public Product(String name, float priceEach, int quantity){
this.productName = new SimpleStringProperty(name);
this.priceEach = new SimpleFloatProperty(priceEach);
this.quantity = new SimpleIntegerProperty(quantity);
this.productTotal = new SimpleFloatProperty();
this.productTotal.bind(Bindings.multiply(this.quantity, this.priceEach));
}
public void setPriceEach(float price){
priceEach.set(price);
}
...
Summary
class
- 为什么要添加嵌套
Item
class。您已经有一个 Product
class。将数据存储在内存中两次没有多大意义。您还依赖外部代码为您绑定值...那是糟糕的设计。
- 更糟糕的是:您的
itemsSummary
是 public
而不是 final
这意味着任何引用 Summary
实例的人都可以更改值并且 Summary
实例未获知该更改。这在这里不是问题的唯一原因是您没有使用 ObservableList
列表。事实上,您可以将代码中的所有列表替换为 ArrayList
s,这不会有什么不同。
我会这样做:
- 制作
itemsSummary
final
甚至 private
并提供 getter
- 使用
ListChangeListener
向列表项添加/删除列表项。
- 使用
Product
的列表
public class Summary {
private final FloatProperty finalTotal;
public final ObservableList<Product> itemsSummary;
private final ChangeListener<Number> itemTotalChangeListener = (observable, oldValue, newValue) -> {
calculateTotal();
};
public Summary() {
finalTotal = new SimpleFloatProperty(0);
itemsSummary = FXCollections.observableArrayList();
itemsSummary.addListener((ListChangeListener.Change<? extends Product> c) -> {
boolean modified = false;
while (c.next()) {
if (c.wasRemoved()) {
modified = true;
for (Product p : c.getRemoved()) {
p.productTotalProperty().removeListener(itemTotalChangeListener);
}
}
if (c.wasAdded()) {
modified = true;
for (Product p : c.getAddedSubList()) {
p.productTotalProperty().addListener(itemTotalChangeListener);
}
}
}
if (modified) {
calculateTotal();
}
});
}
...
这些修改使main
方法中的代码更简单一些:
Product A = new Product("Pencil", 1.50f, 300);
Product B = new Product("Eraser", 0.50f, 200);
Product C = new Product("Paper", 4.95f, 100);
// no need for a observable list here
Product[] products = new Product[]{A, B, C};
Summary summary = new Summary();
summary.itemsSummary.setAll(products);
for (Product p : products) {
System.out.println(p.toString());
}
...
我有点卡住了,也许有人可以帮忙。我希望在某些字段发生更改时计算“总计”。
我在excel中做了一个原型来说明:
因此,每当价格或数量发生变化时,都会重新计算总成本,并且还应更新摘要,包括“最终总计”。我使用了绑定,但它们不起作用,除非首次创建摘要项,随后 finalTotal 字段不会重新计算。
我有两个 class,一个叫做 Product,它包含属性 productName
、priceEach
、quantity
、productTotal
。每当设置 priceEach
或 quantity
属性时,都会调用 calculateTotal()
方法来更新 productTotal
。
这是代码:
import javafx.beans.property.*;
import javafx.beans.property.SimpleFloatProperty;
import javafx.beans.property.StringProperty;
public class Product {
private final StringProperty productName;
private final FloatProperty priceEach;
private final IntegerProperty quantity;
private FloatProperty productTotal;
public Product(){
this.productName = new SimpleStringProperty("Product Name");
this.priceEach = new SimpleFloatProperty(0);
this.quantity = new SimpleIntegerProperty(0);
this.productTotal = new SimpleFloatProperty(0);
}
public Product(String name, float priceEach, int quantity){
this.productName = new SimpleStringProperty(name);
this.priceEach = new SimpleFloatProperty(priceEach);
this.quantity = new SimpleIntegerProperty(quantity);
this.productTotal = calculateProductTotal();
}
private FloatProperty calculateProductTotal() {
return new SimpleFloatProperty(priceEach.get()*quantity.get());
}
public void setProductName(String name){
productName.set(name);
}
public void setPriceEach(float price){
priceEach.set(price);
productTotal = calculateProductTotal();
}
public void setQuantity(int number){
quantity.set(number);
productTotal = calculateProductTotal();
}
public String getProductName(){ return productName.get(); }
public float getPriceEach(){ return priceEach.get(); }
public int getQuantity(){ return quantity.get(); }
public float getProductTotal(){ return productTotal.get(); }
public StringProperty productNameProperty() { return productName;}
public FloatProperty priceEachProperty() { return priceEach;}
public IntegerProperty quantityProperty() { return quantity;}
public FloatProperty productTotalProperty() { return productTotal;}
@Override
public String toString(){
return " Product: " + getProductName() +
", Price: " + getPriceEach() +
", Quantity: " + getQuantity() +
", Total cost: " + getProductTotal();
}
}
我还有一个摘要 class,其中有两个属性单向绑定到产品 class 中的相应属性。这就是问题所在。
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
public class Summary {
private FloatProperty finalTotal;
public ObservableList<Item> itemsSummary = FXCollections.observableArrayList();
public Summary(){
finalTotal=new SimpleFloatProperty(0);
}
public Item add()
{
Item newItem = new Item();
itemsSummary.add(newItem);
newItem.productTotalProperty().addListener((obs, oldAmount, newAmount) -> {
System.out.println("Item total changed from "+oldAmount+" to "+newAmount);
calculateTotal();
System.out.println("Final total: "+finalTotal);
});
return newItem;
}
public class Item{
private final StringProperty productName;
private FloatProperty productTotal;
public Item(){
this.productName = new SimpleStringProperty("");
this.productTotal = new SimpleFloatProperty(-1);
}
public void setProductName(String name) { productName.set(name); }
public void setProductTotal(float total) { productTotal.set(total); }
public String getProductName() { return productName.get(); }
public float getProductTotal() { return productTotal.get(); }
public StringProperty productNameProperty() { return productName;}
public FloatProperty productTotalProperty() { return productTotal;}
}
private void calculateTotal(){
float runningTotal=0;
for (Item i: itemsSummary )
runningTotal+= i.getProductTotal();
finalTotal.set(runningTotal);
}
@Override
public String toString(){
String output= "";
for(Item i: itemsSummary){
String pn = i.getProductName();
float pt = i.getProductTotal();
output = output +"\n"+ String.format("Product: %s, Product cost: %.2f", pn, pt);
}
return output+
"\nFinal total: " + finalTotal.getValue();
}
}
如您所见,我有一个包含数据(名称和小计)的内部 class 项目。我已经向 newItem.productTotalProperty()
添加了一个侦听器,但这似乎只在构造新项目时有效:当产品的字段发生更改时,其实例的总计会更新,但到 Summary 对象的绑定会停止工作,所以它的 none 个字段被更改了。
测试驱动代码如下:
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
public class BindingsPrototypeTestDriver {
public static void main(String[] args)
{
Product A = new Product("Pencil", 1.50f, 300);
Product B = new Product("Eraser", 0.50f, 200);
Product C = new Product("Paper", 4.95f, 100);
ObservableList<Product> products = FXCollections.observableArrayList();
products.add(A);
products.add(B);
products.add(C);
Summary summary = new Summary();
for(Product p: products)
{
Summary.Item summaryItem = summary.add();
summaryItem.productNameProperty().bind(p.productNameProperty());
summaryItem.productTotalProperty().bind(p.productTotalProperty());
}
for(Product p: products){ System.out.println(p.toString()); }
System.out.println(summary.toString());
C.setQuantity(200);
for(Product p: products){ System.out.println(p.toString()); }
System.out.println(summary.toString());
}
}
这是输出:
run:
Item total changed from -1.0 to 450.0
Final total: FloatProperty [value: 450.0]
Item total changed from -1.0 to 100.0
Final total: FloatProperty [value: 550.0]
Item total changed from -1.0 to 494.99997
Final total: FloatProperty [value: 1045.0]
Product: Pencil, Price: 1.50, Quantity: 300, Total cost: 450.00
Product: Eraser, Price: 0.50, Quantity: 200, Total cost: 100.00
Product: Paper, Price: 4.95, Quantity: 100, Total cost: 495.00
Product: Pencil, Product cost: 450.00
Product: Eraser, Product cost: 100.00
Product: Paper, Product cost: 495.00
Final total: 1045.0
Product: Pencil, Price: 1.50, Quantity: 300, Total cost: 450.00
Product: Eraser, Price: 0.50, Quantity: 200, Total cost: 100.00
Product: Paper, Price: 4.95, Quantity: 200, Total cost: 990.00
Product: Pencil, Product cost: 450.00
Product: Eraser, Product cost: 100.00
Product: Paper, Product cost: 495.00
Final total: 1045.0
如您所见,当“Paper”实例的数量更改为 200 时,Product 实例中的所有内容都会正确更新,但 Summary 中的内容不会...
我做错了什么?
您的代码有一些问题。
您遇到的问题是您对 productTotal
使用了一个 属性 并且您没有更新,而是在每次值更新时替换为一个新的 属性 对象。您的 Product
class 的用户不可能知道这一点。事实上,用户希望 属性 getter 总是 return 相同的实例(你也确实希望如此)。
与其创建新的 属性,不如为旧的 属性 分配一个新值,或者为简单起见,只使用 Bindings
class:
public class Product {
private final StringProperty productName;
private final FloatProperty priceEach;
private final IntegerProperty quantity;
private final FloatProperty productTotal;
public Product(){
this("Product Name", 0, 0);
}
public Product(String name, float priceEach, int quantity){
this.productName = new SimpleStringProperty(name);
this.priceEach = new SimpleFloatProperty(priceEach);
this.quantity = new SimpleIntegerProperty(quantity);
this.productTotal = new SimpleFloatProperty();
this.productTotal.bind(Bindings.multiply(this.quantity, this.priceEach));
}
public void setPriceEach(float price){
priceEach.set(price);
}
...
Summary
class
- 为什么要添加嵌套
Item
class。您已经有一个Product
class。将数据存储在内存中两次没有多大意义。您还依赖外部代码为您绑定值...那是糟糕的设计。 - 更糟糕的是:您的
itemsSummary
是public
而不是final
这意味着任何引用Summary
实例的人都可以更改值并且Summary
实例未获知该更改。这在这里不是问题的唯一原因是您没有使用ObservableList
列表。事实上,您可以将代码中的所有列表替换为ArrayList
s,这不会有什么不同。
我会这样做:
- 制作
itemsSummary
final
甚至private
并提供 getter - 使用
ListChangeListener
向列表项添加/删除列表项。 - 使用
Product
的列表
public class Summary {
private final FloatProperty finalTotal;
public final ObservableList<Product> itemsSummary;
private final ChangeListener<Number> itemTotalChangeListener = (observable, oldValue, newValue) -> {
calculateTotal();
};
public Summary() {
finalTotal = new SimpleFloatProperty(0);
itemsSummary = FXCollections.observableArrayList();
itemsSummary.addListener((ListChangeListener.Change<? extends Product> c) -> {
boolean modified = false;
while (c.next()) {
if (c.wasRemoved()) {
modified = true;
for (Product p : c.getRemoved()) {
p.productTotalProperty().removeListener(itemTotalChangeListener);
}
}
if (c.wasAdded()) {
modified = true;
for (Product p : c.getAddedSubList()) {
p.productTotalProperty().addListener(itemTotalChangeListener);
}
}
}
if (modified) {
calculateTotal();
}
});
}
...
这些修改使main
方法中的代码更简单一些:
Product A = new Product("Pencil", 1.50f, 300);
Product B = new Product("Eraser", 0.50f, 200);
Product C = new Product("Paper", 4.95f, 100);
// no need for a observable list here
Product[] products = new Product[]{A, B, C};
Summary summary = new Summary();
summary.itemsSummary.setAll(products);
for (Product p : products) {
System.out.println(p.toString());
}
...