diff --git a/Terminal.Gui/Views/TreeView.cs b/Terminal.Gui/Views/TreeView.cs
index 583ddea3d..c4a2bd15c 100644
--- a/Terminal.Gui/Views/TreeView.cs
+++ b/Terminal.Gui/Views/TreeView.cs
@@ -6,6 +6,9 @@ using System.Linq;
namespace Terminal.Gui {
+ ///
+ /// Hierarchical tree view with expandable branches. Branch objects are dynamically determined when expanded using a user defined
+ ///
public class TreeView : View
{
///
@@ -43,7 +46,10 @@ namespace Terminal.Gui {
///
public int ScrollOffset {get; private set;}
- public TreeView ()
+ ///
+ /// Creates a new tree view with absolute positioning. Use to set set root objects for the tree
+ ///
+ public TreeView ():base()
{
CanFocus = true;
}
@@ -59,11 +65,37 @@ namespace Terminal.Gui {
SetNeedsDisplay();
}
}
+
+ ///
+ /// Removes all objects from the tree and clears
+ ///
+ public void ClearObjects()
+ {
+ SelectedObject = null;
+ roots = new Dictionary();
+ SetNeedsDisplay();
+ }
+
+ ///
+ /// Removes the given root object from the tree
+ ///
+ /// If is the currently then the selection is cleared
+ ///
+ public void Remove(object o)
+ {
+ if(roots.ContainsKey(o)) {
+ roots.Remove(o);
+ SetNeedsDisplay();
+
+ if(Equals(SelectedObject,o))
+ SelectedObject = null;
+ }
+ }
///
/// Adds many new root level objects. Objects that are already root objects are ignored
///
- ///
+ /// Objects to add as new root level objects
public void AddObjects(IEnumerable collection)
{
bool objectsAdded = false;
@@ -83,7 +115,7 @@ namespace Terminal.Gui {
/// Returns the string representation of model objects hosted in the tree. Default implementation is to call
///
///
- public Func AspectGetter {get;set;} = (o)=>o.ToString();
+ public AspectGetterDelegate AspectGetter {get;set;} = (o)=>o.ToString();
///
public override void Redraw (Rect bounds)
@@ -142,8 +174,19 @@ namespace Terminal.Gui {
}
}
+ ///
+ /// Symbol to use for expanded branch nodes to indicate to the user that they can be collapsed. Defaults to '-'
+ ///
public char ExpandedSymbol {get;set;} = '-';
+
+ ///
+ /// Symbol to use for branch nodes that can be expanded to indicate this to the user. Defaults to '+'
+ ///
public char ExpandableSymbol {get;set;} = '+';
+
+ ///
+ /// Symbol to use for branch nodes that cannot be expanded (as they have no children). Defaults to space ' '
+ ///
public char LeafSymbol {get;set;} = ' ';
///
@@ -163,12 +206,48 @@ namespace Terminal.Gui {
case Key.CursorDown:
AdjustSelection(1);
break;
+ case Key.PageUp:
+ AdjustSelection(-Bounds.Height);
+ break;
+
+ case Key.PageDown:
+ AdjustSelection(Bounds.Height);
+ break;
+ case Key.Home:
+ GoToFirst();
+ break;
+ case Key.End:
+ GoToEnd();
+ break;
}
PositionCursor ();
return true;
}
+ ///
+ /// Changes the to the first root object and resets the to 0
+ ///
+ public void GoToFirst()
+ {
+ ScrollOffset = 0;
+ SelectedObject = roots.Keys.FirstOrDefault();
+
+ SetNeedsDisplay();
+ }
+
+ ///
+ /// Changes the to the last object in the tree and scrolls so that it is visible
+ ///
+ public void GoToEnd ()
+ {
+ var map = BuildLineMap();
+ ScrollOffset = Math.Max(0,map.Length - Bounds.Height +1);
+ SelectedObject = map.Last().Model;
+
+ SetNeedsDisplay();
+ }
+
///
/// Changes the selected object by a number of screen lines
///
@@ -201,7 +280,7 @@ namespace Terminal.Gui {
else if(newIdx >= ScrollOffset + Bounds.Height){
//if user has scrolled off bottom of visible tree
- ScrollOffset = Math.Max(0,newIdx - Bounds.Height);
+ ScrollOffset = Math.Max(0,(newIdx+1) - Bounds.Height);
}
}
@@ -211,21 +290,29 @@ namespace Terminal.Gui {
SetNeedsDisplay();
}
- private void Expand(object selectedObject)
+ ///
+ /// Expands the supplied object if it is contained in the tree (either as a root object or as an exposed branch object)
+ ///
+ /// The object to expand
+ public void Expand(object toExpand)
{
- if(selectedObject == null)
- return;
-
- ObjectToBranch(selectedObject).IsExpanded = true;
+ if(toExpand == null)
+ return;
+
+ ObjectToBranch(toExpand)?.Expand();
SetNeedsDisplay();
}
- private void Collapse(object selectedObject)
+ ///
+ /// Collapses the supplied object if it is currently expanded
+ ///
+ /// The object to collapse
+ public void Collapse(object toCollapse)
{
- if(selectedObject == null)
- return;
+ if(toCollapse == null)
+ return;
- ObjectToBranch(selectedObject).IsExpanded = false;
+ ObjectToBranch(toCollapse)?.Collapse();
SetNeedsDisplay();
}
@@ -233,7 +320,7 @@ namespace Terminal.Gui {
/// Returns the corresponding in the tree for . This will not work for objects hidden by their parent being collapsed
///
///
- ///
+ /// The branch for or null if it is not currently exposed in the tree
private Branch ObjectToBranch(object toFind)
{
return BuildLineMap().FirstOrDefault(o=>o.Model.Equals(toFind));
@@ -242,15 +329,36 @@ namespace Terminal.Gui {
class Branch
{
+ ///
+ /// True if the branch is expanded to reveal child branches
+ ///
public bool IsExpanded {get;set;}
- public Object Model{get;set;}
+
+ ///
+ /// The users object that is being displayed by this branch of the tree
+ ///
+ public object Model {get;set;}
+ ///
+ /// The depth of the current branch. Depth of 0 indicates root level branches
+ ///
public int Depth {get;set;} = 0;
+
+ ///
+ /// The children of the current branch. This is null until the first call to to avoid enumerating the entire underlying hierarchy
+ ///
public Dictionary ChildBranches {get;set;}
+
private TreeView tree;
- public Branch(TreeView tree,Branch parentBranchIfAny,Object model)
+ ///
+ /// Declares a new branch of in which the users object is presented
+ ///
+ /// The UI control in which the branch resides
+ /// Pass null for root level branches, otherwise pass the parent
+ /// The user's object that should be displayed
+ public Branch(TreeView tree,Branch parentBranchIfAny,object model)
{
this.tree = tree;
this.Model = model;
@@ -292,7 +400,11 @@ namespace Terminal.Gui {
driver.AddStr(representation.PadRight(availableWidth));
}
- char GetExpandableIcon()
+ ///
+ /// Returns an appropriate symbol for displaying next to the string representation of the object to indicate whether it or not (or it is a leaf)
+ ///
+ ///
+ public char GetExpandableIcon()
{
if(IsExpanded)
return tree.ExpandedSymbol;
@@ -302,6 +414,25 @@ namespace Terminal.Gui {
return ChildBranches.Any() ? tree.ExpandableSymbol : tree.LeafSymbol;
}
+
+ ///
+ /// Expands the current branch if possible
+ ///
+ public void Expand()
+ {
+ if(ChildBranches == null) {
+ FetchChildren();
+ }
+
+ if (ChildBranches.Any ()) {
+ IsExpanded = true;
+ }
+ }
+
+ internal void Collapse ()
+ {
+ IsExpanded = false;
+ }
}
///
@@ -310,4 +441,11 @@ namespace Terminal.Gui {
/// 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
+ ///
+ ///
+ ///
+ public delegate string AspectGetterDelegate(object model);
}
\ No newline at end of file
diff --git a/UICatalog/Scenarios/TreeViewFileSystem.cs b/UICatalog/Scenarios/TreeViewFileSystem.cs
index 47a8b93f1..0f2cbbb41 100644
--- a/UICatalog/Scenarios/TreeViewFileSystem.cs
+++ b/UICatalog/Scenarios/TreeViewFileSystem.cs
@@ -6,7 +6,7 @@ using System.Text;
using Terminal.Gui;
namespace UICatalog.Scenarios {
- [ScenarioMetadata (Name: "TreeViewFileSystem", Description: "A Terminal.Gui tree view file system explorer")]
+ [ScenarioMetadata (Name: "TreeViewFileSystem", Description: "Hierarchical file system explorer based on TreeView")]
[ScenarioCategory ("Controls")]
[ScenarioCategory ("Dialogs")]
[ScenarioCategory ("Text")]
@@ -31,6 +31,9 @@ namespace UICatalog.Scenarios {
Top.Add (menu);
var statusBar = new StatusBar (new StatusItem [] {
+ new StatusItem(Key.F2, "~F2~ Add Root Drives", () => AddRootDrives()),
+ new StatusItem(Key.F3, "~F3~ Remove Root Object", () => RemoveRoot()),
+ new StatusItem(Key.F4, "~F4~ Clear Objects", () => ClearObjects()),
new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()),
});
Top.Add (statusBar);
@@ -51,31 +54,59 @@ namespace UICatalog.Scenarios {
return;
}
+ // Determines how to compute children of any given branch
_treeView.ChildrenGetter = GetChildren;
- _treeView.AddObject(new DirectoryInfo(root));
-
+
+ // Determines how to represent objects as strings on the screen
+ _treeView.AspectGetter = AspectGetter;
+
Win.Add (_treeView);
}
- private IEnumerable GetChildren(object model)
- {
- // If it is a directory it's children are all contained files and dirs
- if(model is DirectoryInfo d) {
- try {
- return d.GetDirectories().Cast().Union(d.GetFileSystemInfos());
- }
- catch(SystemException ex) {
- return new []{ex};
+ private void ClearObjects()
+ {
+ _treeView.ClearObjects();
+ }
+ private void AddRootDrives()
+ {
+ _treeView.AddObjects(DriveInfo.GetDrives().Select(d=>d.RootDirectory));
+ }
+ private void RemoveRoot()
+ {
+ if(_treeView.SelectedObject == null)
+ MessageBox.ErrorQuery(10,5,"Error","No object selected","ok");
+ else {
+ _treeView.Remove(_treeView.SelectedObject);
}
}
- return new object[0];
- }
+ private IEnumerable GetChildren(object model)
+ {
+ // If it is a directory it's children are all contained files and dirs
+ if(model is DirectoryInfo d) {
+ try {
+ return d.GetDirectories().Cast().Union(d.GetFileSystemInfos());
+ }
+ catch(SystemException ex) {
+ return new []{ex};
+ }
+ }
- private void Quit ()
+ return new object[0];
+ }
+ private string AspectGetter(object model)
+ {
+ if(model is DirectoryInfo d)
+ return d.Name;
+ if(model is FileInfo f)
+ return f.Name;
+
+ return model.ToString();
+ }
+
+ private void Quit ()
{
Application.RequestStop ();
}
-
}
}