From 8fe8128b0bcd7d442cf8ba65443e744604eddca5 Mon Sep 17 00:00:00 2001 From: tznind Date: Tue, 15 Dec 2020 09:37:59 +0000 Subject: [PATCH] Replaced delegates with new interface ITreeBuilder --- Terminal.Gui/Views/TreeView.cs | 199 +++++++++++++++++----- UICatalog/Scenarios/TreeViewFileSystem.cs | 17 +- UnitTests/TreeViewTests.cs | 15 +- 3 files changed, 169 insertions(+), 62 deletions(-) diff --git a/Terminal.Gui/Views/TreeView.cs b/Terminal.Gui/Views/TreeView.cs index 31a03f178..8d95e067e 100644 --- a/Terminal.Gui/Views/TreeView.cs +++ b/Terminal.Gui/Views/TreeView.cs @@ -64,30 +64,146 @@ namespace Terminal.Gui { { Text = text; } - - } /// - /// Hierarchical tree view with expandable branches. Branch objects are dynamically determined when expanded using a user defined + /// Interface for supplying data to a on demand as root level nodes are expanded by the user + /// + public interface ITreeBuilder + { + /// + /// Returns true if is implemented by this class + /// + /// + bool SupportsCanExpand {get;} + + /// + /// Returns true/false for whether a model has children. This method should be implemented when is an expensive operation otherwise should return false (in which case this method will not be called) + /// + /// + /// + bool CanExpand(object model); + + /// + /// Returns all children of a given which should be added to the tree as new branches underneath it + /// + /// + /// + IEnumerable GetChildren(object model); + } + + /// + /// Abstract implementation of + /// + public abstract class TreeBuilder : ITreeBuilder { + + /// + public bool SupportsCanExpand { get; protected set;} = false; + + /// + /// Override this method to return a rapid answer as to whether returns results. + /// + /// + /// + public virtual bool CanExpand (object model){ + + return GetChildren(model).Any(); + } + + /// + public abstract IEnumerable GetChildren (object model); + + /// + /// Constructs base and initializes + /// + /// Pass true if you intend to implement otherwise false + public TreeBuilder(bool supportsCanExpand) + { + SupportsCanExpand = supportsCanExpand; + } + } + + /// + /// implementation for objects + /// + public class TreeNodeBuilder : TreeBuilder { + + /// + /// Initialises a new instance of builder for any model objects of Type + /// + public TreeNodeBuilder():base(false) + { + + } + + /// + /// Returns from + /// + /// + /// + public override IEnumerable GetChildren (object model) + { + return model is ITreeNode n ? n.Children : Enumerable.Empty(); + } + } + + /// + /// Implementation of that uses user defined functions + /// + public class DelegateTreeBuilder : TreeBuilder + { + private Func> childGetter; + private Func canExpand; + + /// + /// Constructs an implementation of that calls the user defined method to determine children + /// + /// + /// + public DelegateTreeBuilder(Func> childGetter) : base(false) + { + this.childGetter = childGetter; + } + + /// + /// Constructs an implementation of that calls the user defined method to determine children and to determine expandability + /// + /// + /// + /// + public DelegateTreeBuilder(Func> childGetter, Func canExpand) : base(true) + { + this.childGetter = childGetter; + this.canExpand = canExpand; + } + + /// + /// Returns whether a node can be expanded based on the delegate passed during construction + /// + /// + /// + public override bool CanExpand (object model) + { + return canExpand?.Invoke(model) ?? base.CanExpand (model); + } + + /// + /// Returns children using the delegate method passed during construction + /// + /// + /// + public override IEnumerable GetChildren (object model) + { + return childGetter.Invoke(model); + } + } + + + /// + /// Hierarchical tree view with expandable branches. Branch objects are dynamically determined when expanded using a user defined /// public class TreeView : View { - /// - /// Default implementation of a . Supports returning children of or otherwise returns an empty collection (i.e. no children) - /// - static ChildrenGetterDelegate DefaultChildrenGetter = (s)=>{return s is ITreeNode n ? n.Children : Enumerable.Empty();}; - - /// - /// This is the delegate that will be used to fetch the children of a model object - /// - public ChildrenGetterDelegate ChildrenGetter { - get { return childrenGetter ?? DefaultChildrenGetter; } - set { childrenGetter = value; } - } - - private ChildrenGetterDelegate childrenGetter; - private CanExpandGetterDelegate canExpandGetter; private int scrollOffset; /// @@ -97,13 +213,10 @@ namespace Terminal.Gui { public bool ShowBranchLines {get;set;} = true; /// - /// Optional delegate where is expensive. This should quickly return true/false for whether an object is expandable. (e.g. indicating to a user that all folders can be expanded because they are folders without having to calculate contents) + /// Determines how sub branches of the tree are dynamically built at runtime as the user expands root nodes /// - /// When this is null is used directly to determine if a node should be expandable - public CanExpandGetterDelegate CanExpandGetter { - get { return canExpandGetter; } - set { canExpandGetter = value; } - } + /// + public ITreeBuilder TreeBuilder { get;set;} /// /// private variable for @@ -179,11 +292,20 @@ namespace Terminal.Gui { /// - /// Creates a new tree view with absolute positioning. Use to set set root objects for the tree + /// Creates a new tree view with absolute positioning. Use to set set root objects for the tree. /// - public TreeView ():base() + public TreeView():base() { CanFocus = true; + TreeBuilder = new TreeNodeBuilder(); + } + + /// + /// Initialises .Creates a new tree view with absolute positioning. Use to set set root objects for the tree. + /// + public TreeView(ITreeBuilder builder) : this() + { + TreeBuilder = builder; } /// @@ -580,10 +702,10 @@ namespace Terminal.Gui { /// public virtual void FetchChildren() { - if (tree.ChildrenGetter == null) + if (tree.TreeBuilder == null) return; - var children = tree.ChildrenGetter(this.Model) ?? new object[0]; + var children = tree.TreeBuilder.GetChildren(this.Model) ?? Enumerable.Empty(); this.ChildBranches = children.ToDictionary(k=>k,val=>new Branch(tree,this,val)); } @@ -684,8 +806,8 @@ namespace Terminal.Gui { if(ChildBranches == null) { //if there is a rapid method for determining whether there are children - if(tree.CanExpandGetter != null) { - return tree.CanExpandGetter(Model) ? tree.ExpandableSymbol : leafSymbol; + if(tree.TreeBuilder.SupportsCanExpand) { + return tree.TreeBuilder.CanExpand(Model) ? tree.ExpandableSymbol : leafSymbol; } //there is no way of knowing whether we can expand without fetching the children @@ -736,7 +858,7 @@ namespace Terminal.Gui { // we already knew about some children so preserve the state of the old children // first gather the new Children - var newChildren = tree.ChildrenGetter(this.Model) ?? new object[0]; + var newChildren = tree.TreeBuilder?.GetChildren(this.Model) ?? Enumerable.Empty(); // Children who no longer appear need to go foreach(var toRemove in ChildBranches.Keys.Except(newChildren).ToArray()) @@ -800,13 +922,6 @@ namespace Terminal.Gui { return Parent.ChildBranches.Values.LastOrDefault() == this; } } - - /// - /// Delegates of this type are used to fetch the children of the given model object - /// - /// The parent whose children should be fetched - /// An enumerable over the children - public delegate IEnumerable ChildrenGetterDelegate(object model); /// /// Delegates of this type are used to fetch string representations of user's model objects @@ -814,14 +929,6 @@ namespace Terminal.Gui { /// /// public delegate string AspectGetterDelegate(object model); - - /// - /// Delegates of this type are used to quickly display to the user whether a given user object can be expanded when fetching it's children is expensive (e.g. indicating to a user that all 1000 folders can be expanded because they are folders without having to calculate contents) - /// - /// - /// - public delegate bool CanExpandGetterDelegate(object model); - /// /// Event arguments describing a change in selected object in a tree view diff --git a/UICatalog/Scenarios/TreeViewFileSystem.cs b/UICatalog/Scenarios/TreeViewFileSystem.cs index 1df6b8563..7031ab91a 100644 --- a/UICatalog/Scenarios/TreeViewFileSystem.cs +++ b/UICatalog/Scenarios/TreeViewFileSystem.cs @@ -73,12 +73,14 @@ namespace UICatalog.Scenarios { /// private void SetupFileSystemDelegates () { - - // As a shortcut to enumerating half the file system, tell tree that all directories are expandable (even if they turn out to be empty later on) - _treeView.CanExpandGetter = (o)=>o is DirectoryInfo; + _treeView.TreeBuilder = new DelegateTreeBuilder( - // Determines how to compute children of any given branch - _treeView.ChildrenGetter = GetChildren; + // Determines how to compute children of any given branch + GetChildren, + // As a shortcut to enumerating half the file system, tell tree that all directories are expandable (even if they turn out to be empty later on) + (o)=>o is DirectoryInfo + + ); // Determines how to represent objects as strings on the screen _treeView.AspectGetter = AspectGetter; @@ -88,9 +90,8 @@ namespace UICatalog.Scenarios { { ClearObjects(); - // Clear any previous delegates - _treeView.CanExpandGetter = null; - _treeView.ChildrenGetter = null; + // Set builder to serve children of ITreeNode objects + _treeView.TreeBuilder = new TreeNodeBuilder(); // Add 2 root nodes with simple set of subfolders _treeView.AddObject(CreateSimpleRoot()); diff --git a/UnitTests/TreeViewTests.cs b/UnitTests/TreeViewTests.cs index 1c8daeb04..849d503fc 100644 --- a/UnitTests/TreeViewTests.cs +++ b/UnitTests/TreeViewTests.cs @@ -33,8 +33,7 @@ namespace UnitTests { Cars = new []{car1 ,car2} }; - var tree = new TreeView(); - tree.ChildrenGetter = (s)=> s is Factory f ? f.Cars: null; + var tree = new TreeView(new DelegateTreeBuilder((s)=> s is Factory f ? f.Cars: null)); tree.AddObject(factory1); return tree; @@ -219,11 +218,11 @@ namespace UnitTests { Assert.False(tree.IsExpanded(c1)); // change the children getter so that now cars can have wheels - tree.ChildrenGetter = (o)=> + tree.TreeBuilder = new DelegateTreeBuilder((o)=> // factories have cars o is Factory ? new object[]{c1,c2} // cars have wheels - : new object[]{wheel }; + : new object[]{wheel }); // still cannot expand tree.Expand(c1); @@ -256,11 +255,11 @@ namespace UnitTests { Assert.False(tree.IsExpanded(c1)); // change the children getter so that now cars can have wheels - tree.ChildrenGetter = (o)=> + tree.TreeBuilder = new DelegateTreeBuilder((o)=> // factories have cars o is Factory ? new object[]{c1,c2} // cars have wheels - : new object[]{wheel }; + : new object[]{wheel }); // still cannot expand tree.Expand(c1); @@ -333,7 +332,7 @@ namespace UnitTests { string root = "root"; var tree = new TreeView(); - tree.ChildrenGetter = (s)=> ReferenceEquals(s , root) ? new object[]{obj1 } : null; + tree.TreeBuilder = new DelegateTreeBuilder((s)=> ReferenceEquals(s , root) ? new object[]{obj1 } : null); tree.AddObject(root); // Tree is not expanded so the root has no children yet @@ -346,7 +345,7 @@ namespace UnitTests { Assert.Equal(1,tree.GetChildren(root).Count(child=>ReferenceEquals(obj1,child))); // change the getter to return an Equal object (but not the same reference - obj2) - tree.ChildrenGetter = (s)=> ReferenceEquals(s , root) ? new object[]{obj2 } : null; + tree.TreeBuilder = new DelegateTreeBuilder((s)=> ReferenceEquals(s , root) ? new object[]{obj2 } : null); // tree has cached the knowledge of what children the root has so won't know about the change (we still get obj1) Assert.Equal(1,tree.GetChildren(root).Count(child=>ReferenceEquals(obj1,child)));