Home > Mobile >  Sorting ObservableList in javaFX by String Date
Sorting ObservableList in javaFX by String Date

Time:08-07

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

CodePudding user response:

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

CodePudding user response:

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'}]
  • Related