多对象类型的JavaFXTreeView?(及更多)
我目前拥有以下对象数据结构: 项目多对象类型的JavaFXTreeView?(及更多),java,user-interface,javafx,treeview,Java,User Interface,Javafx,Treeview,我目前拥有以下对象数据结构: 项目 字符串名 信息数组列表 字符 字符串名 项目的收集 账户 字符串名 字符的集合(最多8个) 我想制作一个树状视图,如下所示: Root(invisible) ======Jake(Account) ============JakesChar(Character) ==================Amazing Sword(Item) ==================Broken Bow(Item) ==================Jun
- 字符串名
- 信息数组列表
- 字符串名
- 项目的收集
- 字符串名
- 字符的集合(最多8个)
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)
public class ObjectPointer
{
Object pointer;
String name;
}
这些名字都是我编造的,很明显,真正的实现要复杂得多。如何做到这一点?TreeItem要求每个TreeItem与其“父项”的类型相同
我唯一的解决方案是执行以下操作:
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)
public class ObjectPointer
{
Object pointer;
String name;
}
我的树视图将是ObjectPointer
类型,并且在每一行上,我将ObjectPointer
投射到帐户
、字符、
或项目
。这很糟糕,但我认为它会起作用
子问题:
- 如何让
(s)检测TreeItem
事件setonmousehave
- 如何使
(s)不使用其类型的TreeItem
方法,而是使用自定义方式显示所需的toString
属性字符串
- 如何让
(s)在GUI中显示彩色文本而不是纯文本TreeItem
谢谢大家! 如果您查看您的模型并进行一般性思考,所有类都有一定程度的相似性,您可以将其分解为一个超类:
package model;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
public abstract class GameObject<T extends 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<T> items = FXCollections.observableArrayList();
public ObservableList<T> getItems() {
return items ;
}
public abstract void createAndAddChild(String name);
}
同样的,在整个等级体系中。我会定义一个Information
类(即使它只有一个名称),使所有内容都符合结构,因此:
package model;
public class Item extends GameObject<Information> {
public Item(String name) {
super(name);
}
@Override
public void createAndAddChild(String name) {
getItems().add(new Information(name));
}
}
现在,树中的每个项目都是一个
游戏对象
,因此基本上可以构建一个树视图。主要问题是很难很好地解决。您的其他问题(您应该单独发布问题,而不是将多个问题捆绑成一个问题)都通过“在“TreeView”上使用cellFactory
来回答。如果我以后有时间,我会看看是否能回答主要问题。您可以考虑创建一个接口(如可显示的
),并让您的所有类character、Item、Account和其他实现它的类。这样,你可以创建一个树状视图,但这可能会变得丑陋,这取决于你的应用程序的复杂性。看到您想要显示的体系结构,另一个解决方案可能是使用XML(或JSON或else)转换器来转换您的模型类。然后,您只需解析XML数据即可将其显示给用户。在imo中,这似乎是一种分离模型和显示逻辑的好方法。在某些情况下,无论您做什么,它都会变得丑陋,因为TreeView
是同质的。让所有的对象都实现一个接口有点帮助。所以实际上,我扮演了一个丑陋的角色。由于模型中的所有类都非常相似,所以分解出一个(泛型)超类并将其用作树的类型(本质上是@Kwoinkwoin所建议的),这就非常好了。你可能需要在cell实现中进行一些类型测试,这完全取决于你想要做什么。添加了一个相关但完整(更一般)的示例,我得说,你在这里的帖子中付出的努力是值得称赞的。谢谢你讲了这么多细节。有一件事让我有点紧张,那就是为了TreeView
,我的项目改变了对帐户
、游戏角色
或项目
类的看法。会做TreeView myTree=newtreeview()
然后在需要时将其投射到帐户
,游戏角色
,或项目
,这是个坏主意吗?带有一个字段的界面
想法也是一个有趣的想法。我只是有点害怕做继承的想法,因为我对我的项目有多远。还有没有非同质的TreeView
?我很惊讶还没有人尝试做一个。我有点困惑,为什么TreeView
甚至需要同质化。就像<代码>网格窗格
必须是同质的。想象一下,GridPane
必须在其“网格”的每个插槽中具有相同类型的节点。这是毫无意义的。为什么TreeView
必须这样?你不愿意使用继承是有根据的。测试类型和类型也是一种代码味道。。。不幸的是,TreeView
在整个过程中使用单一类型这一事实实际上迫使您在这两种邪恶中进行选择。请看一下我所介绍的稍微更一般的版本:ModelTree
没有对继承层次结构进行任何假设。基本上,您可以将类型测试封装在传递到ModelTree
的函数中GridPane
是同质的:每个子节点都有相同的类型:节点
。当然,您可以传入任何您喜欢的节点(Liskov substitution principal),但是如果您想遍历getChildren()
列表并执行特定于您传入的节点类型的操作,那么您的情况基本相同。使用T getValue()
和observeList getChildren()
来定义TreeItem
可能是可能的,但使用起来会非常复杂,可能会禁止没有经验的程序员使用它。
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<GameObject<?>> treeView ;
private final List<Class<? extends GameObject<?>>> itemTypes = Arrays.asList(
Account.class, GameCharacter.class, Item.class, Information.class
);
public Tree(ObservableList<Account> accounts) {
treeView = new TreeView<>();
GameObject<?> root = new GameObject<Account>("") {
@Override
public ObservableList<Account> getItems() {
return accounts ;
}
@Override
public void createAndAddChild(String name) {
getItems().add(new Account(name));
}
};
TreeItem<GameObject<?>> treeRoot = createItem(root);
treeView.setRoot(treeRoot);
treeView.setShowRoot(false);
treeView.setCellFactory(tv -> {
TreeCell<GameObject<?>> cell = new TreeCell<GameObject<?>>() {
@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<GameObject<?>> getTreeView() {
return treeView ;
}
private TreeItem<GameObject<?>> createItem(GameObject<?> object) {
// create tree item with children from game object's list:
TreeItem<GameObject<?>> 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<? extends GameObject<?>> 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());
}
}
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<GameObject<?>> treeView = tree.getTreeView();
TextField addField = new TextField();
Button addButton = new Button("Add");
EventHandler<ActionEvent> addHandler = e -> {
TreeItem<GameObject<?>> 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<GameObject<?>> selected = treeView.getSelectionModel().getSelectedItem() ;
return selected == null || selected.getValue() instanceof Information ;
}, treeView.getSelectionModel().selectedItemProperty()));
Button deleteButton = new Button("Delete");
deleteButton.setOnAction(e -> {
TreeItem<GameObject<?>> selected = treeView.getSelectionModel().getSelectedItem() ;
TreeItem<GameObject<?>> 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<Account> 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);
}
}
.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 ;
}