MaterialFX icon indicating copy to clipboard operation
MaterialFX copied to clipboard

Table listener for row selection

Open panosru opened this issue 2 years ago • 8 comments

In a non MFX table in order to observe for row selection you would use something like this:

table.getSelectionModel().selectedItemProperty().addListener((observableValue, oldValue, newValue) -> {
    // logic here
});

with MFXTable I tried the following:

table.getSelectionModel().getSelection()
     .addListener((MapChangeListener<Integer, Person>) change -> {
         // logic here
     }));

But I'm not getting any response on row selection/change

My goals are:

  • By default some buttons are disabled and I want them to be enabled only when a row from the table is selected.
  • A label is updated on row selection/change.

So far I have tried this:

table.setOnMouseClicked(e -> {
    // logic here
});

But the event is only triggered when you click on the TableView object, therefore, when you click on TableRow it is not invoked.

Thank you for your good work!

panosru avatar Apr 25 '22 23:04 panosru

Could you provide a minimal reproducible example?

Tech-Expert-Wizard avatar Apr 26 '22 15:04 Tech-Expert-Wizard

@Tech-Expert-Wizard The fxml I have is the following:

<AnchorPane xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1"
            fx:id="childRoot" prefHeight="800" prefWidth="800"
            fx:controller="com.cn5004ap.payroll.controller.employees.ListController">
    <stylesheets>
        <URL value="@../../css/employees.css"/>
        <URL value="@../../css/table.css"/>
    </stylesheets>
    <children>
        <HBox fx:id="topMenu" prefHeight="50.0" prefWidth="712.0" spacing="10.0" layoutX="14.0" layoutY="15">
            <children>
                <MFXButton fx:id="showBtn" onAction="#show" styleClass="employee-show" text="Show" disable="true">
                    <graphic>
                        <FontIcon iconLiteral="fas-user-tie"/>
                    </graphic>
                </MFXButton>
                <MFXButton fx:id="hireBtn" onAction="#hire" styleClass="employee-hire" text="Hire">
                    <graphic>
                        <FontIcon iconLiteral="fas-user-plus"/>
                    </graphic>
                </MFXButton>
                <MFXButton fx:id="updateBtn" onAction="#update" styleClass="employee-update" text="Update"
                           disable="true">
                    <graphic>
                        <FontIcon iconLiteral="fas-user-edit"/>
                    </graphic>
                </MFXButton>
                <MFXButton fx:id="terminateBtn" onAction="#terminate" styleClass="employee-terminate" text="Terminate"
                           disable="true">
                    <graphic>
                        <FontIcon iconLiteral="fas-user-alt-slash"/>
                    </graphic>
                </MFXButton>
            </children>
        </HBox>
        <MFXPaginatedTableView fx:id="table" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
                               prefHeight="700.0" prefWidth="772.0" layoutX="14.0" layoutY="85"/>
    </children>
</AnchorPane>

and I setup my table like so:

private void setupTable()
{
    MFXTableColumn<EmployeeEntity> firstNameColumn = new MFXTableColumn<>(
        "Name", true, Comparator.comparing(EmployeeEntity::getFirstName));
    MFXTableColumn<EmployeeEntity> lastNameColumn = new MFXTableColumn<>(
        "Surname", true, Comparator.comparing(EmployeeEntity::getLastName));
    MFXTableColumn<EmployeeEntity> departmentColumn = new MFXTableColumn<>(
        "Department", true, Comparator.comparing(EmployeeEntity::getDepartment));
    MFXTableColumn<EmployeeEntity> titleColumn = new MFXTableColumn<>(
        "Title", true, Comparator.comparing(EmployeeEntity::getTitle));
    MFXTableColumn<EmployeeEntity> salaryColumn = new MFXTableColumn<>(
        "Salary", true, Comparator.comparing(EmployeeEntity::getSalary));

    firstNameColumn.setRowCellFactory(employee -> new MFXTableRowCell<>(EmployeeEntity::getFirstName));
    lastNameColumn.setRowCellFactory(employee -> new MFXTableRowCell<>(EmployeeEntity::getLastName));
    departmentColumn.setRowCellFactory(employee -> new MFXTableRowCell<>(EmployeeEntity::getDepartment));
    titleColumn.setRowCellFactory(employee -> new MFXTableRowCell<>(EmployeeEntity::getTitle));
    salaryColumn.setRowCellFactory(employee -> new MFXTableRowCell<>(EmployeeEntity::getSalaryPretty) {{
        setAlignment(Pos.CENTER_RIGHT);
    }});

    table.getTableColumns().addAll(
        firstNameColumn,
        lastNameColumn,
        departmentColumn,
        titleColumn,
        salaryColumn
    );

    table.getFilters().addAll(
        new StringFilter<>("Name", EmployeeEntity::getFirstName),
        new StringFilter<>("Surname", EmployeeEntity::getLastName),
        new StringFilter<>("Department", EmployeeEntity::getDepartment),
        new StringFilter<>("Title", EmployeeEntity::getTitle),
        new DoubleFilter<>("Salary", EmployeeEntity::getSalary)
    );

    table.setOnMouseClicked(e -> {
        if (e.getButton().equals(MouseButton.PRIMARY))
        {
            showBtn.setDisable(!hasTableSelection());
            updateBtn.setDisable(!hasTableSelection());
            terminateBtn.setDisable(!hasTableSelection());
        }
    });
}

The initialize() method of the controller has the following:

table.setRowsPerPage(19);

setupTable();

table.getSelectionModel().setAllowsMultipleSelection(false);

ObservableList<EmployeeEntity> employees = FXCollections.observableArrayList(
    employeeRepository.findAll()
);

table.setItems(employees);
table.autosizeColumnsOnInitialization();

Normally, with a non MaterialFX table I would use table.getSelectionModel().selectedItemProperty().addListener(...) to listen for any row selections, but with MFXPaginatedTableView I don't see any way to accomplish it since selectedItemProperty() isn't exposed by IMultipleSelectionModel

panosru avatar Apr 26 '22 16:04 panosru

@panosru

Ah yes I finally see what's going on. So, from your code I can reproduce the issue, and I was ready to label this as a bug but then I realized what is the problem.

The MultipleSelectionModel uses a MapProperty and exposes getters and setters to access it. In some occasions the Map wrapped by the MapProperty is replaced with a new one (so that changes are "atomic") and this makes the getSelection() method useless because the user doesn't know when this occurs. So, getSelection() returns the current wrapped ObservableMap.

What you want to do instead is to attach the listener on the MapProperty so you have to do:

table.getSelectionModel().selectionProperty().addListener((MapChangeListener<? super Integer, ? super Device>) change -> {
 // logic
});

Now, this is not a bug, but it's definitely an enhancement to make. I still don't know what to do... maybe just remove the getSelection() method or replace the MapProperty with an ObjectProperty<ObservableMpa>... I'll test the different approaches and figure out which is the best

palexdev avatar Apr 26 '22 16:04 palexdev

@palexdev I was about to post a minimum code to reproduce since my previous post was a bit "hey here's my code, find me a solution" hahaha but thankfully you got the point :)

I think I have tried using selectionProperty(), let me check again and I'll post back :)

UPDATE: @palexdev yeah, using selectionProperty() worked, I could swear that I tried it!! I really read docs and anything I can find before asking for support! I guess I might've missed it!

Thanks!

panosru avatar Apr 26 '22 16:04 panosru

@panosru haha well your description of the issue was crystal clear and accompanied by some code so I didn't really need a MRE this time. You're welcome, thanks for using MaterialFX

P.S: I'll re-open this issue as a remainder for the enhancement

palexdev avatar Apr 26 '22 16:04 palexdev

@palexdev well, based on what you have described, I guess maybe removing the getSelection() method will reduce the confusion that others might also bump into. Currently, when I want to retrieve data from the table I use the following method:

private @Nullable EmployeeEntity getTableSelection()
{
    if (hasTableSelection())
        return table.getSelectionModel().getSelectedValues().get(0);

    return null;
}

and hasTableSelection() looks like so:

private boolean hasTableSelection()
{
    return !table.getSelectionModel().getSelection().isEmpty();
}

As you can see, the only place where I'm using the getSelection() method is there, but that could easily be replaced with getSelectedValues().isEmpty(), so, yeah, I'm not seeing much value out of getSelection() other than confusion, but, I might be missing something since I just started using your MaterialFX project.

panosru avatar Apr 26 '22 16:04 panosru

@panosru Yeah I agree Heck you could even do:

deTable.getSelectionModel().selectionProperty().isEmpty();

palexdev avatar Apr 26 '22 17:04 palexdev

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar May 24 '22 17:05 stale[bot]