javafx 中 table 行的删除线

Strikethough on table row in javafx

我想在用户删除某行时将其更新为删除线。我是 javafx 的新手,一直在寻找但没有运气。

        donationsTable.setRowFactory(tv -> {
            TableRow<Donation> row = new TableRow<Donation>() {
                // to force updateItem called
                @Override
                protected boolean isItemChanged(Donation d,
                        Donation d2) {
                    return true;
                }
                
                @Override
                public void updateItem(Donation d, boolean empty) {
                    super.updateItem(d, empty) ;
                    if (d == null) {
                        setStyle("");
                    } else if (d.getAction().equals(Donation.DELETE_DONATION)) {
                        setStyle("delete-row");
                    } else if (d.getAction().equals(Donation.NEW_DONATION)) {
                        setStyle("-fx-font-weight: bold;");
                    } else {
                        setStyle("");
                    }           
                }
            };
            row.setOnMouseClicked(event -> {
                    deleteDonation.setDisable(false);
            });
            return row;
        });

新捐赠的大胆作品,但我无法让删除线生效。我确实看到它需要在文本上设置,而不是行所以我的 css 是:

.delete-row .text {
    -fx-strikethrough: true;
}

但是,我收到一条警告:警告 CSS 解析 '*{delete-row} 时出错:[1,12] 处应有冒号 我对css只有很基础的了解。这是我在其他答案中看到的,但我不明白为什么它对我不起作用。

非常感谢任何帮助。

根据James_D的建议,我修改了updateItem:


        public void updateItem(Donation d, boolean empty) {
                    super.updateItem(d, empty) ;
                    
                    PseudoClass delete = PseudoClass.getPseudoClass("delete-row");
                    pseudoClassStateChanged(delete, d != null && d.getAction().equals(Donation.DELETE_DONATION));

                    PseudoClass add = PseudoClass.getPseudoClass("add-row");
                    pseudoClassStateChanged(add, d != null && d.getAction().equals(Donation.NEW_DONATION));

                }

css 有

.table-row-cell:delete-row .text {
    -fx-strikethrough: true;
}

.table-row-cell:add-row {
    -fx-font-weight: bold;
}

删除线仍然无效,粗体停止工作。

setStyle 方法将在 Node 上设置内联样式;此样式采用 CSS 规则的形式。这就是你对粗体的处理:

if (d.getAction().equals(Donation.NEW_DONATION)) {
    setStyle("-fx-font-weight: bold;");
}

要将 CSS class 添加到节点的 classes 列表中,获取节点的 CSS classes 列表getStyleClass(),并操纵它。

这里你必须小心一点,因为列表可以包含相同值的多个副本,而且你无法控制 updateItem() 被调用的次数以及调用 [=22= 的次数]s 作为参数。最好的选择是删除 class delete-row 的所有实例,并在正确的条件下添加一个:

@Override
public void updateItem(Donation d, boolean empty) {
    super.updateItem(d, empty) ;

    getStyleClass().removeAll(Collections.singleton("delete-row"));

    if (d == null) {
        setStyle("");
    } else if (d.getAction().equals(Donation.DELETE_DONATION)) {
        setStyle("");
        getStyleClass().add("delete-row");
    } else if (d.getAction().equals(Donation.NEW_DONATION)) {
        setStyle("-fx-font-weight: bold;");
    } else {
        setStyle("");
    }           
}

另一种选择是使用 CSS 伪 class 代替:

@Override
public void updateItem(Donation d, boolean empty) {
    super.updateItem(d, empty) ;

    PseudoClass delete = PseudoClass.getPseudoClass("delete-row");
    pseudoClassStateChanged(delete, d != null && d.getAction().equals(Donation.DELETE_DONATION));

    if (d != null && d.getAction().equals(Donation.NEW_DONATION)) {
        setStyle("-fx-font-weight: bold;");
    } else {
        setStyle("");
    }           
}

.table-row-cell:delete-row .text {
    -fx-strikethrough: true;
}

在这种情况下,为了保持一致性,我可能也会将 NEW_DONATION 样式重构为伪class。

这是一个使用伪classes 的完整示例。请注意,我将 CSS 更改为粗体(据我了解,使用 font-weight 取决于系统为 currently-selected 字体提供粗体字体;使用通用的东西(sans-serif ) 使用 -fx-font 规则更可靠。)

Donation.java

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

public class Donation {

    public enum Action { NEW_DONATION, DELETE_DONATION, NO_ACTION }
    
    private final StringProperty name = new SimpleStringProperty() ;
    private final ObjectProperty<Action> action = new SimpleObjectProperty<>() ;
    
    public Donation(String name, Action action) {
        setName(name);
        setAction(action);
    }

    public final StringProperty nameProperty() {
        return this.name;
    }
    

    public final String getName() {
        return this.nameProperty().get();
    }
    

    public final void setName(final String name) {
        this.nameProperty().set(name);
    }
    

    public final ObjectProperty<Action> actionProperty() {
        return this.action;
    }
    

    public final Action getAction() {
        return this.actionProperty().get();
    }
    

    public final void setAction(final Action action) {
        this.actionProperty().set(action);
    }
    
    
    
}

App.java

import java.util.Random;
import java.util.function.Function;

import javafx.application.Application;
import javafx.beans.property.Property;
import javafx.css.PseudoClass;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;


public class App extends Application {

    @Override
    public void start(Stage stage) {
        TableView<Donation> table = new TableView<>();
        
        table.setRowFactory(tv -> {
            TableRow<Donation> row = new TableRow<>() {
                @Override
                protected void updateItem(Donation donation, boolean empty) {
                    super.updateItem(donation, empty);
                    PseudoClass add = PseudoClass.getPseudoClass("add-row");
                    pseudoClassStateChanged(add, 
                        donation != null && donation.getAction() == Donation.Action.NEW_DONATION);

                    PseudoClass delete = PseudoClass.getPseudoClass("delete-row");
                    pseudoClassStateChanged(delete, 
                        donation != null && donation.getAction() == Donation.Action.DELETE_DONATION);
                }
            };
            return row ;
        });
        
        Random rng = new Random();
        for (int i = 1 ; i <= 40 ; i++) {
            table.getItems().add(new Donation("Donation "+i, Donation.Action.values()[rng.nextInt(3)]));
        }
        
        table.getColumns().add(column("Donation", Donation::nameProperty));
        table.getColumns().add(column("Action", Donation::actionProperty));
        
        BorderPane root = new BorderPane(table);
        Scene scene = new Scene(root);
        scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
        stage.setScene(scene);
        stage.show();
    }
    
    private static <S,T> TableColumn<S,T> column(String name, Function<S, Property<T>> prop) {
        TableColumn<S,T> col = new TableColumn<>(name);
        col.setCellValueFactory(data -> prop.apply(data.getValue()));
        return col ;
    }

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

}

style.css:

.table-row-cell:delete-row .text {
    -fx-strikethrough: true;
}

.table-row-cell:add-row {
    /* -fx-font-weight: bold; */
    -fx-font: bold 1em sans-serif ;
}


更新:

如果其中一列未观察到 属性 确定 table 行的样式(例如,在上面的示例中,“action”列不存在),您需要安排行来观察 属性 本身。这有点棘手,因为该行被不同的 table 项重复使用,因此您需要在发生这种情况时从正确的 属性 添加和删除侦听器。这看起来像:

    table.setRowFactory(tv -> {
        TableRow<Donation> row = new TableRow<>() {

            // Listener that updates style when the actionProperty() changes
            private final ChangeListener<Donation.Action> listener = 
                (obs, oldAction, newAction) -> updateStyle();
            
            {
                // make sure listener above is registered 
                // with the correct actionProperty()
                itemProperty().addListener((obs, oldDonation, newDonation) -> {
                    if (oldDonation != null) {
                        oldDonation.actionProperty().removeListener(listener);
                    }
                    if (newDonation != null) {
                        newDonation.actionProperty().addListener(listener);
                    }
                });
            }
            
            @Override
            protected void updateItem(Donation donation, boolean empty) {
                super.updateItem(donation, empty);
                updateStyle();
            }

            private void updateStyle() {
                Donation donation = getItem();
                PseudoClass add = PseudoClass.getPseudoClass("add-row");
                pseudoClassStateChanged(add, donation != null && donation.getAction() == Donation.Action.NEW_DONATION);
                PseudoClass delete = PseudoClass.getPseudoClass("delete-row");
                pseudoClassStateChanged(delete, donation != null && donation.getAction() == Donation.Action.DELETE_DONATION);
            }
        };
        return row ;
    });