Programowa Zmiana wyglądu wiersza TableView

Po zrobieniu Oracle tutorial na temat TableView , zastanawiałem się, czy jest sposób, aby programowo zastosować inny styl CSS do wybranego wiersza TableView. Na przykład użytkownik wybiera określony wiersz, klikając przycisk" Podświetl", a wybrany wiersz otrzymuje brązowe tło, białe wypełnienie tekstu itp. Przeczytałem JavaFX TableView colors, Aktualizacja wyglądu wiersza TableView i tła z 2 kolorami w JavaFX?, ale bez skutku =/

Oto źródło:

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;

public class TableViewSample extends Application {

    private TableView<Person> table = new TableView<Person>();
    private final ObservableList<Person> data =
        FXCollections.observableArrayList(
            new Person("Jacob", "Smith", "[email protected]"),
            new Person("Isabella", "Johnson", "[email protected]"),
            new Person("Ethan", "Williams", "[email protected]"),
            new Person("Emma", "Jones", "[email protected]"),
            new Person("Michael", "Brown", "[email protected]")
        );

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

    @Override
    public void start(Stage stage) {
        Scene scene = new Scene(new Group());
        stage.setTitle("Table View Sample");
        stage.setWidth(450);
        stage.setHeight(600);

        final Label label = new Label("Address Book");
        label.setFont(new Font("Arial", 20));

        TableColumn firstNameCol = new TableColumn("First Name");
        firstNameCol.setMinWidth(100);
        firstNameCol.setCellValueFactory(
                new PropertyValueFactory<Person, String>("firstName"));

        TableColumn lastNameCol = new TableColumn("Last Name");
        lastNameCol.setMinWidth(100);
        lastNameCol.setCellValueFactory(
                new PropertyValueFactory<Person, String>("lastName"));

        TableColumn emailCol = new TableColumn("Email");
        emailCol.setMinWidth(200);
        emailCol.setCellValueFactory(
                new PropertyValueFactory<Person, String>("email"));

        table.setItems(data);
        table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);

        final Button btnHighlight = new Button("Highlight selected row");
        btnHighlight.setMaxWidth(Double.MAX_VALUE);
        btnHighlight.setPrefHeight(30);

        btnHighlight.setOnAction(new EventHandler<ActionEvent>(){
            public void handle(ActionEvent e){
                // this is where the CSS should be applied
            }
        });

        final VBox vbox = new VBox();
        vbox.setSpacing(5);
        vbox.setPadding(new Insets(10, 0, 0, 10));
        vbox.getChildren().addAll(label, table, btnHighlight);

        ((Group) scene.getRoot()).getChildren().addAll(vbox);

        stage.setScene(scene);
        stage.show();
    }

    public static class Person {

        private final SimpleStringProperty firstName;
        private final SimpleStringProperty lastName;
        private final SimpleStringProperty email;

        private Person(String fName, String lName, String email) {
            this.firstName = new SimpleStringProperty(fName);
            this.lastName = new SimpleStringProperty(lName);
            this.email = new SimpleStringProperty(email);
        }

        public String getFirstName() {
            return firstName.get();
        }

        public void setFirstName(String fName) {
            firstName.set(fName);
        }

        public String getLastName() {
            return lastName.get();
        }

        public void setLastName(String fName) {
            lastName.set(fName);
        }

        public String getEmail() {
            return email.get();
        }

        public void setEmail(String fName) {
            email.set(fName);
        }
    }
} 

I aplikacji .css , od którego przycisk "Podświetl wybrany wiersz" stosuje klasę highlightedRow do wybranego wiersza tabeli:

.highlightedRow {
    -fx-background-color: brown;
    -fx-background-insets: 0, 1, 2;
    -fx-background: -fx-accent;
    -fx-text-fill: -fx-selection-bar-text;
}

Edit:

Po kilku godzinach prób, najlepsze co mogłem wymyślić to to używając poniższego kodu:

firstNameCol.setCellFactory(new Callback<TableColumn<Person, String>, TableCell<Person, String>>() {
    @Override
    public TableCell<Person, String> call(TableColumn<Person, String> personStringTableColumn) {
        return new TableCell<Person, String>() {
            @Override
            protected void updateItem(String name, boolean empty) {
                super.updateItem(name, empty);
                if (!empty) {
                    if (name.toLowerCase().startsWith("e") || name.toLowerCase().startsWith("i")) {
                        getStyleClass().add("highlightedRow");
                    }
                    setText(name);
                } else {
                    setText("empty");  // for debugging purposes
                }
            }
        };
    }
});

Nie bardzo rozumiem, dlaczego nie mogę tego zrobić od wewnątrz setOnAction metody btnHighlight? Próbowałem też odświeżyć stół potem ( opisane tutaj ), ale wydawało się, że nie działa. Ponadto moje "rozwiązanie" działa tylko dla kolumny firstNameCol, więc czy trzeba ustawić nową fabrykę komórek dla każdej kolumny, aby zastosować określony styl, czy jest mądrzejsze rozwiązanie?

Author: Community, 2013-12-03

6 answers

Jeśli nie chcesz możliwości ponownego użycia rozwiązania, które napisałem powyżej, jest to naprawdę to samo, ale używając anonimowej klasy wewnętrznej dla fabryki wiersza zamiast samodzielnej klasy. Być może kod jest łatwiejszy do naśladowania, ponieważ wszystko jest w jednym miejscu. To rodzaj hybrydy między rozwiązaniem Jonathana a moim, ale automatycznie zaktualizuje podświetlenia bez wymuszania ich sortowaniem.

Użyłem listy liczb całkowitych, więc obsługuje wielokrotne zaznaczanie, ale jeśli nie potrzebujesz, możesz oczywiście zamiast tego użyj IntegerProperty.

Oto fabryka wierszy:

    final ObservableList<Integer> highlightRows = FXCollections.observableArrayList();

    table.setRowFactory(new Callback<TableView<Person>, TableRow<Person>>() {
        @Override
        public TableRow<Person> call(TableView<Person> tableView) {
            final TableRow<Person> row = new TableRow<Person>() {
                @Override
                protected void updateItem(Person person, boolean empty){
                    super.updateItem(person, empty);
                    if (highlightRows.contains(getIndex())) {
                        if (! getStyleClass().contains("highlightedRow")) {
                            getStyleClass().add("highlightedRow");
                        }
                    } else {
                        getStyleClass().removeAll(Collections.singleton("highlightedRow"));
                    }
                }
            };
            highlightRows.addListener(new ListChangeListener<Integer>() {
                @Override
                public void onChanged(Change<? extends Integer> change) {
                    if (highlightRows.contains(row.getIndex())) {
                        if (! row.getStyleClass().contains("highlightedRow")) {
                            row.getStyleClass().add("highlightedRow");
                        }
                    } else {
                        row.getStyleClass().removeAll(Collections.singleton("highlightedRow"));
                    }
                }
            });
            return row;
        }
    });

A oto jak mogą wyglądać niektóre przyciski:

    final Button btnHighlight = new Button("Highlight");
    btnHighlight.disableProperty().bind(Bindings.isEmpty(table.getSelectionModel().getSelectedIndices()));
    btnHighlight.setOnAction(new EventHandler<ActionEvent>() {
        @Override
        public void handle(ActionEvent event) {
            highlightRows.setAll(table.getSelectionModel().getSelectedIndices());
        }
    });

    final Button btnClearHighlight = new Button("Clear Highlights");
    btnClearHighlight.disableProperty().bind(Bindings.isEmpty(highlightRows));
    btnClearHighlight.setOnAction(new EventHandler<ActionEvent>() {
        @Override
        public void handle(ActionEvent event) {
            highlightRows.clear();
        }
    });
 16
Author: James_D,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-09 16:08:27

Co powiesz na utworzenie fabryki wierszy, która wyświetla obserwowalną listę indeksów wierszy tabeli, które mają być podświetlone? W ten sposób można po prostu zaktualizować listę indeksami, które należy wyróżnić: na przykład wywołując getSelectedIndices () w modelu selekcji i przekazując ją do setAll listy(...) metoda.

To może wyglądać tak:

import java.util.Collections;

import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.util.Callback;


public class StyleChangingRowFactory<T> implements
        Callback<TableView<T>, TableRow<T>> {

    private final String styleClass ;
    private final ObservableList<Integer> styledRowIndices ;
    private final Callback<TableView<T>, TableRow<T>> baseFactory ;

    public StyleChangingRowFactory(String styleClass, Callback<TableView<T>, TableRow<T>> baseFactory) {
        this.styleClass = styleClass ;
        this.baseFactory = baseFactory ;
        this.styledRowIndices = FXCollections.observableArrayList();
    }

    public StyleChangingRowFactory(String styleClass) {
        this(styleClass, null);
    }

    @Override
    public TableRow<T> call(TableView<T> tableView) {

        final TableRow<T> row ;
        if (baseFactory == null) {
            row = new TableRow<>();
        } else {
            row = baseFactory.call(tableView);
        }

        row.indexProperty().addListener(new ChangeListener<Number>() {
            @Override
            public void changed(ObservableValue<? extends Number> obs,
                    Number oldValue, Number newValue) {
                updateStyleClass(row);
            }
        });

        styledRowIndices.addListener(new ListChangeListener<Integer>() {

            @Override
            public void onChanged(Change<? extends Integer> change) {
                updateStyleClass(row);
            }
        });

        return row;
    }

    public ObservableList<Integer> getStyledRowIndices() {
        return styledRowIndices ;
    }

    private void updateStyleClass(TableRow<T> row) {
        final ObservableList<String> rowStyleClasses = row.getStyleClass();
        if (styledRowIndices.contains(row.getIndex()) ) {
            if (! rowStyleClasses.contains(styleClass)) {
                rowStyleClasses.add(styleClass);
            }
        } else {
            // remove all occurrences of styleClass:
            rowStyleClasses.removeAll(Collections.singleton(styleClass));
        }
    }

}

Teraz możesz zrobić

final StyleChangingRowFactory<Person> rowFactory = new StyleChangingRowFactory<>("highlightedRow");
table.setRowFactory(rowFactory);

I w przycisku obsługi akcji do

    rowFactory.getStyledRowIndices().setAll(table.getSelectionModel().getSelectedIndices());

Ponieważ StyleChangingRowFactory owija inną fabrykę wierszy, nadal możesz jej używać, jeśli masz już niestandardową implementację fabryki wierszy, której chcesz użyć. Na przykład:

final StyleChangingRowFactory<Person> rowFactory = new StyleChangingRowFactory<Person>(
        "highlightedRow",
        new Callback<TableView<Person>, TableRow<Person>>() {

            @Override
            public TableRow<Person> call(TableView<Person> tableView) {
                final TableRow<Person> row = new TableRow<Person>();
                ContextMenu menu = new ContextMenu();
                MenuItem removeMenuItem = new MenuItem("Remove");
                removeMenuItem.setOnAction(new EventHandler<ActionEvent>() {
                    @Override
                    public void handle(ActionEvent event) {
                        table.getItems().remove(row.getItem());
                    }
                });
                menu.getItems().add(removeMenuItem);
                row.contextMenuProperty().bind(
                        Bindings.when(row.emptyProperty())
                                .then((ContextMenu) null)
                                .otherwise(menu));
                return row;
            }

        });
table.setRowFactory(rowFactory);

Tutaj {[16] } jest kompletny przykład kodu.

 15
Author: James_D,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-06 14:50:12

Oto brzydkie rozwiązanie hack. Po pierwsze, zdefiniuj pole int o nazwie highlightedRow. Następnie Ustaw fabryczny wiersz w widoku TableView:

table.setRowFactory(new Callback<TableView<Person>, TableRow<Person>>() {
    @Override public TableRow<Person> call(TableView<Person> param) {
        return new TableRow<Person>() {
            @Override protected void updateItem(Person item, boolean empty) {
                super.updateItem(item, empty);

                if (getIndex() == highlightedRow) {
                    getStyleClass().add("highlightedRow");
                } else {
                    getStyleClass().remove("highlightedRow");
                }
            }
        };
    }
});

Następnie dodaj poniższy kod w przycisku akcji (i tu wkracza brzydki hack):

btnHighlight.setOnAction(new EventHandler<ActionEvent>(){
    public void handle(ActionEvent e){
        // set the highlightedRow integer to the selection index
        highlightedRow = table.getSelectionModel().getSelectedIndex();

        // force a tableview refresh - HACK
        List<Person> items = new ArrayList<>(table.getItems());
        table.getItems().setAll(items);
    }
});

Gdy to zrobisz, otrzymasz brązowe podświetlenie na wybranym wierszu. Możesz oczywiście z łatwością obsługiwać wiele brązowych podświetleń, zastępując int listą itns.

 7
Author: Jonathan Giles,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-05 20:19:20

Najlepszy sposób na to:

W moim CSS

.table-row-cell:feederChecked{
    -fx-background-color: #06FF00;
}

In my table initialization with a SimpleBooleanProperty of a Object content in my ObservableList:

// The pseudo classes feederChecked that were defined in the css file.
PseudoClass feederChecked = PseudoClass.getPseudoClass("feederChecked");
// Set a rowFactory for the table view.
tableView.setRowFactory(tableView -> {
    TableRow<Feeder> row = new TableRow<>();
    ChangeListener<Boolean> changeListener = (obs, oldFeeder, newFeeder) -> {
        row.pseudoClassStateChanged(feederChecked, newFeeder);
    };
    row.itemProperty().addListener((obs, previousFeeder, currentFeeder) -> {
        if (previousFeeder != null) {
            previousFeeder.feederCheckedProperty().removeListener(changeListener);
        }
        if (currentFeeder != null) {
            currentFeeder.feederCheckedProperty().addListener(changeListener);
            row.pseudoClassStateChanged(feederChecked, currentFeeder.getFeederChecked());
        } else {
            row.pseudoClassStateChanged(feederChecked, false);
        }
    });
    return row;
});

Kod adaptujący się z Ten kompletny exemple

 2
Author: negstek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-26 15:14:37

Może znalazłem coś, co działa:

Po dodaniu tego kodu, po naciśnięciu przycisku podświetlony wiersz zmienia kolor, Po wybraniu innego wiersza kolor zmienia się z powrotem na domyślny, po ponownym naciśnięciu przycisku zmienia kolor nowego wiersza na brązowy.

final String css = getClass().getResource("style.css").toExternalForm();
final Scene scene = new Scene(new Group());


btnHighlight.setOnAction(new EventHandler<ActionEvent>() {
    @Override
     public void handle(ActionEvent e) {
         scene.getStylesheets().add(css);
     }
});
table.getSelectionModel().selectedIndexProperty()
            .addListener(new ChangeListener<Number>() {
    @Override
     public void changed(ObservableValue<? extends Number> ov, Number t, Number t1) {
         scene.getStylesheets().remove(css);
     }
});

Css:

.table-row-cell:selected
{
     -fx-background-color: brown;
     -fx-text-inner-color: white;
}

Jedynym problemem z tym rozwiązaniem jest to, że jeśli naciśniesz przycisk dwa razy z rzędu, następny wybrany wiersz będzie już brązowy. Będziesz musiał użyć oddzielnego pliku css dla w przeciwnym razie przy uruchomieniu aplikacji nie będą stosowane reguły css, dopóki nie naciśniesz przycisku.

 0
Author: WonderWorld,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-06 19:08:48

Stwierdziłem, że najlepszym rozwiązaniem będzie słuchanie wiersza.ItemProperty () zmienia się, ponieważ podczas sortowania na przykład wiersze zmieniają indeksy, więc wiersze są powiadamiane automatycznie.

 0
Author: michael laudrup,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-19 17:16:25