多个对象类型的JavaFX TreeView? (和更多)

我目前有以下对象数据结构:

项目

  • 字符串名称
  • ArrayList信息

字符

  • 字符串名称
  • 物品的集合

帐户

  • 字符串名称
  • 字符集 (最多8个)

我想创建一个如下所示的TreeView:

Root(invisible) ======Jake(Account) ============JakesChar(Character) ==================Amazing Sword(Item) ==================Broken Bow(Item) ==================Junk Metal(Item) ======Mark(Account) ============myChar(Character) ==================Godly Axe(Item) ======FreshAcc(Account) ======MarksAltAcc(Account) ============IllLvlThisIPromise(Character) ======Jeffrey(Account) ============Jeff(Character) ==================Super Gun(Item) ==================Better Super Gun(Item) ==================Super Gun Scope(Item) 

我把所有这些名字都搞定了,显然真正的实现会复杂得多。 如何才能做到这一点? TreeItem要求每个TreeItem与其父类型相同。

我唯一的解决方案是执行以下操作:

 public class ObjectPointer { Object pointer; String name; } 

我的TreeView将是ObjectPointer类型,并且在每行上我将ObjectPointerAccountCharacter,Item 。 这是AWFUL,但我认为它会起作用。

子问题:

  • 如何让TreeItem检测setOnMouseHover事件?

  • 如何让TreeItem不使用其类型的toString方法,而是使用自定义方式显示所需的String属性?

  • 如何让TreeItem在GUI中显示彩色文本而不是纯文本?

谢谢!

如果你看一下你的模型并一般地思考,所有类都有一定程度的相似性,你可以把它们分解成一个超类:

 package model; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; public abstract class GameObject> { public GameObject(String name) { setName(name); } private final StringProperty name = new SimpleStringProperty(); 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); } private final ObservableList items = FXCollections.observableArrayList(); public ObservableList getItems() { return items ; } public abstract void createAndAddChild(String name); } 

这里的类型参数T表示“子”对象的类型。 所以你的Account类(其子类型是GameCharacter – 不要将类命名为java.lang任何类,btw …)看起来像

 package model; public class Account extends GameObject { public Account(String name) { super(name); } @Override public void createAndAddChild(String name) { getItems().add(new GameCharacter(name)); } } 

同样一直沿着层次结构。 我定义了一个Information类(即使它只有一个名字)来使一切都适合结构,所以:

 package model; public class Item extends GameObject { public Item(String name) { super(name); } @Override public void createAndAddChild(String name) { getItems().add(new Information(name)); } } 

并且,由于Information没有子节点,因此其子列表只是一个空列表:

 package model; import javafx.collections.FXCollections; import javafx.collections.ObservableList; public class Information extends GameObject> { public Information(String name) { super(name); } @Override public ObservableList> getItems() { return FXCollections.emptyObservableList(); } @Override public void createAndAddChild(String name) { throw new IllegalStateException("Information has no child items"); } } 

现在,树中的每个项目都是GameObject ,因此您基本上可以构建TreeView> 。 棘手的部分是您的树项需要反映模型中已经构建的结构。 由于您在那里有可观察的列表,因此您可以使用列表中的侦听器执行此操作。

您可以使用树上的单元工厂来自定义显示TreeItem的单元格的外观。 如果你想为每种类型的项目设置不同的外观,我建议在外部CSS类中定义样式,并在与项目类型对应的单元格上设置CSS PseudoClass 。 如果你使用一些命名约定(我有伪类名称是类名的小写版本),它可以很光滑。 这是一个相当简单的例子:

 package ui; import static java.util.stream.Collectors.toList; import java.util.Arrays; import java.util.List; import javafx.collections.ListChangeListener.Change; import javafx.collections.ObservableList; import javafx.css.PseudoClass; import javafx.scene.control.TreeCell; import javafx.scene.control.TreeItem; import javafx.scene.control.TreeView; import model.Account; import model.GameCharacter; import model.GameObject; import model.Information; import model.Item; public class Tree { private final TreeView> treeView ; private final List>> itemTypes = Arrays.asList( Account.class, GameCharacter.class, Item.class, Information.class ); public Tree(ObservableList accounts) { treeView = new TreeView<>(); GameObject root = new GameObject("") { @Override public ObservableList getItems() { return accounts ; } @Override public void createAndAddChild(String name) { getItems().add(new Account(name)); } }; TreeItem> treeRoot = createItem(root); treeView.setRoot(treeRoot); treeView.setShowRoot(false); treeView.setCellFactory(tv -> { TreeCell> cell = new TreeCell>() { @Override protected void updateItem(GameObject item, boolean empty) { super.updateItem(item, empty); textProperty().unbind(); if (empty) { setText(null); itemTypes.stream().map(Tree.this::asPseudoClass) .forEach(pc -> pseudoClassStateChanged(pc, false)); } else { textProperty().bind(item.nameProperty()); PseudoClass itemPC = asPseudoClass(item.getClass()); itemTypes.stream().map(Tree.this::asPseudoClass) .forEach(pc -> pseudoClassStateChanged(pc, itemPC.equals(pc))); } } }; cell.hoverProperty().addListener((obs, wasHovered, isNowHovered) -> { if (isNowHovered && (! cell.isEmpty())) { System.out.println("Mouse hover on "+cell.getItem().getName()); } }); return cell ; } } public TreeView> getTreeView() { return treeView ; } private TreeItem> createItem(GameObject object) { // create tree item with children from game object's list: TreeItem> item = new TreeItem<>(object); item.setExpanded(true); item.getChildren().addAll(object.getItems().stream().map(this::createItem).collect(toList())); // update tree item's children list if game object's list changes: object.getItems().addListener((Change> c) -> { while (c.next()) { if (c.wasAdded()) { item.getChildren().addAll(c.getAddedSubList().stream().map(this::createItem).collect(toList())); } if (c.wasRemoved()) { item.getChildren().removeIf(treeItem -> c.getRemoved().contains(treeItem.getValue())); } } }); return item ; } private PseudoClass asPseudoClass(Class clz) { return PseudoClass.getPseudoClass(clz.getSimpleName().toLowerCase()); } } 

快速测试,但有效,但请注意您可能需要测试更多function:

 package application; import javafx.application.Application; import javafx.beans.binding.Bindings; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.TextField; import javafx.scene.control.TreeItem; import javafx.scene.control.TreeView; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.stage.Stage; import model.Account; import model.GameCharacter; import model.GameObject; import model.Information; import model.Item; import ui.Tree; public class Main extends Application { @Override public void start(Stage primaryStage) throws Exception { Tree tree = new Tree(createAccounts()); TreeView> treeView = tree.getTreeView(); TextField addField = new TextField(); Button addButton = new Button("Add"); EventHandler addHandler = e -> { TreeItem> selected = treeView .getSelectionModel() .getSelectedItem(); if (selected != null) { selected.getValue().createAndAddChild(addField.getText()); addField.clear(); } }; addField.setOnAction(addHandler); addButton.setOnAction(addHandler); addButton.disableProperty().bind(Bindings.createBooleanBinding(() -> { TreeItem> selected = treeView.getSelectionModel().getSelectedItem() ; return selected == null || selected.getValue() instanceof Information ; }, treeView.getSelectionModel().selectedItemProperty())); Button deleteButton = new Button("Delete"); deleteButton.setOnAction(e -> { TreeItem> selected = treeView.getSelectionModel().getSelectedItem() ; TreeItem> parent = selected.getParent() ; parent.getValue().getItems().remove(selected.getValue()); }); deleteButton.disableProperty().bind(treeView.getSelectionModel().selectedItemProperty().isNull()); HBox controls = new HBox(5, addField, addButton, deleteButton); controls.setPadding(new Insets(5)); controls.setAlignment(Pos.CENTER); BorderPane root = new BorderPane(treeView); root.setBottom(controls); Scene scene = new Scene(root, 600, 600); scene.getStylesheets().add(getClass().getResource("/ui/style/style.css").toExternalForm()); primaryStage.setScene(scene); primaryStage.show(); } public static void main(String[] args) { launch(args); } private ObservableList createAccounts() { Account jake = new Account("Jake"); Account mark = new Account("Mark"); Account freshAcc = new Account("Fresh Account"); Account marksAltAcc = new Account("Mark's alternative account"); Account jeffrey = new Account("Jeffrey"); GameCharacter jakesChar = new GameCharacter("Jakes character"); Item amazingSword = new Item("Amazing Sword"); Item brokenBow = new Item("Broken Bow"); Item junkMetal = new Item("Junk Metal"); GameCharacter myChar = new GameCharacter("Me"); Item godlyAxe = new Item("Godly Axe"); GameCharacter level = new GameCharacter("I'll level this I promise"); GameCharacter jeff = new GameCharacter("Jeff"); Item superGun = new Item("Super Gun"); Item superGunScope = new Item("Super Gun Scope"); jake.getItems().add(jakesChar); mark.getItems().add(myChar); marksAltAcc.getItems().add(level); jeffrey.getItems().add(jeff); jakesChar.getItems().addAll(amazingSword, brokenBow, junkMetal); myChar.getItems().add(godlyAxe); jeff.getItems().addAll(superGun, superGunScope); return FXCollections.observableArrayList(jake, mark, freshAcc, marksAltAcc, jeffrey); } } 

以CSS为例:

 .tree-cell, .tree-cell:hover:empty { -fx-background-color: -fx-background ; -fx-background: -fx-control-inner-background ; } .tree-cell:hover { -fx-background-color: crimson, -fx-background ; -fx-background-insets: 0, 1; } .tree-cell:account { -fx-background: lightsalmon ; } .tree-cell:gamecharacter { -fx-background: bisque ; } .tree-cell:item { -fx-background: antiquewhite ; } .tree-cell:selected { -fx-background: crimson ; }