-1

I have a TableView and two text fields. When the user write numbers into textfields and push the button. All tablerows should be painted as a percentage. For exmaple 30% should be yellow and 70% should be green. enter image description here

I tried to use lookup method to look for TableView objects. Adding new css class changed the colour of tablerows. But this solution is not good. Lookup can find only visible elements.

@FXML
void confirmPercentage(ActionEvent event) {
    clearSelectionsOfTestingTbaleView();
    TableRow[] tableRows = selectTrainingDataTableView.lookupAll(".table-row-cell").toArray(new TableRow[0]);

    double percent = Double.parseDouble(this.trainingSetPercentageTextField.getText());
    percent = percent > 1 ? percent / 100 : percent;
    int trainIndexes = (int) (Math.round(tableRows.length * percent));

    for (int i = 0 ; i < trainIndexes; i++){
        tableRows[i].getStyleClass().add("selected-as-train");
    }
}

I have 15 notes, but there are only 13... enter image description here

So what is the way to paint ALL tablerows?

SOLUTION

Finally, I received my sight!

enter image description here

So i created two sets to keep selected indexes. And, i just set PseudoClass according this indexes...

void init()
    Set<Integer> testSetIndexes = new HashSet<>();
    Set<Integer> trainSetIndexes = new HashSet<>();
    tableView.setRowFactory( tableView2 -> {

        PseudoClass train = PseudoClass.getPseudoClass("train");
        PseudoClass test = PseudoClass.getPseudoClass("test");
        PseudoClass trainAndTest = PseudoClass.getPseudoClass("train-and-test");
        /*Percentage selection with button*/
        final TableRow<List<Double>> row = new TableRow<List<Double>>(){

            @Override
            public void updateItem(List<Double> item, boolean empty){

                if (item != null) {
                    int index = dataInsideTableView.indexOf(item);
                    this.pseudoClassStateChanged(train, trainSetIndexes.contains(index));
                    this.pseudoClassStateChanged(test, testSetIndexes.contains(index));
                    this.pseudoClassStateChanged(trainAndTest, trainSetIndexes.contains(index) && testSetIndexes.contains(index));
                } else {
                    this.pseudoClassStateChanged(train, false);
                    this.pseudoClassStateChanged(test, false);
                    this.pseudoClassStateChanged(trainAndTest, false);
                }
            }
        };

        /*Mouse selection*/
        row.addEventFilter(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent event) {
                if (manualSelectionCheckBox.isSelected()) {
                    if (event.isPrimaryButtonDown()) {
                        int index = dataInsideTableView.indexOf(row.getTableView().getItems().get(row.getIndex()));
                        if(trainSetIndexes.contains(index)){
                            trainSetIndexes.remove(index);
                        } else {
                            trainSetIndexes.add(index);
                        }
                        selectTrainingDataTableView.refresh(); //call update
                    } else if (event.isSecondaryButtonDown()) {
                        int index = dataInsideTableView.indexOf(row.getTableView().getItems().get(row.getIndex()));
                        if(testSetIndexes.contains(index)){
                            testSetIndexes.remove(index);
                        } else {
                            testSetIndexes.add(index);
                        }
                        tableView.refresh();
                    }
                }
            }
        });

        return row;
    });

I click "Refresh" button, confirmPercentage method just recount indexes and refresh the table.

@FXML
void confirmPercentage(ActionEvent event) {
    trainSetIndexes.clean();
    testSetIndexes.clean();

    double percent = Double.parseDouble(this.trainingSetPercentageTextField.getText());
    percent = percent > 1 ? percent / 100 : percent;
    trainSize = (int) (Math.floor(usedData.size() * percent));
    percent = Double.parseDouble(this.testingSetPercentageTextField .getText());
    percent = percent > 1 ? percent / 100 : percent;
    testSize = (int) (Math.floor(usedData.size() * percent));

    //put indexes
    for (int i = 0 ; i < trainSize; i++){
        trainSetIndexes.add(i);
    }

    for (int i = usedData.size()-1 ; i >= usedData.size()-1-testSize; i--){
        testSetIndexes.add(i);
    }
    this.tableView.refresh();
}
SkaaRJ
  • 173
  • 1
  • 1
  • 14
  • completely wrong approach: never-ever think to a tableRow in the scenegraph, instead use a custom cellFactory for the rows that configures itself based on state in the model (and don't forget to _model_ that state) – kleopatra Jan 28 '19 at 10:25
  • @JanS. It is not duplicate, because UpdateItem works with visible rows. I tried RowFactory - it is working with visible rows too. While they are hiding becuse scrolling RowFactory is not working with them – SkaaRJ Jan 28 '19 at 10:25
  • 2
    there are no rows that are invisible, they _don't exist_ ;) Please read up on the re-use mechanism of cells – kleopatra Jan 28 '19 at 10:27
  • 1
    and please don't delete your questions just to pose the same again - all input will be lost which is a loss of time for everybody involved – kleopatra Jan 28 '19 at 10:30
  • @kleopatra And yet I do not think that this question is a duplicate. The solution that was proposed in another topic made me add a new column to somehow solve the problem. I consider this a crutch for my task, which in some way corrupts the data. – SkaaRJ Jan 28 '19 at 13:56
  • you don't have to add a new column, just a state property in your data model. Then configure the items with an extractor on that state property (to make it fire an update event if the state changes) - the rest is automagic :) Modulo a bug in tableRow that might require to override its indexChanged instead of/ in addition to updateItem .. anyway, in your next question please provide a [mcve] that demonstrates the problem (vs. mere snippets that are very rarely enough) – kleopatra Jan 28 '19 at 15:51
  • and repeating: it's wrong to loop across the tableRows! always! you have exactly zero control about their re-use, so might grab all or none. – kleopatra Jan 28 '19 at 15:58
  • your last edit is not only not good, it's wrong ;) – kleopatra Jan 28 '19 at 16:33
  • @kleopatra, agree - loop across tableRows - is a bad idea. Already fixed it... But as for data property... Could you show some simple code? I can not imagine how such a little thing to turn – SkaaRJ Jan 28 '19 at 16:33
  • @kleopatra it works fine. On my computer) – SkaaRJ Jan 28 '19 at 16:34
  • @kleopatra okey, i understand why it is wrong... – SkaaRJ Jan 28 '19 at 16:38
  • still not good: refresh is _evil_ - absolute emergency api if nothing else is possible. Here a clean solution is possible, as outlined in one of my earlier comments ;) – kleopatra Jan 29 '19 at 10:29

1 Answers1

-1

you can have TableRow StyleClass as field in your TableView Data Model

fxml view

<VBox fx:id="root" prefHeight="300.0" prefWidth="600.0" fx:controller="sample.View"
      xmlns="http://javafx.com/javafx/8.0.172-ea" xmlns:fx="http://javafx.com/fxml/1" >
    <stylesheets>
        <URL value="@app.css" />
    </stylesheets>

    <ToolBar>
        <Button fx:id="set" mnemonicParsing="false" onAction="#set" text="Set" />
        <Button fx:id="reset" mnemonicParsing="false" onAction="#reset" text="Reset" />
    </ToolBar>


    <TableView fx:id="tvDevices" VBox.vgrow="ALWAYS">
        <columns>
            <TableColumn fx:id="colNum1" text="Num1">
                <cellValueFactory>
                    <PropertyValueFactory property="num1" />
                </cellValueFactory>
            </TableColumn>
            <TableColumn fx:id="colNum2" text="Num2">
                <cellValueFactory>
                    <PropertyValueFactory property="num2" />
                </cellValueFactory>
            </TableColumn><TableColumn fx:id="colNum3" text="Num3">
                <cellValueFactory>
                    <PropertyValueFactory property="num3" />
                </cellValueFactory>
            </TableColumn>
        </columns>
    </TableView>
</VBox>

Data View Model class

public class Foo {
    private DoubleProperty num1;
    private DoubleProperty num2;
    private DoubleProperty num3;
    private StringProperty styleClass;

    public Foo(int num1, int num2, int num3) {
        this.num1 = new SimpleDoubleProperty(num1);
        this.num2 = new SimpleDoubleProperty(num2);
        this.num3 = new SimpleDoubleProperty(num3);
        this.styleClass = new SimpleStringProperty();
    }

    public double getNum1() {
        return num1.get();
    }

    public DoubleProperty num1Property() {
        return num1;
    }

    public void setNum1(double num1) {
        this.num1.set(num1);
    }

    public double getNum2() {
        return num2.get();
    }

    public DoubleProperty num2Property() {
        return num2;
    }

    public void setNum2(double num2) {
        this.num2.set(num2);
    }

    public double getNum3() {
        return num3.get();
    }

    public DoubleProperty num3Property() {
        return num3;
    }

    public void setNum3(double num3) {
        this.num3.set(num3);
    }

    public String getStyleClass() {
        return styleClass.get();
    }

    public StringProperty styleClassProperty() {
        return styleClass;
    }

    public void setStyleClass(String styleClass) {
        this.styleClass.set(styleClass);
    }
}

view controller

public class View implements Initializable {

    @FXML
    private TableView<Foo> tvDevices;

    @Override
    public void initialize(URL location, ResourceBundle resources) {    
            tvDevices.setRowFactory(param -> {
            TableRow<Foo> row = new TableRow<>();
            //This listener will monitor the Change in the Modal StyleClass to keep row updated when StyleClass value chenge.
            ChangeListener<String> listener = (observable, oldValue, newValue) -> {
                if (newValue == null) {
                    //reset StyleClass to default by removing any additional class's
                    row.getStyleClass().remove(3,row.getStyleClass().size());
                } else {
                    //removing any additional class's and add the new one
                    row.getStyleClass().remove(3,row.getStyleClass().size());
                    row.getStyleClass().add(newValue);
                }
            };
            //This listener will monitor the particular Modal attached to this row.
            row.itemProperty().addListener((observable, oldValue, newValue) -> {
                if (oldValue != null) {
                    //remover the listener from the old item
                    oldValue.styleClassProperty().removeListener(listener);
                }
                if (newValue != null) {
                    // attach the listener to the new item
                    newValue.styleClassProperty().addListener(listener);
                    // update row StyleClass to new item StyleClass
                    if (newValue.styleClassProperty() == null) {
                        row.getStyleClass().remove(3,row.getStyleClass().size());
                    } else {
                        row.getStyleClass().remove(3,row.getStyleClass().size());
                        row.getStyleClass().add(newValue.getStyleClass());
                    }
                } else {
                    //reset StyleClass to default because new item is null
                    row.getStyleClass().remove(3,row.getStyleClass().size());
                }
            });
            return row;
        });

        for (int i = 0; i < 50; i++) {
            tvDevices.getItems().add(new Foo(1, 2, 3));
        }
    }

    @FXML
    private void reset(ActionEvent actionEvent) {
        tvDevices.getItems().forEach(foo -> foo.setStyleClass(null));
        actionEvent.consume();
    }

    @FXML
    private void set(ActionEvent actionEvent) {
        tvDevices.getItems().get(10).setStyleClass("class1");
        tvDevices.getItems().get(20).setStyleClass("class2");
        tvDevices.getItems().get(40).setStyleClass("class3");
        actionEvent.consume();
    }
}
Mahmoud Fathy
  • 124
  • 1
  • 8
  • don't mix view and data aspects - the style doesn't belong into your data! – kleopatra Jan 28 '19 at 10:26
  • anyway, I suspect that's not working at all: nobody's listening to that property and afais the items aren't wired to fire any notification such that the tableView can update itself. Seems like you didn't try it, did you ;) – kleopatra Jan 28 '19 at 10:36
  • tvDevices.getItems().forEach(foo -> foo.resetStyleClass()); foo - is data, it doesnt contain styles – SkaaRJ Jan 28 '19 at 11:02
  • sorry for my old code, updated the code and add some comments it should work now – Mahmoud Fathy Jan 28 '19 at 15:50
  • sry, but that's simply wrong (apart from having the styles as data properties) - no need for a listener in the tableRow, provided the data setup is correct ... – kleopatra Jan 28 '19 at 15:55