0

I have a String containing a Date in the form "dd-MM-yyyy" I want the dates to be sorted when displayed in the table view.

Controller Class

private final ObservableList<Stock>stockList = FXCollections.observableArrayList();
public class StocksController implements Initializable {
    @FXML
    private TableColumn<Stock, String> date;

    public void initialize(URL url, ResourceBundle resourceBundle){
        addToCSV.setOnAction(event -> {
            try {
                writeCSV();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        });
        dollarButton.setOnAction(event -> {changeDollar();});
        percentButton.setOnAction(event -> {changePercent();});
        price.setCellValueFactory(new PropertyValueFactory<>("price"));
        date.setCellValueFactory(new PropertyValueFactory<>("dateRN"));
        checker.setCellValueFactory(new PropertyValueFactory<>("checker"));
        stockView.setItems(stockList);
        search();
        readCSV();
    }
    public void writeCSV() throws IOException {
        String stockNames = ("$" + stockName.getText()).trim().toUpperCase();
        Double stockPrices = Double.parseDouble((stockPrice.getText()).trim());
        String stockDates = stockDate.getValue().format(DateTimeFormatter.ofPattern("dd-MM-yyyy"));
        FileWriter fw = new FileWriter("src/stocks.csv",true);
        BufferedWriter bw = new BufferedWriter(fw);
        PrintWriter pw = new PrintWriter(bw);
        pw.println(stockNames + ',' + stockDates + ',' + stockPrices);
        pw.flush();
        pw.close();
        stockList.clear();
        readCSV();
    }
    public void readCSV(){
        String csv;
        csv = "src/stocks.csv";
        String delimiter = ",";
        try{
            System.out.println(new FileReader(csv));
            BufferedReader br = new BufferedReader(new FileReader(csv));
            String line;
            while((line = br.readLine()) != null) {
                String[] values = line.split(delimiter);
                String stockNames = values[0];
                String stockDates = values[1];
                Double stockPrices = Double.parseDouble(values[2]);
                Stock stock = new Stock(stockNames, stockPrices,stockDates);
                stockList.add(stock);
            }
        } catch (FileNotFoundException ex) {
            Logger.getLogger(StocksController.class.getName())
                    .log(Level.FINE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(StocksController.class.getName())
                    .log(Level.FINE, null, ex);
        }
    }
}

Stock Class

public class Stock {
    private String dateRN;

    public Stock(){
        this.dateRN = "";
    }
    public Stock(String ticker, Double price, String dateRN){
        this.ticker = ticker;
        this.price = price;
        this.dateRN = dateRN;
        this.checker = new CheckBox();
    }
    public String getDateRN() {
        return dateRN;
    }
    public void setDateRN(String dateRN) {
        this.dateRN = dateRN;
    }
}

I've tried creating a comparator for Stock but it's not working so I gave up on that method is there anyway I could just create a function inside my controller sorting it directly from the observableList itself? or even when I read in the CSV?

This is how it looks right now, however, I want the dates to be sorted

Dan
  • 3,647
  • 5
  • 20
  • 26
  • 4
    Are you sure you want to store the dates as string as opposed to `LocalDate` or similar? – dan1st Aug 07 '22 at 07:28
  • 2
    I don’t see where stockList is declared. It is usually good to provide a [mcve] so the issue can be replicated via copy/paste without change. You can just provide some dummy data, rather than csv as that is irrelevant, but it should compile, you provide fxml if relevant or just a Java app if that is all that is needed, but then it shouldn’t use fxml at all. – jewelsea Aug 07 '22 at 07:33
  • 3
    You shouldn’t put nodes like check boxes in your model objects, it will cause problems especially if you put them in a table. – jewelsea Aug 07 '22 at 07:35
  • 5
    Wrap your list in a [SortedList](https://openjfx.io/javadoc/17/javafx.base/javafx/collections/transformation/SortedList.html). Use a LocalDate or Instant to represent the date. Use a cell factory to render the local date or instant using the format you need. Similarly for the checkbox, use a CheckBoxTableCell backed by a BooleanProperty in the model. – jewelsea Aug 07 '22 at 07:37
  • @jewelsea I had no issues with the checkboxes however I did need to place them in the table as I need the ability to choose ONLY two rows and compare there prices –  Aug 07 '22 at 07:42
  • @jewelsea I'm a bit confused on your solution to the problem. I'll try getting done what I understood. Also as per your suggestion I included the stockList declaration I must have missed it when adding the code, thanks for pointing it out –  Aug 07 '22 at 07:45
  • 1
    For further info on sorted lists applied to TableViews and cell factories, see the [TableView](https://openjfx.io/javadoc/17/javafx.controls/javafx/scene/control/TableView.html) does documentation. [makery also provide a tutorial](https://code.makery.ch/blog/javafx-8-tableview-sorting-filtering/). – jewelsea Aug 07 '22 at 07:55
  • I don't see anything in your snippets related to sorting (on the other hand: reading and writing data is completely unrelated) - as already suggested by @jewelsea, [mcve] please (also follow the other advices, f.i. it's __wrong__ to add nodes as data) – kleopatra Aug 07 '22 at 08:15
  • Where did the `Stock` class come from? It cannot be compiled. – SedJ601 Aug 07 '22 at 15:15

3 Answers3

2

The application class, StockTable

import java.net.URL;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class StockTable extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        URL url = getClass().getResource("stoktabl.fxml");
        FXMLLoader loader = new FXMLLoader(url);
        BorderPane root = loader.load();
        Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

The FXML file, stoktabl.fxml

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.BorderPane?>

<BorderPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="jfxtests.StocksController">
    <center>
        <TableView fx:id="table">
            <columns>
                <TableColumn fx:id="dateColumn" text="Date">
                </TableColumn>
                <TableColumn fx:id="priceColumn" text="Price">
                </TableColumn>
                <TableColumn fx:id="checkBoxColumn" text="Check Box">
                </TableColumn>
            </columns>
        </TableView>
    </center>
</BorderPane>

FXML controller class, StocksController

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.util.StringConverter;

public class StocksController extends StringConverter<LocalDate> {
    private static final DateTimeFormatter  FORMATTER = DateTimeFormatter.ofPattern("dd-MM-yyyy", Locale.ENGLISH);
    private static final String  DELIMITER = ",";

    @FXML
    TableColumn<Stock, Boolean>  checkBoxColumn;

    @FXML
    TableColumn<Stock, LocalDate>  dateColumn;

    @FXML
    TableColumn<Stock, BigDecimal>  priceColumn;

    @FXML
    TableView<Stock>  table;

    @Override // javafx.util.StringConverter
    public String toString(LocalDate object) {
        return object.format(FORMATTER);
    }

    @Override // javafx.util.StringConverter
    public LocalDate fromString(String string) {
        return LocalDate.parse(string, FORMATTER);
    }

    @FXML
    private void initialize() {
        table.setEditable(true);
        checkBoxColumn.setCellValueFactory(f -> f.getValue().checkedProperty());
        checkBoxColumn.setCellFactory(CheckBoxTableCell.forTableColumn(checkBoxColumn));
        dateColumn.setCellValueFactory(f -> f.getValue().dateProperty());
        dateColumn.setCellFactory(TextFieldTableCell.forTableColumn(this));
        priceColumn.setCellValueFactory(f -> f.getValue().priceProperty());
        ObservableList<Stock> items = FXCollections.observableArrayList();
        InputStream is = getClass().getResourceAsStream("stocks.csv");
        try (InputStreamReader isr = new InputStreamReader(is);
             BufferedReader br = new BufferedReader(isr)) {
            String line = br.readLine();
            while (line != null) {
                String[] fields = line.split(DELIMITER);
                BigDecimal price = new BigDecimal(fields[2]);
                LocalDate date = LocalDate.parse(fields[1], FORMATTER);
                String name = fields[0];
                Stock stock = new Stock(price, date, name);
                items.add(stock);
                line = br.readLine();
            }
            items = items.sorted();
            table.setItems(items);
        }
        catch (IOException xIo) {
            throw new RuntimeException(xIo);
        }
    }
}

The "model" class, Stock

import java.math.BigDecimal;
import java.time.LocalDate;

import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;

public class Stock implements Comparable<Stock> {
    private SimpleObjectProperty<BigDecimal>  price;
    private SimpleBooleanProperty  checked;
    private SimpleObjectProperty<LocalDate>  date;
    private SimpleStringProperty  name;

    public Stock(BigDecimal price, LocalDate date, String name) {
        this.price = new SimpleObjectProperty<BigDecimal>(this, "price", price);
        this.date = new SimpleObjectProperty<LocalDate>(this, "date", date);
        this.name = new SimpleStringProperty(this, "name", name);
        checked = new SimpleBooleanProperty(false);
    }

    public SimpleBooleanProperty checkedProperty() {
        return checked;
    }

    public boolean getChecked() {
        return checked.get();
    }

    public void setChecked(boolean check) {
        checked.set(check);
    }

    public SimpleObjectProperty<LocalDate> dateProperty() {
        return date;
    }

    public LocalDate getDate() {
        return date.get();
    }

    public void setDate(LocalDate ld) {
        date.set(ld);
    }

    public SimpleStringProperty nameProperty() {
        return name;
    }

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

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

    public SimpleObjectProperty<BigDecimal> priceProperty() {
        return price;
    }

    public BigDecimal getPrice() {
        return price.get();
    }

    public void setPrice(BigDecimal cost) {
        price.set(cost);
    }

    @Override
    public int compareTo(Stock o) {
        int result;
        if (o == null) {
            result = 1;
        }
        else {
            if (o.date == null) {
                result = 1;
            }
            else {
                if (o.date.get() == null) {
                    result = 1;
                }
                else {
                    if (date == null) {
                        result = -1;
                    }
                    else {
                        if (date.get() == null) {
                            result = -1;
                        }
                        else {
                            if (date.get().isBefore(o.date.get())) {
                                result = -1;
                            }
                            else if (date.get().isAfter(o.date.get())) {
                                result = 1;
                            }
                            else {
                                result = 0;
                            }
                        }
                    }
                }
            }
        }
        return result;
    }
}

The CSV file, stocks.csv

first,05-08-2022,1.22
second,03-08-2022,1.35
third,07-08-2022,67.0
last,06-08-2022,4.5
  • Class Stock implements comparable to allow items to be sorted by natural order. Refer to [default] method sorted in interface javafx.collections.ObservableList.
  • The TableView is made editable so as to allow selecting the check boxes.
  • Values are read from the CSV file and converted to the appropriate type in order to create Stock instances.
  • A StringConverter is used to both format and parse values in the date column.
  • Files stoktabl.fxml and stocks.csv are located in the same folder as file StockTable.class
  • All above [Java] classes are in the same package (even though I omitted the package statements from the above code.
  • Controller classes no longer need to implement interface javafx.fxml.Initializable. They may, optionally, declare method initialize that takes no parameters and returns void.

Here is a screen capture:

screen capture

Abra
  • 19,142
  • 7
  • 29
  • 41
  • 1
    There is a built-in [`LocalDateStringConverter`](https://openjfx.io/javadoc/17/javafx.base/javafx/util/converter/LocalDateStringConverter.html), which could be used. – jewelsea Aug 07 '22 at 20:00
2

@Abra has shown how to effect sorting by implementing Comparable<Stock>, and @Dan has shown how to leverage LocalDate::compareTo, provided by its implementation of Comparable<ChronoLocalDate>. The example below focuses on the benefit of using LocalDate directly in your model. In particular, you can simply add() the date column to the sort order. As shown in the TableView API, a simple binding can restore the original, unsorted order.

Date sort

import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;
import javafx.application.Application;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.transformation.SortedList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

/**
 * @see https://stackoverflow.com/a/72437984/230513
 * @see https://stackoverflow.com/a/72437984/230513
 */
public class LocalDateTest extends Application {

    private record Model(LocalDate date, String name) {}

    @Override
    public void start(Stage stage) {
        stage.setTitle(this.getClass().getName());
        var model = FXCollections.observableArrayList(
            new Model(LocalDate.now().minusDays(1),"Yesterday"),
            new Model(LocalDate.now(), "Today"),
            new Model(LocalDate.now().plusDays(1), "Tommorrow"),
            new Model(LocalDate.now().with(TemporalAdjusters.lastDayOfMonth()), "Deadline")
        );
        SortedList sortedList = new SortedList(model);
        var table = new TableView(sortedList);
        sortedList.comparatorProperty().bind(table.comparatorProperty());

        TableColumn<Model, LocalDate> date = new TableColumn<>("Date");
        date.setCellValueFactory(cd -> new SimpleObjectProperty(cd.getValue().date()));
        table.getColumns().add(date);
        table.getSortOrder().add(date);

        TableColumn<Model, String> name = new TableColumn<>("Name");
        name.setCellValueFactory(cd -> new SimpleStringProperty(cd.getValue().name()));
        table.getColumns().add(name);

        StackPane root = new StackPane(table);
        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
1

As it has been pointed out in the comments, I second @dan1st's suggestion of using a LocalDate instead of a plain String to represent a date and order your Stock instances according to their dateRN.

However, If for some reason you're actually bound to a String implementation, then you could resort to a LocalDate conversion within the compareTo definition and still implement the Comparable interface in your Stock class.

Here is a rough implementation:

public class Stock implements Comparable<Stock> {
    private String ticker;
    private Double price;
    private String dateRN;

    public Stock() {
        this.dateRN = "";
    }

    public Stock(String ticker, Double price, String dateRN) {
        this.ticker = ticker;
        this.price = price;
        this.dateRN = dateRN;
    }

    //... getters, setters & rest of class implementation

    @Override
    public int compareTo(Stock o) {
        return LocalDate.parse(dateRN, DateTimeFormatter.ofPattern("dd-MM-yyyy")).compareTo(LocalDate.parse(o.dateRN, DateTimeFormatter.ofPattern("dd-MM-yyyy")));
    }

    @Override
    public String toString() {
        return "Stock{dateRN='" + dateRN + '\'' +'}';
    }
}
public class Main {
    public static void main(String[] args) {
        List<Stock> list = new ArrayList<>(List.of(
                new Stock("test1", 1.5, "05-08-2022"),
                new Stock("test2", 2.5, "03-08-2022"),
                new Stock("test3", 3.5, "07-08-2022"),
                new Stock("test4", 4.5, "06-08-2022")
        ));

        System.out.println(list);

        Collections.sort(list);
        System.out.println("\n" + list);
    }
}

Here is a link to test the code above:

https://www.jdoodle.com/iembed/v0/tLI

Output

Original and sorted output based on the sample data of your question.

[Stock{dateRN='05-08-2022'}, Stock{dateRN='03-08-2022'}, Stock{dateRN='07-08-2022'}, Stock{dateRN='06-08-2022'}]

[Stock{dateRN='03-08-2022'}, Stock{dateRN='05-08-2022'}, Stock{dateRN='06-08-2022'}, Stock{dateRN='07-08-2022'}]
Dan
  • 3,647
  • 5
  • 20
  • 26