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

2023-11-22

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

Item

  • 字符串名称
  • 信息的数组列表

特点

  • 字符串名称
  • 收集Item

Account

  • 字符串名称
  • 收集特点(最多 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在每一行我都会投射ObjectPointer to Account, Character, or Item。这很糟糕,但我认为它会起作用。

子问题:

  • 如何得到TreeItem(s) 检测setOnMouseHover events?

  • 如何得到TreeItem(s) 不使用toString其类型的方法,而不是显示的自定义方式String他们需要的财产?

  • 我如何获得TreeItem(s) 在 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<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);
}

类型参数T这里代表“子”对象的类型。所以你的Account类(其子类型是GameCharacter- 不要将类命名为与中的任何名称相同java.lang,顺便说一句...)看起来像

package model;

public class Account extends GameObject<GameCharacter> {

    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<Information> {

    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<GameObject<?>> {

    public Information(String name) {
        super(name);
    }

    @Override
    public ObservableList<GameObject<?>> getItems() {
        return FXCollections.emptyObservableList();
    }

    @Override
    public void createAndAddChild(String name) {
        throw new IllegalStateException("Information has no child items");
    }

}

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

您可以使用树上的单元工厂来自定义显示单元格的外观TreeItems。如果您希望每种类型的项目具有不同的外观,我建议在外部 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<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);

    }

}

以 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 ;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JavaFX TreeView 的多种对象类型? (和更多) 的相关文章

随机推荐