ObservableList 中的粗体文本会破坏 ChangeListener

Bold text in ObservableList breaks ChangeListener

我想要我的 ObservableList 中的 header 是粗体。我把它加粗了,但是当它被选中时它会触发异常块,因为它不是字符串。我可以将其他项目设为文本,但 ChangeListener 需要一个字符串。我只想让异常消失。

有没有办法让它不是 select-able OR 粗体和字符串类型?

   ListView<String> list = new ListView<>();
   ObservableList items = FXCollections.observableArrayList();
   Task curr = tasklist.head;

   items.add(TextBuilder.create().text("Done Tasks").style("-fx-font-weight:bold;").build());
   items.add(curr.name + " - " + curr.description);
   //items.add(new Text(curr.name + " - " + curr.description));

   try {
        list.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<String>() {
            public void changed(ObservableValue<? extends String> ov,
                                final String oldvalue, final String newvalue) {
                if(newvalue != "Done Tasks")
                    selectedValue[0] = newvalue.split(" - ")[0];
                else
                    selectedValue[0] = "done";
            }
        });
    } catch(Exception e)
    {
        actiontarget.setFill(Color.FIREBRICK);
        actiontarget.setText("You must select a task.");
    }

快速而简单的回答

要直接回答您的愿望 "I can make the other items Text but then the ChangeListener wants a string. I just want the exception to go away.",您可以将 ChangeListener 的类型更改为接受 Object 而不是 String:

new ChangeListener<Object>() {
    @Override
    public void changed(ObservableValue<? extends Object> observable, Object oldValue, Object newValue) {
        if(!(newvalue instanceof Text)) {
            String string = ((String) newvalue).split(" - ")[0];
            System.out.println(string);
        }
    }
}

但是,我不推荐上述方法,因为您会失去一些 Java 环境提供的先天类型安全优势。

编码建议

基于您的代码片段的一些建议。

  1. 使用 cell factories 从 ListView 的基础项模型数据生成 ListView 单元格节点。
  2. 不要将Text等节点直接放入ListView的项目列表中。 ListView 项目列表用于表示不是节点的模型 objects。我知道 ListView javadoc 没有明确说明这一点,但是,尽管如此,这仍然是我的最佳建议。每当您认为某个节点适合 ListView 时,通常更好的选择是使用单元工厂。
  3. 当您将 items 放入 ListView 时,它们应该与 ListView 的项目类型匹配(ListView 的 javadoc 确实说明了这一点)。
  4. 对于像你这里这样的 non-trivial 用例,使用更复杂的数据结构来表示列表中的项目,这样你就可以编码语义数据,比如一个项目是否是一个header 直接写入项目而不是硬编码字符串,例如 "Done Tasks".
  5. 不要使用 Builder 类,例如 TextBuilder,它们已被弃用,将从未来的 JavaFX 运行时分发中删除。
  6. 使用外部样式表并通过设置和取消设置样式来操纵样式 类 而不是在代码中内联样式定义。

合并建议示例

根据建议,您可以将您的问题编码为类似下面的应用程序(注意它仍然没有将样式信息提取到外部样式表,我将把那部分留作练习):

import javafx.application.Application;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.stage.Stage;

public class ListDisplay extends Application {
    @Override
    public void start(Stage stage) {
        ListView<TaskItem> listView = new ListView<>(
                FXCollections.observableArrayList(
                        new TaskItem("Todo Tasks", null, true, true),
                        new TaskItem("Senility", "The Autumn Years", false, false),
                        new TaskItem("Death", "But I didn't eat the salmon mousse", false, false),
                        new TaskItem("Done Tasks", null, true, true),
                        new TaskItem("Birth", "The Miracle of Life", true, false),
                        new TaskItem("School", "Growth and Learning", true, false),
                        new TaskItem("Middle Age", "Stagnation", true, false),
                        new TaskItem("Live Organ Transplants", "The Machine that goes 'Ping'", true, false)
                )
        );

        listView.setCellFactory(param -> new ListCell<TaskItem>() {
            @Override
            protected void updateItem(TaskItem item, boolean empty) {
                super.updateItem(item, empty);

                if (!empty && item != null) {
                    if (item.isHeader()) {
                        setText(item.getName());
                        setStyle("-fx-font-weight: bold;");
                    } else {
                        setText(item.getName() + " - " + item.getDescription());
                        setStyle(null);
                    }
                } else {
                    setText(null);
                }
            }
        });

        listView.setPrefHeight(200);

        listView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
            if (!newValue.isHeader()) {
                System.out.println("Selected: " + newValue.getName());
            }
        });

        stage.setTitle("Monty Python's Meaning of Life");
        stage.setScene(new Scene(listView));
        stage.show();
    }

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

    private static class TaskItem {
        private final String name;
        private final String description;
        private final SimpleBooleanProperty completed;
        private final boolean header;

        public TaskItem(String name, String description, boolean completed, boolean header) {
            this.name = name;
            this.description = description;
            this.completed = new SimpleBooleanProperty(completed);
            this.header = header;
        }

        public boolean isHeader() {
            return header;
        }

        public boolean isCompleted() {
            return completed.get();
        }

        public SimpleBooleanProperty completedProperty() {
            return completed;
        }

        public String getName() {
            return name;
        }

        public String getDescription() {
            return description;
        }
    }
}