本文介绍了如何在JavaFX8 TableView中根据是否选择了TableRow的背景颜色和/或数据模型中的值来设置它们?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

一个相对的Java新手问题。

A relative Java newbie question.

我正在尝试设置 TableRow 的背景颜色基于是否选中它和/或数据模型中的布尔值是否为真。

I'm trying set a TableRow's background colour based on whether or not it's selected and/or whether or not a boolean value in the data model is true.

我找到了各种方法,但不是两者都在一起相同的 setRowFactory

I've found ways of doing each but not both together in the same setRowFactory.

我最想得到的是这个(尽管只有可怕的颜色,仅用于示例目的) !):

What I would like to end up with is this (albeit with horrible colours for example purposes only!):

我将如何进行实现这个目标?

How would I go about achieving that?

这是我发现根据选择改变行颜色的原因。它改编自用户James_D的答案 。

This is what I found wrt changing row colour based on selection. It's adapted from user James_D's answer here https://community.oracle.com/thread/3528543.

final ObservableSet<Integer> selectedRowIndexes = FXCollections.observableSet();

table.getSelectionModel().getSelectedCells().addListener((Change<? extends TablePosition> change) -> {  
    selectedRowIndexes.clear();
    selectedRowIndexes.add( (table.getSelectionModel().getSelectedCells().get(0)).getRow() );
});

table.setRowFactory(tv -> {
    TableRow<TestModel> row = new TableRow<>();
    BooleanBinding selected = Bindings.createBooleanBinding(() ->  
        selectedRowIndexes.contains(new Integer(row.getIndex())), row.indexProperty(), selectedRowIndexes);
    row.styleProperty().bind(Bindings.when(selected)
        .then("-fx-background-color:  green;")
        .otherwise(""));
    return row;
});

这就是我发现wrt根据单元格值改变行颜色。它改编自用户kleopatra的答案。

And this is what I found wrt changing row colour based on a cell value. It's adapted from user kleopatra's answer here TreeTableView : setting a row not editable.

table.setRowFactory(tv -> {
    TableRow<TestModel> row = new TableRow<TestModel>() {
        @Override
        public void updateItem(TestModel testmodel, boolean empty) {
            super.updateItem(testmodel, empty);
            boolean locked = false;
            if ( getItem() != null ) {
                locked = getItem().lockedProperty().get();
                setEditable( ! locked);
            }
            if (!isEmpty() && locked ) {
                setStyle("-fx-background-color: red;");
            }else{
                setStyle(null);
            }
        }
    };
    return row;
});

然而,我最终选择了两排工厂而无法弄清楚如何将它们合并为一个。

However, I've ended up with two row factories and haven't been able to figure out how merge them into one.

如果有帮助,这是我一直在玩的MVCE。它有两排工厂。我没有包括我的(很多!)尝试合并它们,因为没有工作。

If it helps, here is the MVCE I've been playing with. It has the two row factories. I haven't included my (many!) attempts to merge them as none worked.

我正在使用JavaFX8(JDK1.8.0_181),NetBeans 8.2和Scene Builder 8.3。

I'm using JavaFX8 (JDK1.8.0_181), NetBeans 8.2 and Scene Builder 8.3.

package test31;

import java.util.Arrays;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.collections.ObservableSet;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.converter.BooleanStringConverter;

public class Test31 extends Application {

    private Parent createContent() {

        TableView<TestModel> table = new TableView<>();

        ObservableList<TestModel> olTestModel = FXCollections.observableArrayList(testmodel -> new Observable[] {});
        olTestModel.add(new TestModel("1", true));
        olTestModel.add(new TestModel("2", false));
        olTestModel.add(new TestModel("3", false));
        olTestModel.add(new TestModel("4", true));
        olTestModel.add(new TestModel("5", false));

        TableColumn<TestModel, String> colText = new TableColumn<>("textfield");
        colText.setCellValueFactory(cb -> cb.getValue().textFieldProperty());
        colText.setCellFactory(TextFieldTableCell.forTableColumn());

        TableColumn<TestModel, Boolean> colBoolean = new TableColumn<>("locked");
        colBoolean.setCellValueFactory(cb -> cb.getValue().lockedProperty());
        colBoolean.setCellFactory(TextFieldTableCell.forTableColumn(new BooleanStringConverter()));

        table.getSelectionModel().setCellSelectionEnabled(true);
        table.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
        table.setEditable(true);
        table.getColumns().addAll(Arrays.asList(colText, colBoolean));
        table.setItems(olTestModel);

        //****************************************************************************************
        //First row factory:  Set background colour based on whether or not the row is selected
        final ObservableSet<Integer> selectedRowIndexes = FXCollections.observableSet();

        table.getSelectionModel().getSelectedCells().addListener((Change<? extends TablePosition> change) -> {  
            selectedRowIndexes.clear();
            selectedRowIndexes.add( (table.getSelectionModel().getSelectedCells().get(0)).getRow() );
        });

        table.setRowFactory(tv -> {
            TableRow<TestModel> row = new TableRow<>();
            BooleanBinding selected = Bindings.createBooleanBinding(() ->  
                selectedRowIndexes.contains(new Integer(row.getIndex())), row.indexProperty(), selectedRowIndexes);
            row.styleProperty().bind(Bindings.when(selected)
                .then("-fx-background-color:  green;")
                .otherwise(""));
            return row;
        });

        //****************************************************************************************
        //Second row factory:  Set background colour based on the value of a boolean property
        table.setRowFactory(tv -> {
            TableRow<TestModel> row = new TableRow<TestModel>() {
                @Override
                public void updateItem(TestModel testmodel, boolean empty) {
                    super.updateItem(testmodel, empty);
                    boolean locked = false;
                    if ( getItem() != null ) {
                        locked = getItem().lockedProperty().get();
                        setEditable( ! locked);
                    }
                    if (!isEmpty() && locked ) {
                        setStyle("-fx-background-color: red;");
                    }else{
                        setStyle(null);
                    }
                }
            };
            return row;
        });

        BorderPane content = new BorderPane(table);

        return content;

    }

    public class TestModel {

        private StringProperty textField;
        private BooleanProperty locked;

        public TestModel() {
            this("", false);
        }

        public TestModel(
            String textField,
            boolean locked
        ) {
            this.textField = new SimpleStringProperty(textField);
            this.locked = new SimpleBooleanProperty(locked);
        }

        public String getTextField() {
            return textField.get().trim();
        }

        public void setTextField(String textField) {
            this.textField.set(textField);
        }

        public StringProperty textFieldProperty() {
            return textField;
        }

        public boolean getLocked() {
            return locked.get();
        }

        public void setLocked(boolean locked) {
            this.locked.set(locked);
        }

        public BooleanProperty lockedProperty() {
            return locked;
        }

    }

    @Override
    public void start(Stage stage) throws Exception {
        stage.setScene(new Scene(createContent()));
        stage.setTitle("Test");
        stage.setWidth(500);
        stage.show();
    }

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

}


推荐答案

有几种方法可以做到这一点。以下是使用外部CSS和伪类的示例:

There are a couple ways you can do this. Here's an example using external CSS and pseudo-classes:

Main.java

import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class Main extends Application {

  @Override
  public void start(Stage primaryStage) {
    TableView<Item> table = new TableView<>(createDummyData(100));
    table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
    table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);

    table.setRowFactory(t -> new ItemTableRow());

    TableColumn<Item, String> nameCol = new TableColumn<>("Name");
    nameCol.setCellValueFactory(features -> features.getValue().nameProperty());
    table.getColumns().add(nameCol);

    TableColumn<Item, Boolean> validCol = new TableColumn<>("Valid");
    validCol.setCellValueFactory(features -> features.getValue().validProperty());
    table.getColumns().add(validCol);

    primaryStage.setScene(new Scene(new StackPane(table), 800, 600));
    primaryStage.getScene().getStylesheets().add(getClass().getResource("Main.css").toExternalForm());
    primaryStage.setTitle("JavaFX Application");
    primaryStage.show();
  }


  private ObservableList<Item> createDummyData(int count) {
    return IntStream.rangeClosed(1, count)
        .mapToObj(i -> "Item #" + i)
        .map(name -> new Item(name, Math.random() >= 0.5))
        .collect(Collectors.toCollection(FXCollections::observableArrayList));
  }

}

ItemTableRow.java

import javafx.beans.value.ChangeListener;
import javafx.beans.value.WeakChangeListener;
import javafx.css.PseudoClass;
import javafx.scene.control.TableRow;

public class ItemTableRow extends TableRow<Item> {

  private static final PseudoClass VALID = PseudoClass.getPseudoClass("valid");

  private final ChangeListener<Boolean> listener = (obs, oldVal, newVal) -> updateValidPseudoClass(newVal);
  private final WeakChangeListener<Boolean> weakListener = new WeakChangeListener<>(listener);

  public ItemTableRow() {
    getStyleClass().add("item-table-row");
  }

  @Override
  protected void updateItem(Item item, boolean empty) {
    Item oldItem = getItem();
    if (oldItem != null) {
      oldItem.validProperty().removeListener(weakListener);
    }
    super.updateItem(item, empty);
    if (empty || item == null) {
      updateValidPseudoClass(false);
    } else {
      item.validProperty().addListener(weakListener);
      updateValidPseudoClass(item.isValid());
    }
  }

  private void updateValidPseudoClass(boolean active) {
    pseudoClassStateChanged(VALID, active);
  }

}

Item.java

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

public class Item {

  private final StringProperty name = new SimpleStringProperty(this, "name");
  public final void setName(String name) { this.name.set(name); }
  public final String getName() { return name.get(); }
  public final StringProperty nameProperty() { return name; }

  private final BooleanProperty valid = new SimpleBooleanProperty(this, "valid");
  public final void setValid(boolean valid) { this.valid.set(valid); }
  public final boolean isValid() { return valid.get(); }
  public final BooleanProperty validProperty() { return valid; }

  public Item() {}

  public Item(String name, boolean valid) {
    setName(name);
    setValid(valid);
  }

}

Main.css

.item-table-row:selected {
    -fx-background-color: -fx-control-inner-background, green;
}

.item-table-row:valid {
    -fx-background-color: -fx-control-inner-background, yellow;
}

.item-table-row:valid:selected {
    -fx-background-color: -fx-control-inner-background, red;
}






如果您更喜欢使用代码,将 ItemTableRow 更改为此(并从<$ c中删除 getStylesheets()。add(...) $ c> Main ):


If you prefer to only use code, change ItemTableRow to this (and remove getStylesheets().add(...) from Main):

import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
import javafx.scene.control.TableRow;

public class ItemTableRow extends TableRow<Item> {

  private final InvalidationListener listener = observable -> updateStyle();
  private final WeakInvalidationListener weakListener = new WeakInvalidationListener(listener);

  public ItemTableRow() {
    getStyleClass().add("item-table-row");
    selectedProperty().addListener(listener); // could also override updateSelected
  }

  @Override
  protected void updateItem(Item item, boolean empty) {
    Item oldItem = getItem();
    if (oldItem != null) {
      oldItem.validProperty().removeListener(weakListener);
    }
    super.updateItem(item, empty);
    if (item != null) {
      item.validProperty().addListener(weakListener);
    }
    updateStyle();
  }

  private void updateStyle() {
    final Item item = getItem();
    if (item == null || (!isSelected() && !item.isValid())) {
      setStyle(null);
    } else if (isSelected() && item.isValid()) {
      setStyle("-fx-background-color: -fx-control-inner-background, red;");
    } else if (isSelected()) {
      setStyle("-fx-background-color: -fx-control-inner-background, green;");
    } else if (item.isValid()) {
      setStyle("-fx-background-color: -fx-control-inner-background, yellow;");
    } else {
      // I don't think this branch is possible, but not 100% sure
      throw new AssertionError("Shouldn't be here?");
    }
  }

}






-fx-control-inner-background 值在 modena.css (JavaFX 8+的默认样式表)。它为 TableRow 提供了一点颜色填充。


The -fx-control-inner-background value is defined in modena.css (the default stylesheet for JavaFX 8+). It gives the TableRow that little bit of color padding.

这篇关于如何在JavaFX8 TableView中根据是否选择了TableRow的背景颜色和/或数据模型中的值来设置它们?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-19 23:38