javafxtreeviewdomain-object

Javafx : Domain objects to TreeView


I have three domain objects in my app as follows :

public class Workflow {
  private String name;
  private List<Sheet> sheets;
}

public class Sheet {
  private String name;
  private List<Task> tasks;
}

public class Task {
  private String name;
}

All three are dependent as Workflow -> Sheet -> Task. My goal is to build TreeView so that it look like below :

-Workflow
|
 - workflow name
 -Sheets
 |
  - sheet name
  - Tasks
  |
   - task name

So far, I have build a sample that builds more less what I expect, but it's not generic and 'automated' at all.

public class TreeViewSample extends Application {

public static void main(String[] args) {
    launch(args);
}

@Override
public void start(Stage primaryStage) {
    primaryStage.setTitle("Tree View Sample");        

    Workflow w = setup();

    TreeItem<String> rootItem = new TreeItem<String> ("Workflow");
    rootItem.setExpanded(true);

        TreeItem<String> item = new TreeItem<String> (w.getName());
        rootItem.getChildren().add(item);
(...)


    TreeView<String> tree = new TreeView<String> (rootItem);        
    StackPane root = new StackPane();
    root.getChildren().add(tree);
    primaryStage.setScene(new Scene(root, 300, 250));
    primaryStage.show();
}

private Workflow setup(){

    Workflow wflow = new Workflow();
    wflow.setName("wflow name");
    wflow.setSheets(Arrays.asList(new Sheet("sheet name", Arrays.asList(new Task("task name")))));

    return wflow;
}

Can someone advise on how I can recursively go to my domain objects and build the TreeView as in my example ?


Solution

  • You have to create a common Model to all of you models(Workflow, Sheet,Task), since all have a String property, it is quite simple to create one. Let's suppose we have the following model:

    public class Model {
    
        private String name;
    
        public Model(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
        @Override
        public String toString() {
            return getName();
        }
    }
    
    class Workflow {
        private String name;
        private List<Sheet> sheets = new ArrayList<>();
    
        public Workflow(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
        public List<Sheet> getSheets() {
            return sheets;
        }
    }
    
    class Sheet {
        private String name;
        private List<Task> tasks = new ArrayList<>();
    
        public Sheet(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
        public List<Task> getTasks() {
            return tasks;
        }
    }
    
    class Task {
        private String name;
    
        public Task(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    }
    

    I kept there together all of your models, to see them better.

    I see you don't use any .fxml file at your app, mine is with .fxml I recommend that you separate at least to separate the Main class from the Controller class, like:

    public class Main extends Application{
    
    
        @Override
        public void start(Stage primaryStage) throws Exception {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("View.fxml"));
            AnchorPane pane = loader.load();
            primaryStage.setScene(new Scene(pane,800,600));
            primaryStage.show();
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }  
    

    Then the Controller class:

    public class Controller implements Initializable {
    
        @FXML
        private TreeView<Model> treeView;
    
        @Override
        public void initialize(URL location, ResourceBundle resources) {
            Workflow workflow = createWorkflow(); // This just sets up the models that you are using.
    
            // You have to create a root in your case the "Workflow"
            TreeItem<Model> root = new TreeItem<>(new Model(workflow.getName()));
            // The foreach sheet you create a branch
            workflow.getSheets().forEach(sheet -> {
                TreeItem<Model> sheetBranch = new TreeItem<>(new Model(sheet.getName()));
                // Then you have to add each branch to the root
                root.getChildren().add(sheetBranch);
                // Then foreach sheet you create a task item
                sheet.getTasks().forEach(task -> {
                    TreeItem<Model> taskItem = new TreeItem<>(new Model(task.getName()));
                    // Then you have to add each task to its sheet parent
                    sheetBranch.getChildren().add(taskItem);
                });
            });
            // Finally, you set the root for the TreeView. Of course this can be done right after instantiating the root.
            treeView.setRoot(root);
        }
    
        // ------------------- Setup the model -----------------------
    
        private Workflow createWorkflow() {
            Workflow workflow = new Workflow("Workflow");
            workflow.getSheets().addAll(createSheets());
            return workflow;
        }
    
        private List<Sheet> createSheets() {
            List<Sheet> sheets = new ArrayList<>();
            IntStream.range(1, 10).forEach(value -> sheets.add(createSheet()));
            return sheets;
        }
    
        private Sheet createSheet() {
            Sheet sheet = new Sheet("Sheet" + new Random().nextInt(100)); // Random added to have different names
            sheet.getTasks().addAll(createTasks());
            return sheet;
        }
    
        private List<Task> createTasks() {
            List<Task> tasks = new ArrayList<>();
            IntStream.range(1, 5).forEach(value -> tasks.add(createTask()));
            return tasks;
        }
    
        private Task createTask() {
            return new Task("Task" + new Random().nextInt(100)); // Random added to have different names
        }
    }
    

    Just in case if you need here is the .fxml file:

    <?xml version="1.0" encoding="UTF-8"?>
    
    <?import javafx.scene.control.TreeView?>
    <?import javafx.scene.layout.AnchorPane?>
    <AnchorPane xmlns="http://javafx.com/javafx"
                xmlns:fx="http://javafx.com/fxml"
                fx:controller="stackoverflow.tree.Controller">
        <TreeView fx:id="treeView"/>
    </AnchorPane>
    

    If you don't know the depth of the TreeView you can create all of the branches or leaves using recursion. In this case it is much simpler to use two foreachs instead of creating a recursive method which builds the tree structure.