瀏覽代碼

TreeView, TreeItem new features

TreeView: added possibility to hide the root node.
TreeItem: added possibility to specify the children left margin.

Signed-off-by: PAlex404 <alessandro.parisi406@gmail.com>
PAlex404 4 年之前
父節點
當前提交
13e5cb0242

+ 6 - 2
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXTreeItem.java

@@ -72,6 +72,8 @@ public class MFXTreeItem<T> extends AbstractMFXTreeItem<T> {
      * <p>
      * Adds a listener to {@link #selectedProperty()} and the {@link #treeViewProperty()} allowing item selection before the Scene is shown
      * by calling the SelectionModel {@link SelectionModel#scanTree(AbstractMFXTreeItem)} method.
+     * <p>
+     * Adds a listener to {@link #childrenMarginProperty()} to request layout in case it changes.
      */
     private void initialize() {
         getStyleClass().add(STYLE_CLASS);
@@ -99,6 +101,8 @@ public class MFXTreeItem<T> extends AbstractMFXTreeItem<T> {
                 getSelectionModel().scanTree(this);
             }
         });
+
+        childrenMarginProperty().addListener((observable, oldValue, newValue) -> requestLayout());
     }
 
     /**
@@ -272,14 +276,14 @@ public class MFXTreeItem<T> extends AbstractMFXTreeItem<T> {
     }
 
     /**
-     * Simple layout strategy. Each item in the {@link #items} list has a left margin set to 20 by default.
+     * Simple layout strategy. Each item in the {@link #items} list has a left margin defined by {@link #childrenMarginProperty()}.
      */
     @Override
     protected void layoutChildren() {
         super.layoutChildren();
 
         items.forEach(
-                item -> VBox.setMargin(item, new Insets(0, 0, 0, 20))
+                item -> VBox.setMargin(item, new Insets(0, 0, 0, getChildrenMargin()))
         );
     }
 

+ 67 - 1
materialfx/src/main/java/io/github/palexdev/materialfx/controls/MFXTreeView.java

@@ -3,12 +3,20 @@ package io.github.palexdev.materialfx.controls;
 import io.github.palexdev.materialfx.MFXResourcesLoader;
 import io.github.palexdev.materialfx.controls.base.AbstractMFXTreeItem;
 import io.github.palexdev.materialfx.controls.base.ISelectionModel;
+import javafx.beans.property.BooleanProperty;
 import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleBooleanProperty;
 import javafx.beans.property.SimpleObjectProperty;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.event.Event;
+import javafx.event.EventType;
 import javafx.geometry.Insets;
+import javafx.scene.control.Skin;
 
 /**
  * This is the container for a tree made of AbstractMFXTreeItems.
+ *
  * @param <T> The type of the data within the items.
  */
 public class MFXTreeView<T> extends MFXScrollPane {
@@ -20,6 +28,7 @@ public class MFXTreeView<T> extends MFXScrollPane {
 
     private final ObjectProperty<AbstractMFXTreeItem<T>> root = new SimpleObjectProperty<>(null);
     private final ObjectProperty<ISelectionModel<T>> selectionModel = new SimpleObjectProperty<>(null);
+    private final BooleanProperty showRoot = new SimpleBooleanProperty(true);
 
     //================================================================================
     // Constructors
@@ -48,7 +57,32 @@ public class MFXTreeView<T> extends MFXScrollPane {
         setPadding(new Insets(3));
         MFXScrollPane.smoothVScrolling(this);
 
-        getRoot().prefWidthProperty().bind(widthProperty().subtract(10));
+        AbstractMFXTreeItem<T> root = getRoot();
+        rootProperty().addListener((observable, oldRoot, newRoot) -> {
+            newRoot.setTreeView(this);
+            setContent(newRoot);
+            setupRoot();
+        });
+        setupRoot();
+
+        showRoot.addListener((observable, oldValue, newValue) -> root.fireEvent(new TreeViewEvent(TreeViewEvent.HIDE_ROOT_EVENT, newValue)));
+    }
+
+    /**
+     * Contains common code for when the root is set the first time and when it changes.
+     */
+    public void setupRoot() {
+        AbstractMFXTreeItem<T> root = getRoot();
+        root.prefWidthProperty().bind(widthProperty().subtract(10));
+        root.skinProperty().addListener(new ChangeListener<>() {
+            @Override
+            public void changed(ObservableValue<? extends Skin<?>> observable, Skin<?> oldValue, Skin<?> newValue) {
+                if (newValue != null && !isShowRoot()) {
+                    root.fireEvent(new TreeViewEvent(TreeViewEvent.HIDE_ROOT_EVENT, isShowRoot()));
+                }
+                root.skinProperty().removeListener(this);
+            }
+        });
     }
 
     /**
@@ -86,6 +120,14 @@ public class MFXTreeView<T> extends MFXScrollPane {
         this.selectionModel.set(selectionModel);
     }
 
+    public boolean isShowRoot() {
+        return showRoot.get();
+    }
+
+    public void setShowRoot(boolean showRoot) {
+        this.showRoot.set(showRoot);
+    }
+
     //================================================================================
     // Override Methods
     //================================================================================
@@ -94,4 +136,28 @@ public class MFXTreeView<T> extends MFXScrollPane {
         return STYLESHEET;
     }
 
+    /**
+     * Events class for tree views.
+     * <p>
+     * Defines a new EventType:
+     * <p>
+     * - HIDE_ROOT_EVENT: when the tree view's root is set to be hidden/visible we need to fire this event
+     * because we need to communicate with the root's skin.
+     * <p>
+     * Of course these events are for internal use only so they should not be used by users.
+     */
+    public static class TreeViewEvent extends Event {
+        private final boolean show;
+
+        public static final EventType<TreeViewEvent> HIDE_ROOT_EVENT = new EventType<>(ANY, "HIDE_ROOT_EVENT");
+
+        public TreeViewEvent(EventType<? extends Event> eventType, boolean show) {
+            super(eventType);
+            this.show = show;
+        }
+
+        public boolean isShow() {
+            return show;
+        }
+    }
 }

+ 23 - 4
materialfx/src/main/java/io/github/palexdev/materialfx/controls/base/AbstractMFXTreeItem.java

@@ -2,10 +2,7 @@ package io.github.palexdev.materialfx.controls.base;
 
 import io.github.palexdev.materialfx.controls.MFXTreeView;
 import io.github.palexdev.materialfx.utils.TreeItemStream;
-import javafx.beans.property.BooleanProperty;
-import javafx.beans.property.ObjectProperty;
-import javafx.beans.property.SimpleBooleanProperty;
-import javafx.beans.property.SimpleObjectProperty;
+import javafx.beans.property.*;
 import javafx.collections.FXCollections;
 import javafx.collections.ObservableList;
 import javafx.scene.control.Control;
@@ -44,6 +41,7 @@ public abstract class AbstractMFXTreeItem<T> extends Control {
     private final ObjectProperty<MFXTreeView<T>> treeView = new SimpleObjectProperty<>(null);
 
     protected final ObjectProperty<Callback<AbstractMFXTreeItem<T>, AbstractMFXTreeCell<T>>> cellFactory = new SimpleObjectProperty<>();
+    private final DoubleProperty childrenMargin = new SimpleDoubleProperty(20);
     private final BooleanProperty startExpanded =  new SimpleBooleanProperty(false);
     private final BooleanProperty selected = new SimpleBooleanProperty(false);
 
@@ -232,6 +230,27 @@ public abstract class AbstractMFXTreeItem<T> extends Control {
         this.parent = parent;
     }
 
+    /**
+     * @return the children left margin to be used in layout.
+     */
+    public double getChildrenMargin() {
+        return childrenMargin.get();
+    }
+
+    /**
+     * Specifies the left margin of each children.
+     */
+    public DoubleProperty childrenMarginProperty() {
+        return childrenMargin;
+    }
+
+    /**
+     * Sets the children left margin.
+     */
+    public void setChildrenMargin(double childrenMargin) {
+        this.childrenMargin.set(childrenMargin);
+    }
+
     /**
      * @return the state of {@link #startExpandedProperty()}
      */

+ 35 - 28
materialfx/src/main/java/io/github/palexdev/materialfx/skins/MFXTreeItemSkin.java

@@ -8,6 +8,7 @@ import io.github.palexdev.materialfx.utils.NodeUtils;
 import javafx.animation.*;
 import javafx.collections.FXCollections;
 import javafx.collections.ListChangeListener;
+import javafx.geometry.Insets;
 import javafx.geometry.Side;
 import javafx.scene.Node;
 import javafx.scene.control.ContextMenu;
@@ -25,6 +26,7 @@ import java.util.Comparator;
 import java.util.List;
 
 import static io.github.palexdev.materialfx.controls.MFXTreeItem.TreeItemEvent;
+import static io.github.palexdev.materialfx.controls.MFXTreeView.TreeViewEvent;
 
 /**
  * This is the implementation of the {@code Skin} associated with every {@link MFXTreeItem}.
@@ -149,14 +151,7 @@ public class MFXTreeItemSkin<T> extends SkinBase<MFXTreeItem<T>> {
         setListeners();
 
         if (item.isStartExpanded()) {
-            forcedUpdate = true;
-            box.getChildren().addAll(item.getItems());
-            box.applyCss();
-            box.layout();
-            box.setPrefHeight(item.getInitialHeight() + computeExpandCollapse());
-            cell.updateCell(item);
-            item.setExpanded(true);
-            forcedUpdate = false;
+            forceUpdate();
         }
     }
 
@@ -182,6 +177,9 @@ public class MFXTreeItemSkin<T> extends SkinBase<MFXTreeItem<T>> {
      * - COLLAPSE_EVENT: when the item is asked to collapse itself we fire an event, build the expand animation, and
      * if the event is on the item on which is fired build a fade out animation for each of its items. <p></p>
      *
+     * - HIDE_ROOT_EVENT: if the tree view is set to hide the root node then we need force update the root and expand it, hide its cell
+     * and reposition its children by setting its top and left padding. Otherwise the cell is set to be visible and the padding is reset.
+     *
      * - MOUSE_PRESSED: when the mouse is pressed on the item we check if the button was the primary button and if
      * it was a double click. If that is the case than we fire a dummy MOUSE_PRESSED event on the cell's disclosure node because
      * the behavior is to expand/collapse the item only if the mouse was pressed on the disclosure node.
@@ -227,6 +225,19 @@ public class MFXTreeItemSkin<T> extends SkinBase<MFXTreeItem<T>> {
             animation.play();
         });
 
+        item.addEventHandler(TreeViewEvent.HIDE_ROOT_EVENT, hideRootEvent -> {
+            if (!hideRootEvent.isShow()) {
+                if (!item.isExpanded()) {
+                    forceUpdate();
+                }
+                cell.setVisible(false);
+                item.setPadding(new Insets(-(item.getInitialHeight() * 2), 0, 0, -item.getChildrenMargin()));
+            } else {
+                cell.setVisible(true);
+                item.setPadding(Insets.EMPTY);
+            }
+        });
+
         item.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
             if (event.getButton() == MouseButton.PRIMARY &&
                     event.getClickCount() == 2
@@ -239,11 +250,6 @@ public class MFXTreeItemSkin<T> extends SkinBase<MFXTreeItem<T>> {
                 item.getSelectionModel().select(item, event);
             }
         });
-
-        //================================================================================
-        // DEBUG
-        //================================================================================
-        //debugListeners(); // TODO remove
     }
 
     /**
@@ -323,6 +329,22 @@ public class MFXTreeItemSkin<T> extends SkinBase<MFXTreeItem<T>> {
         return value;
     }
 
+    /**
+     * Contains common code for forcing an item to expand.
+     */
+    private void forceUpdate() {
+        MFXTreeItem<T> item = getSkinnable();
+
+        forcedUpdate = true;
+        box.getChildren().addAll(item.getItems());
+        box.applyCss();
+        box.layout();
+        box.setPrefHeight(item.getInitialHeight() + computeExpandCollapse());
+        cell.updateCell(item);
+        item.setExpanded(true);
+        forcedUpdate = false;
+    }
+
     /**
      * This method is responsible for calling the MFXTreeItem's {@link MFXTreeItem#cellFactoryProperty()} thus creating the cell
      * and adding an event handler for MOUSE_PRESSED on its disclosure node. If the items list is empty we consume the event and return.
@@ -357,20 +379,5 @@ public class MFXTreeItemSkin<T> extends SkinBase<MFXTreeItem<T>> {
 
         return cell;
     }
-
-    //================================================================================
-    // DEBUG
-    //================================================================================
-    private void debugListeners() {
-        MFXTreeItem<T> item = getSkinnable();
-        item.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
-            System.out.println("\n-------------------------------------------");
-            System.out.println("DATA:" + item.getData());
-            System.out.println("CELL:" + cell.getHeight());
-            System.out.println("ITEM:" + item.getHeight() + ", " + snapSizeY(item.getHeight()));
-            System.out.println("BOX:" + box.getHeight());
-            System.out.println("-------------------------------------------\n");
-        });
-    }
 }