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

时间:2016-01-26 08:53:39

标签: java user-interface javafx treeview

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

物品

  • 字符串名称
  • 信息列表

字符

  • 字符串名称
  • 项目
  • 的集合

帐户

  • 字符串名称
  • 字符的集合(最多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;
}

My TreeView的类型为ObjectPointer,每行我会将ObjectPointer投射到AccountCharacter,Item。这是AWFUL,但我认为它会起作用。

子问题:

  • 如何让TreeItem(s)检测到setOnMouseHover个事件?

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

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

谢谢!

1 个答案:

答案 0 :(得分:14)

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

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表示&#34; child&#34;的类型。对象。因此,您的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<?>>。棘手的部分是您的树项需要反映模型中已经构建的结构。由于您在那里有可观察列表,因此您可以使用列表上的侦听器执行此操作。

您可以使用树上的单元工厂来自定义显示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<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 ;
}