TableView: laufender Saldo mit BigDecimal?

Hallo

Ich hab vor 3 Wochen angefangen mit Java und mir zum Ziel gesetzt, gleich von Anfang etwas richtiges zu programmieren: Ein Kassen/Kontobuch. Ich bin schon sehr weit gekommen, man findet eigentlich alles im Internet. Nun häng ich aber bei einem Problem und komme nicht mehr weiter.

Ich hab eine TableView mit Spalten, unter anderem Einnahme, Ausgabe sowie den laufenden Saldo. Änderungen in der Tabelle werden über ein Formular gemacht. Der Saldo soll nun laufend neu berechnet werden, also bei einer neuen Buchung, aber eben auch bei einer Änderung einer (in der Vergangenheit liegenden) Buchung. Das ganze soll mit BigDecimal berechnet werden.

Ich hab ein funktionierendes Beispiel gefunden, jedoch mit Integer. Ich bring es leider nicht hin, es auf BigDecimal umzuschreiben.
Daneben bin ich mir noch nicht im klaren, ob dieser Code auch für das Neuberechnen bei Änderungen funktioniert, aber ich hab nichts besseres gefunden. Wie bring ich das hin mit BigDecimal, um mit Geldbeträgen zu rechnen? Vielleicht brauchts auch einen anderen Code für mein Vorhaben?

Java:
import javafx.application.Application;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Callback;

/**
 *
 * @author reegan
 */
public class CalculateTableCellStackover extends Application {

    @Override
    public void start(Stage primaryStage) {

        Transcation t1 = new Transcation("Transcation1", 100);
        Transcation t2 = new Transcation("Transcation2", 500);
        Transcation t3 = new Transcation("Transcation3", 300);
        Transcation t4 = new Transcation("Transcation4", 1000);
        Transcation t5 = new Transcation("Transcation5", 200);
        ObservableList<Transcation> list = FXCollections.observableArrayList();
        list.addAll(t1, t2, t3, t4, t5);



        TableView<Transcation> tableView = new TableView<Transcation>();
        TableColumn name = new TableColumn("Name");
        name.setCellValueFactory(new PropertyValueFactory<Transcation, String>("name"));
        TableColumn amount = new TableColumn("Amount");
        amount.setCellValueFactory(new PropertyValueFactory<Transcation, Integer>("amount"));
        TableColumn balance = new TableColumn("Balance");
        balance.setMinWidth(50);
        balance.setCellValueFactory(new Callback<TableColumn.CellDataFeatures, ObservableValue>() {
            @Override
            public ObservableValue call(TableColumn.CellDataFeatures p) {
                return new ReadOnlyObjectWrapper(p.getValue());
            }
        });
//       
        balance.setCellFactory(new Callback<TableColumn, TableCell>() {
            @Override
            public TableCell call(TableColumn p) {
                return new TableCell() {
                    @Override
                    protected void updateItem(Object item, boolean empty) {
                        super.updateItem(item, empty);
                        if (this.getTableRow() != null && item != null) {
//                            System.out.println(this.getTableRow().getItem().toString());
//                            setText((this.getTableRow().getIndex() + 1) + "");
//                              System.out.println(this.getTableRow().getIndex());
//                            System.out.println(getTableView().getItems().get(getTableRow().getIndex() -1));
//                            System.out.println();
                            int current = this.getTableRow().getIndex();
                            int pre = this.getTableRow().getIndex() - 1;
                            if(current == 0) {
                                pre = current;
                            }
                            System.out.println("");
//                            setText(String.valueOf(Integer.parseInt(getTableRow().getItem().toString()) + Integer.parseInt(getTableView().getItems().get(pre).toString())));
                            Integer totalValue = new Integer(0);
                            for(int i = 0; i <= current;i++) { 
                               totalValue = totalValue + (Integer.parseInt(getTableView().getItems().get(i).toString()));
                            }

                            setText(String.valueOf(totalValue));


//                            setText(this.getTableRow().getItem().toString());
                        } else {
                            setText("");
                        }
                    }
                };
            }
        });
        tableView.getColumns().addAll(name, amount, balance);
        tableView.setItems(list);
        StackPane root = new StackPane();
        root.getChildren().add(tableView);

        Scene scene = new Scene(root, 300, 250);

        primaryStage.setTitle("Hello World!");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    /**
     * The main() method is ignored in correctly deployed JavaFX application.
     * main() serves only as fallback in case the application can not be
     * launched through deployment artifacts, e.g., in IDEs with limited FX
     * support. NetBeans ignores main().
     *
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }

    public class Transcation {

        String name;
        Integer amount;

        public Transcation(String name, int amount) {
            this.name = name;
            this.amount = amount;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAmount() {
            return amount;
        }

        public void setAmount(int amount) {
            this.amount = amount;
        }

        @Override
        public String toString() {
            return amount.toString();
        }
    }
}

Ich denke das Problem liegt in dieser Zeile, die ich aber leider nicht wirklich verstehe bzw. mit System.out keinen nachvollziehbaren Wert ausgeben konnte (der fett markierte Teil):

Java:
 totalValue = totalValue + ([B]Integer.parseInt(getTableView().getItems().get(i).toString())[/B]);
Vielen Dank
 
Warum brauchst du BigDecimal? Reicht ein Double nicht aus? Wenn du Double benutzen mochtest kannst du Integer.parseInt() durch Double.parseDouble() ersetzen. Wenn du den code nicht verstehen kannst ist es vielleicht besser erst mit einem Einsteigerbuch kleinere Beispiele zu programmieren und nicht gleich so ein komplexes Programm mit JavaFX zu erstellen.
 
Ohje, für Währungen würde ich weder double (zu ungenau) noch BigDezimal (zu viel Overhead) nehmen. Nimm einfach int. Es sei denn Du must mit Summen > 40 Millionen Euro rechnen (Was ich bei einem privaten Kassenbuch für relativ unwahrscheinlich halte)

Gruß

Claus
 
Vielen Dank für Eure Antworten!
Richtig, Double ist zu ungenau, deshalb wollte ich es mit BigDecimal machen. Ich hab den Code soweit auch umschreiben können. Und dann auch gemerkt, dass das .toString() die überschriebene Methode aufruft. Hab ich übersehen und deshalb nicht verstanden.

Mit int? Da müsste ich ja die Beträge zuerst mit 100 multiplizieren, dann rechnen und schlussendlich wieder mit 100 dividieren.

So oder so... ich hab festgestellt, dass die Callback-Funktion nicht nur beim ändern von Beträgen ausgeführt wird, sondern auch dauernd beim Scrollen, anklicken einer Zelle oder sogar beim ändern der Fenstergrösse. Aktuell hab ich über 5000 Zeilen in der Tabelle und es wird so bei jeder Aktion sehr langsam.
 
Vorab: ich kenne mich mit dem JavaFX-Gerümpel nicht aus, hier mal ein Ansatz:
Java:
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.ObservableListBase;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Callback;

import java.util.Random;
import java.util.List;
import static java.util.stream.Collectors.*;

/**
 *
 * @author reegan
 */
public class CalculateTableCellStackover extends Application {

    @Override
    public void start(Stage primaryStage) {
        Random rand = new Random(1L);
        ObservableList<Transcation> list = FXCollections.observableArrayList();
        for (int i = 0; i < 5000; i++) {
            list.add(new Transcation("Transcation" + (i+1), rand.nextInt(1000)+1));
        }


        TableView<TransactionWithBalance> tableView = new TableView<>();
        TableColumn name = new TableColumn("Name");
        name.setCellValueFactory(new PropertyValueFactory<Transcation, String>("name"));
        TableColumn amount = new TableColumn("Amount");
        amount.setCellValueFactory(new PropertyValueFactory<Transcation, Integer>("amount"));
        TableColumn<TransactionWithBalance, String> balance = new TableColumn("Balance");
        balance.setCellValueFactory(cdf -> 
            new SimpleStringProperty(String.format("%.2f", cdf.getValue().getBalance() / 100d)));

        tableView.getColumns().addAll(name, amount, balance);
        tableView.setItems(new Transactions(list));
        StackPane root = new StackPane();
        root.getChildren().add(tableView);

        Scene scene = new Scene(root, 300, 250);

        primaryStage.setTitle("Hello World!");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    /**
     * The main() method is ignored in correctly deployed JavaFX application.
     * main() serves only as fallback in case the application can not be
     * launched through deployment artifacts, e.g., in IDEs with limited FX
     * support. NetBeans ignores main().
     *
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }


    public class Transactions extends ObservableListBase<TransactionWithBalance> {
        private final List<TransactionWithBalance> data;

        public Transactions(List<Transcation> data) {
            this.data = data.stream().map(TransactionWithBalance::new).collect(toList());
            if (!data.isEmpty()) {
                int sum = 0;
                for (TransactionWithBalance tx : this.data) {
                    sum += tx.getAmount();
                    tx.setBalance(sum);
                }
            }
        }
    
        @Override
        public TransactionWithBalance get(int index) { return data.get(index); }

        @Override
        public int size() { return data.size(); }
    }

    public class TransactionWithBalance extends Transcation {
        private int balance;

        public TransactionWithBalance(Transcation tx) {
            super(tx.getName(), tx.getAmount());
        }

        public void setBalance(int b) { balance = b; }
        public int getBalance() { return balance; }
    }

    public class Transcation {

        String name;
        Integer amount;

        public Transcation(String name, int amount) {
            this.name = name;
            this.amount = amount;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAmount() {
            return amount;
        }

        public void setAmount(int amount) {
            this.amount = amount;
        }

        @Override
        public String toString() {
            return amount.toString();
        }
    }
}
 
Ein weiterer Ansatz. Durch die CellValueFactories direkt auf die Properties wird ein bidirektionales Mapping sichergestellt. Wird eine Property geändert, so merkt das die Collection, an die der TableView gebunden ist und dann werden die Balances neu berechnete und gesetzt.
Zustandsänderungen werden nur bis in die ViewModels propagiert. Transaction bleibt also unverändert. Üblicherweise werden die Eigenschaften erst während der Validierung oder beim Speichern in das Model übernommen.
(Bonus: ) Für balance habe ich außerdem eine eigene CellFactory hinterlegt, die den Text entsprechend des Vorzeichens einfärbt.
Java:
package sample;
/**
*
* @author reegan
*/
public class CalculateTableCellStackover extends Application {

    @Override
    public void start(Stage primaryStage) {
        Random rand = new Random(1L);
        List<Transcation> list = new ArrayList<>();
        for (int i = 0; i < 5000; i++) {
            list.add(new Transcation("Transaction" + (i+1), rand.nextInt(1000)+1));
        }

        MainViewModel viewModel = new MainViewModel(list);
        TableView<TransactionViewModel> tableView = new TableView<>();
        tableView.setEditable(true);

        TableColumn<TransactionViewModel, String> name = new TableColumn<>("Name");
        TableColumn<TransactionViewModel, BigDecimal> amount = new TableColumn<>("Amount");
        TableColumn<TransactionViewModel, BigDecimal> balance = new TableColumn<>("Balance");

        name.setCellValueFactory(new PropertyValueFactory<>("name"));
        amount.setCellValueFactory(new PropertyValueFactory<>("amount"));
        amount.setCellFactory(TextFieldTableCell.forTableColumn(new BigDecimalStringConverter()));
        balance.setCellValueFactory(new PropertyValueFactory<>("balance"));
        balance.setCellFactory(BigDecimalSignumTextFieldTableCell.newForTableColumn(new BigDecimalStringConverter()));

        tableView.getColumns().addAll(Arrays.asList(name, amount, balance));
        tableView.setItems(viewModel.transactionItems());

        StackPane root = new StackPane();
        root.getChildren().add(tableView);

        Scene scene = new Scene(root, 300, 250);

        primaryStage.setTitle("Hello World!");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    /**
     * The main() method is ignored in correctly deployed JavaFX application.
     * main() serves only as fallback in case the application can not be
     * launched through deployment artifacts, e.g., in IDEs with limited FX
     * support. NetBeans ignores main().
     *
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }

    public class MainViewModel {

        /** Diese Liste erkennt Änderungen an den genannten Properties der Elemente */
        private final ObservableList<TransactionViewModel> transactionItems = FXCollections.observableList(
                new ArrayList<>(), tvm -> new Observable[]{tvm.nameProperty(), tvm.amountProperty()});

        public MainViewModel(List<Transcation> transactions) {
            transactionItems.addAll(transactions.stream()
                                                .map(TransactionViewModel::new)
                                                .collect(Collectors.toList()));

            refreshBalance();
            transactionItems.addListener((ListChangeListener<TransactionViewModel>) c -> refreshBalance());
        }

        private void refreshBalance() {
            TransactionViewModel previous = null;
            for (TransactionViewModel transactionItem : transactionItems) {
                if(previous == null) {
                    transactionItem.setBalance(transactionItem.getAmount());
                } else if (previous != transactionItem) {
                    transactionItem.setBalance(previous.getBalance().add(transactionItem.getAmount()));
                }
                previous = transactionItem;
            }
        }

        public final ObservableList<TransactionViewModel> transactionItems() {
            return transactionItems;
        }

    }

    public class TransactionViewModel {

        private final SimpleStringProperty name = new SimpleStringProperty(this,"name");
        private final ObjectProperty<BigDecimal> amount = new SimpleObjectProperty<>(this, "amount");
        private final ObjectProperty<BigDecimal> balance = new SimpleObjectProperty<>(this, "balance");
     
        public TransactionViewModel(Transcation transcation) {
            this(transcation.getName(), BigDecimal.valueOf(transcation.getAmount()), BigDecimal.ZERO);
        }

        private TransactionViewModel(String name, BigDecimal amount, BigDecimal balance) {
            setName(name);
            setAmount(amount);
            setBalance(balance);
        }

        public final StringProperty nameProperty() {
            return name;
        }

        public final String getName() {
            return name.get();
        }

        public final void setName(String name) {
            this.name.set(name);
        }

        public final ObjectProperty<BigDecimal> amountProperty() {
            return amount;
        }

        public final BigDecimal getAmount() {
            return amount.get();
        }

        public final void setAmount(BigDecimal amount) {
            this.amount.set(amount);
        }

        public final ObjectProperty<BigDecimal> balanceProperty() {
            return balance;
        }

        public final BigDecimal getBalance() {
            return balance.get();
        }

        public final void setBalance(BigDecimal balance) {
            this.balance.set(balance);
        }

    }
   
    public class Transcation {

        String name;
        Integer amount;

        public Transcation(String name, int amount) {
            this.name = name;
            this.amount = amount;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAmount() {
            return amount;
        }

        public void setAmount(int amount) {
            this.amount = amount;
        }

        @Override
        public String toString() {
            return amount.toString();
        }
    }

    public static class BigDecimalSignumTextFieldTableCell<S, T extends BigDecimal> extends TextFieldTableCell<S, T> {

        @Override
        public void updateItem(T item, boolean empty) {
            super.updateItem(item, empty);
            if (item == null || empty) {
                setText(null);
            } else {
                setText(getConverter().toString(item));
                setTextFill( item.signum() == -1 ? Color.RED : Color.BLACK );
            }
        }

        /**
         * Creates a TextFieldTableCell that provides a {@link TextField} when put
         * into editing mode that allows editing of the cell content. This method
         * will work on any TableColumn instance, regardless of its generic type.
         * However, to enable this, a {@link StringConverter} must be provided that
         * will convert the given String (from what the user typed in) into an
         * instance of type T. This item will then be passed along to the
         * {@link TableColumn#onEditCommitProperty()} callback.
         *
         * @param converter A {@link StringConverter converter} that can convert
         *                  the given String (from what the user typed in) into an instance of
         *                  type T.
         */
        public BigDecimalSignumTextFieldTableCell(StringConverter<T> converter) {
            super(converter);
        }

        /**
         * Provides a {@link javafx.scene.control.TextField} that allows editing of the cell content when
         * the cell is double-clicked, or when
         * {@link TableView#edit(int, javafx.scene.control.TableColumn) } is called.
         * This method will work  on any {@link TableColumn} instance, regardless of
         * its generic type. However, to enable this, a {@link StringConverter} must
         * be provided that will convert the given String (from what the user typed
         * in) into an instance of type T. This item will then be passed along to the
         * {@link TableColumn#onEditCommitProperty()} callback.
         *
         * @param converter A {@link StringConverter} that can convert the given String
         *      (from what the user typed in) into an instance of type T.
         * @return A {@link Callback} that can be inserted into the
         *      {@link TableColumn#cellFactoryProperty() cell factory property} of a
         *      TableColumn, that enables textual editing of the content.
         */
        public static <S,T extends BigDecimal> Callback<TableColumn<S,T>, TableCell<S,T>> newForTableColumn(StringConverter<T> converter) {
            return unused -> new BigDecimalSignumTextFieldTableCell<>(converter);
        }

    }
}
 
Passende Stellenanzeigen aus deiner Region:

Neue Themen

Oben