在使用JavaFx 编写GUI程序时,不可避免的需要创建一个树组件,下面是一个简单的树组件的代码。
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TreeViewTest extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("TreeViewTest");
VBox vBox = new VBox();
buildTreeView(vBox);
Scene scene = new Scene(vBox);
primaryStage.setScene(scene);
primaryStage.show();
}
public void buildTreeView(Pane parent) {
TreeView<String> treeView = new TreeView<>();
TreeItem<String> rootItem = new TreeItem<>("五年级");
for (int i = 0; i < 10; i++) {
TreeItem<String> treeItem = new TreeItem<>("张三" + i);
rootItem.getChildren().add(treeItem);
}
treeView.setRoot(rootItem);
parent.getChildren().add(treeView);
}
}
通过上面的代码我们就实现了一个简单的树的构建,效果如图:
但是我们构建了树,有时候并不简简单单的只做展示用,有时候我们还会对树进行操作,比如获取选择节点的详细信息,如我们上面创建的树形图展示的是一个班级的学生,那么如果我需要点击某个学生然后弹出该学生的具体信息应该怎么做呢?一个立马能想到的方法是在创建树节点的时候,将学生的额外属性封装到树节点中去,要知道TreeItem才是节点组件,TreeView只是父组件,TreeView只能设置根TreeItem,此时我们查看TreeItem的方法,
发现并没有设置额外属性的方法。
然后想到javaFx的 property属性,
但是也并没有找到符合我们需要的属性。
难道JavaFx 的TreeView 在设计的时候没有考虑过这个需求吗?答案是否定的,我们看一下Tree的源码:
我们看到在类注释上有个泛型 T 它的意思是:
–此TreeView中所有树项的TreeItem值属性中包含的项的类型。
就是说,你创建TreeView 时给的是什么类型,TreeItem就封装了什么类型,然后观察TreeItem的源码,
TreeItem中{@link#getValue()值}属性的类型。
发现TreeItem中也存在T的泛型,而且可以通过TreeItem.getValue()方法得到T的实例对象,那现在一目了然了,我们在构建TreeItem的时候完全可以将我们的学生类封装进去,这样当我获取额外属性时,就可以通过getValue()方法获取了,不过另一个问题是,在这个节点上我显示的是什么,如果我封装的是对象的话,是不是显示的就是对象地址?
我们查看TreeItem的setValue()方法:
public class TreeItem<T> implements EventTarget {
....略......
/**
* Sets the application-specific data represented by this TreeItem.
*/
public final void setValue(T value) {
valueProperty().setValue(value);
}
/**
* A property representing the application-specific data contained within
* this TreeItem.
*/
public final ObjectProperty<T> valueProperty() {
if (value == null) {
value = new ObjectPropertyBase<T>() {
@Override protected void invalidated() {
fireEvent(new TreeModificationEvent<T>(VALUE_CHANGED_EVENT, TreeItem.this, get()));
}
@Override public Object getBean() {
return TreeItem.this;
}
@Override public String getName() {
return "value";
}
};
}
return value;
}
....略......
}
通过源码我们发现最终设置值的方法是通过ObjectProperty.setValue()来实现的,我们继续跟踪来到
javafx.beans.value.WritableObjectValue
public interface WritableObjectValue<T> extends WritableValue<T> {
/**
* Get the wrapped value. This must be identical to
* the value returned from {@link #getValue()}.
* <p>
* This method exists only to align WritableObjectValue API with
* {@link WritableBooleanValue} and subclasses of {@link WritableNumberValue}
* @return The current value
*/
T get();
/**
* Set the wrapped value.
* Should be equivalent to {@link #setValue(java.lang.Object) }
* @see #get()
*
* @param value
* The new value
*/
void set(T value);
}
由此我们看到确实设置的是对象,但是我们希望节点在展示的时候是String文字,而不是内存地址。等一下,这一点有点像打印对象。我们做一个测试
public class Main {
public static void main(String[] args) {
User user = new User("张三",33);
System.out.println(user);
}
static class User {
private String name;
private Integer age;
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
}
}
打印结果:
更改一下代码:
public class Main {
public static void main(String[] args) {
User user = new User("张三",33);
System.out.println(user);
}
static class User {
private String name;
private Integer age;
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
}
打印结果如下:
我们发现改动后的代码只是添加了toString()方法而已,打印出来的内容就变成了人可以理解的内容。
那如果TreeItem封装的对象设置了toString()方法,是不是节点的显示内容就是toString()方法返回的内容呢?
更新代码如下:
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TreeViewTest extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("TreeViewTest");
VBox vBox = new VBox();
buildTreeView(vBox);
Scene scene = new Scene(vBox);
primaryStage.setScene(scene);
primaryStage.show();
}
public void buildTreeView(Pane parent) {
TreeView<Student> treeView = new TreeView<>();
TreeItem<Student> rootItem = new TreeItem("五年级");
for (int i = 0; i < 10; i++) {
Student student = new Student("张三" + i, 10 + i);
TreeItem<Student> treeItem = new TreeItem(student);
rootItem.getChildren().add(treeItem);
}
treeView.setRoot(rootItem);
// 设置TreeView的点击事件
treeView.setOnMouseClicked(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
TreeItem<Student> selectedItem = treeView.getSelectionModel().getSelectedItem();
if (selectedItem == null) return;
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setContentText(selectedItem.getValue().toString2());
alert.show();
}
});
parent.getChildren().add(treeView);
}
static class Student {
private String name;
private Integer age;
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name;
}
public String toString2() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
}
效果如图:
可以发现我们取到了封装的额外属性。
当然实际情况可能会更复杂,比如不同层级的TreeItem可能封装的对象不同,此时我们在定义TreeView时,泛型最好指定一个基类,比如BaseModel,然后各个属性类去继承该基类,然后实现各自的toString()方法,在获取属性值时,先判断当前选中的节点的层级,然后从基类强转到实际的属性类即可!