Skip to content

Instantly share code, notes, and snippets.

@james-d
Last active October 11, 2015 22:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save james-d/3da84819033a03db1496 to your computer and use it in GitHub Desktop.
Save james-d/3da84819033a03db1496 to your computer and use it in GitHub Desktop.
Demo of creating TreeViews with dynamic connections between them. Not guaranteed to be memory-leak-free or efficient....
package connectedtrees;
public class Assignment {
private final Task task ;
private final Employee assignee ;
public Assignment(Task task, Employee assignee) {
this.task = task;
this.assignee = assignee;
}
public Task getTask() {
return task;
}
public Employee getAssignee() {
return assignee;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((assignee == null) ? 0 : assignee.hashCode());
result = prime * result + ((task == null) ? 0 : task.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Assignment other = (Assignment) obj;
if (assignee == null) {
if (other.assignee != null)
return false;
} else if (!assignee.equals(other.assignee))
return false;
if (task == null) {
if (other.task != null)
return false;
} else if (!task.equals(other.task))
return false;
return true;
}
@Override
public String toString() {
return task + " -> " + assignee ;
}
}
package connectedtrees;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.shape.Line;
public class AssignmentView {
private final Assignment assignment ;
private final Line line = new Line();
private final ObjectProperty<Point2D> start = new SimpleObjectProperty<>(this, "start");
public final Point2D getStart() {
return start.get();
}
public final void setStart(Point2D start) {
this.start.set(start);
}
public ObjectProperty<Point2D> startProperty() {
return start ;
}
private final ObjectProperty<Point2D> end = new SimpleObjectProperty<>(this, "end");
public final Point2D getEnd() {
return end.get();
}
public final void setEnd(Point2D end) {
this.end.set(end);
}
public ObjectProperty<Point2D> endProperty() {
return end ;
}
public AssignmentView(Assignment assignment) {
this.assignment = assignment ;
line.getStyleClass().add("assignment");
line.setManaged(false);
line.startXProperty().bind(Bindings.createDoubleBinding(() -> {
if (start.get()==null) {
return 0.0 ;
} else {
return start.get().getX();
}
}, start));
line.startYProperty().bind(Bindings.createDoubleBinding(() -> {
if (start.get()==null) {
return 0.0 ;
} else {
return start.get().getY();
}
}, start));
line.endXProperty().bind(Bindings.createDoubleBinding(() -> {
if (end.get()==null) {
return 0.0 ;
} else {
return end.get().getX();
}
}, end));
line.endYProperty().bind(Bindings.createDoubleBinding(() -> {
if (end.get()==null) {
return 0.0 ;
} else {
return end.get().getY();
}
}, end));
line.visibleProperty().bind(
Bindings.isNotNull(start)
.and(Bindings.isNotNull(end)));
}
public Node getView(){
return line ;
}
public Assignment getAssignment() {
return assignment ;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((assignment == null) ? 0 : assignment.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
AssignmentView other = (AssignmentView) obj;
if (assignment == null) {
if (other.assignment != null)
return false;
} else if (!assignment.equals(other.assignment))
return false;
return true;
}
}
package connectedtrees;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.ObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
import javafx.collections.SetChangeListener.Change;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class ConnectedTrees extends Application {
private final List<Employee> employees = new ArrayList<>();
private final List<Project> projects = new ArrayList<>();
private final ObservableSet<Assignment> assignments = FXCollections.observableSet();
private final ObservableSet<AssignmentView> assignmentViews = FXCollections.observableSet();
@Override
public void start(Stage primaryStage) {
createData();
TreeItem<Employee> employeeTreeRoot = buildEmployeeTree();
TreeItem<Object> taskTreeRoot = buildTaskTree();
TreeView<Object> taskTree = new TreeView<>(taskTreeRoot);
TreeView<Employee> employeeTree = new TreeView<>(employeeTreeRoot);
setUpTreeCellFactories(taskTree, employeeTree);
BorderPane root = new BorderPane();
HBox controls = new HBox(5);
Label assignToLabel = new Label("Assign to:");
ComboBox<Employee> assigneeCombo
= new ComboBox<Employee>(FXCollections.observableList(employees));
Button assignButton = new Button("Assign");
assignButton.disableProperty().bind(
Bindings.createBooleanBinding(() -> {
TreeItem<Object> selectedTask = taskTree.getSelectionModel().getSelectedItem();
if (selectedTask == null) return true ;
if (! (selectedTask.getValue() instanceof Task)) return true ;
if (assigneeCombo.getValue() == null) return true;
return false ;
}, taskTree.getSelectionModel().selectedItemProperty(), assigneeCombo.valueProperty())
);
assignButton.setOnAction(event ->
createAssignment((Task)taskTree.getSelectionModel().getSelectedItem().getValue(),
assigneeCombo.getValue()));
controls.getChildren().addAll(assignToLabel, assigneeCombo, assignButton);
controls.setAlignment(Pos.CENTER);
root.setTop(controls);
HBox trees = new HBox(15);
trees.getChildren().addAll(taskTree, employeeTree);
root.setCenter(trees);
setUpAssignmentListeners(trees);
Scene scene = new Scene(root,400,400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
private void setUpTreeCellFactories(TreeView<Object> taskTree, TreeView<Employee> employeeTree) {
taskTree.setCellFactory(treeView -> {
TreeCell<Object> cell = new TreeCell<>();
cell.itemProperty().addListener((obs, oldItem, newItem) -> {
if (oldItem != null && oldItem instanceof Task) {
assignmentViews.stream().filter(view -> view.getAssignment().getTask() == oldItem)
.forEach(view -> {
view.startProperty().unbind();
view.setStart(null);
});
}
if (newItem != null && newItem instanceof Task) {
assignmentViews.stream().filter(view -> view.getAssignment().getTask() == newItem)
.forEach(view -> bindLocation(view.startProperty(), cell, taskTree));
}
cell.setText(newItem == null ? null : newItem.toString());
});
assignmentViews.addListener((Change<? extends AssignmentView> change) -> {
if (change.wasAdded() && change.getElementAdded().getAssignment().getTask() == cell.getItem()) {
AssignmentView view = change.getElementAdded();
bindLocation(view.startProperty(), cell, taskTree);
}
});
return cell ;
});
employeeTree.setCellFactory(treeView -> {
TreeCell<Employee> cell = new TreeCell<Employee>() ;
cell.itemProperty().addListener((obs, oldEmployee, newEmployee) -> {
if (oldEmployee != null) {
assignmentViews.stream().filter(view -> view.getAssignment().getAssignee() == oldEmployee)
.forEach(view -> {
view.endProperty().unbind();
view.setEnd(null);
});
}
if (newEmployee != null) {
assignmentViews.stream().filter(view -> view.getAssignment().getAssignee() == newEmployee)
.forEach(view -> bindLocation(view.endProperty(), cell, employeeTree));
}
cell.setText(newEmployee == null ? null : newEmployee.toString());
});
assignmentViews.addListener((Change<? extends AssignmentView> change) -> {
if (change.wasAdded() && change.getElementAdded().getAssignment().getAssignee() == cell.getItem()) {
AssignmentView view = change.getElementAdded();
bindLocation(view.endProperty(), cell, taskTree);
}
});
return cell ;
});
}
private void bindLocation(ObjectProperty<Point2D> locationToBind, TreeCell<?> cell, TreeView<?> tree) {
Node treeParent = tree.getParent();
ObjectBinding<Point2D> locationBinding = new ObjectBinding<Point2D>() {
{
cell.localToSceneTransformProperty().addListener((obs, oldTransform, newTransform) -> this.invalidate());
tree.boundsInParentProperty().addListener((obs, oldBounds, newBounds) -> this.invalidate());
}
@Override
protected Point2D computeValue() {
Bounds cellBoundsInLocal = cell.getBoundsInLocal();
Point2D cellCenter = new Point2D(
cellBoundsInLocal.getMinX() + cellBoundsInLocal.getWidth() / 2,
cellBoundsInLocal.getMinY() + cellBoundsInLocal.getHeight() / 2);
Point2D cellCenterInScene = cell.localToScene(cellCenter);
Point2D cellCenterInTreeParent = treeParent.sceneToLocal(cellCenterInScene);
return cellCenterInTreeParent ; }
};
locationToBind.bind(locationBinding);
}
private void setUpAssignmentListeners(Pane container) {
assignments.addListener((Change<? extends Assignment> change) -> {
if (change.wasAdded()) {
Assignment newAssignment = change.getElementAdded();
AssignmentView view = new AssignmentView(newAssignment);
assignmentViews.add(view);
} else if (change.wasRemoved()) {
Assignment removedAssignment = change.getElementRemoved();
assignmentViews.remove(new AssignmentView(removedAssignment));
}
});
assignmentViews.addListener((Change<? extends AssignmentView> change) -> {
if (change.wasAdded()) {
AssignmentView view = change.getElementAdded();
container.getChildren().add(view.getView());
} else if (change.wasRemoved()) {
AssignmentView view = change.getElementRemoved();
container.getChildren().remove(view.getView());
view.startProperty().unbind();
view.endProperty().unbind();
}
});
}
private void createData() {
employees.addAll(Arrays.asList(
new Employee("Bill Gates"),
new Employee("Tim Cook"),
new Employee("Larry Ellison"),
new Employee("Larry Page"),
new Employee("Mark Zuckerman")
));
Project morningProject = new Project("Morning routine");
morningProject.getTasks().addAll(
new Task("Make Coffee"),
new Task("Fetch news reports"),
new Task("Check daily calendar")
);
Project janitorialProject = new Project("Janitorial");
janitorialProject.getTasks().addAll(
new Task("Clean desk"),
new Task("Sweep floor"),
new Task("Clean coffee maker")
);
Project adminProject = new Project("Administrative");
adminProject.getTasks().addAll(
new Task("Organize filing"),
new Task("Order supplies")
);
projects.addAll(Arrays.asList(
morningProject, janitorialProject, adminProject
));
}
private TreeItem<Employee> buildEmployeeTree() {
TreeItem<Employee> root = new TreeItem<>(new Employee("Staff"));
root.getChildren().addAll(
employees.stream().map(
employee -> new TreeItem<Employee>(employee)
).collect(Collectors.toList())
);
return root ;
}
private TreeItem<Object> buildTaskTree() {
TreeItem<Object> root = new TreeItem<>("Projects");
root.getChildren().addAll(
projects.stream().map(
project -> {
TreeItem<Object> item = new TreeItem<Object>(project);
item.getChildren().addAll(
project.getTasks().stream().map(TreeItem<Object>::new).collect(Collectors.toList())
);
return item ;
}
).collect(Collectors.toList())
);
return root ;
}
private void createAssignment(Task task, Employee employee) {
assignments.add(new Assignment(task, employee));
}
public static void main(String[] args) {
launch(args);
}
}
package connectedtrees;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
public class Employee {
private final StringProperty name = new SimpleStringProperty(this, "name");
public final String getName() {
return name.get() ;
}
public final void setName(String name) {
this.name.set(name);
}
public StringProperty nameProperty() {
return name ;
}
public final ObservableList<Task> assignments = FXCollections.observableArrayList();
public final ObservableList<Task> getAssignments() {
return assignments ;
}
public Employee(String name) {
setName(name);
}
@Override
public String toString() {
return name.get();
}
}
package connectedtrees;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
public class Project {
private final StringProperty title = new SimpleStringProperty(this, "title");
public final String getTitle() {
return title.get();
}
public final void setTitle(String title) {
this.title.set(title);
}
public StringProperty titleProperty() {
return title ;
}
private final ObservableList<Task> tasks = FXCollections.observableArrayList();
public final ObservableList<Task> getTasks() {
return tasks ;
}
public Project(String title) {
setTitle(title);
}
@Override
public String toString() {
return title.get();
}
}
package connectedtrees;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Task {
private final StringProperty title = new SimpleStringProperty(this, "title");
public final String getTitle() {
return title.get() ;
}
public final void setTitle(String title) {
this.title.set(title);
}
public StringProperty titleProperty() {
return title ;
}
public Task(String title) {
setTitle(title);
}
@Override
public String toString() {
return title.get();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment