One of the things that needs editing is the menu: a simple Key | ParentKey | MenuKey | Name | Url table (MenuKey because I have more than one menu). I bound a TreeView control to this menu using a set of classes I wrote a while ago to bind a TreeView to a folder structure. I decided to share my code, because it's considerably easier to use than anything else I've been able to find online.
I've based my code on the following article: http://www.codeproject.com/Articles/19639/Implementing-IHierarchy-Support-Into-Your-Custom-C . I was able to get my menu items bound to my TreeView using this method. It does not seem reasonable to have to write the same type of code every time I want to bind to a custom data type though. What's more; I'm adding a collection class, simply to implement the IHierarchicalEnumerable interface. The methods I'm simply patching through to existing methods and properties. I'm a programmer, not a plumber.
So here is a generic implementation of IHierarchyData and IHierarchicalEnumerable (download link at the end):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.UI;
namespace Tabeoka
{
/// <summary>
/// Utility class for creating a hierarchical data source
/// </summary>
public static class HierarchyData
{
/// <summary>
/// Returns a hierarchical data list containing only the root item
/// </summary>
/// <typeparam name="T">The data type used for building up the hierarchical data source</typeparam>
/// <param name="root">The root data item</param>
/// <param name="childSelector">A delegate that returns the child items of the item passed as an argument</param>
/// <param name="parentSelector">A delegate that returns the parent item of the item passed as an argument, or null if root</param>
/// <param name="pathSelector">A delegate that is called for the path of the current item as a string</param>
/// <returns></returns>
public static HierarchyDataList<T> GetData<T>(T root, Func<T, IEnumerable<T>> childSelector, Func<T, T> parentSelector, Func<T, string> pathSelector) where T : class
{
if (root == null)
throw new ArgumentNullException("root");
if (childSelector == null)
throw new ArgumentNullException("childSelector");
if (parentSelector == null)
throw new ArgumentNullException("parentSelector");
if (pathSelector == null)
throw new ArgumentNullException("pathSelector");
return new HierarchyDataList<T>(
new List<T> { root },
childSelector,
parentSelector,
pathSelector
);
}
/// <summary>
/// Wrapper class for the data type that implements IHierarchyData
/// </summary>
/// <typeparam name="T">The underlying data type</typeparam>
public class HierarchyDataItem<T> : IHierarchyData where T : class
{
internal T DataItem { get; set; }
internal Func<T, IEnumerable<T>> ChildSelector { get; set; }
internal Func<T, T> ParentSelector { get; set; }
internal Func<T, string> PathSelector { get; set; }
internal HierarchyDataItem(
T dataItem,
Func<T, IEnumerable<T>> childSelector,
Func<T, T> parentSelector,
Func<T, string> pathSelector
)
{
DataItem = dataItem;
this.ChildSelector = childSelector;
this.ParentSelector = parentSelector;
this.PathSelector = pathSelector;
}
#region IHierarchyData Members
/// <summary>
/// Gets the child items wrapped in a IHierarchicalEnumerable
/// </summary>
/// <returns>A HierarchyDataList<T></returns>
public IHierarchicalEnumerable GetChildren()
{
return new HierarchyDataList<T>(
ChildSelector(DataItem),
this.ChildSelector,
this.ParentSelector,
this.PathSelector
);
}
/// <summary>
/// Gets the parent item, and wraps it in a HierarchyDataItem
/// </summary>
/// <returns>A HierarchyDataItem<T>, or null if not found</returns>
public IHierarchyData GetParent()
{
var parent = this.ParentSelector(DataItem);
if (parent != null)
return new HierarchyDataItem<T>(
parent,
this.ChildSelector,
this.ParentSelector,
this.PathSelector
);
return null;
}
/// <summary>
/// Checks if there are any child nodes, and returns true if there are
/// </summary>
public bool HasChildren
{
get
{
return this.ChildSelector(DataItem).Count() > 0;
}
}
/// <summary>
/// The underlying data object
/// </summary>
public object Item
{
get { return DataItem; }
}
/// <summary>
/// Is supposed to return the logical path according to the underlying data,
/// just calls the pathSelector.
/// </summary>
public string Path
{
get { return PathSelector(DataItem); }
}
/// <summary>
/// TypeOf(T)
/// </summary>
public string Type
{
get { return typeof(T).ToString(); }
}
#endregion
}
/// <summary>
/// A list of T that implements IHierarchicalEnumerable
/// </summary>
/// <typeparam name="T">The underlying data type</typeparam>
public class HierarchyDataList<T> : List<T>, IHierarchicalEnumerable where T : class
{
internal Func<T, IEnumerable<T>> ChildSelector { get; set; }
internal Func<T, T> ParentSelector { get; set; }
internal Func<T, string> PathSelector { get; set; }
internal HierarchyDataList(
IEnumerable<T> items,
Func<T, IEnumerable<T>> childSelector,
Func<T, T> parentSelector,
Func<T, string> pathSelector
)
: base(items)
{
this.ChildSelector = childSelector;
this.ParentSelector = parentSelector;
this.PathSelector = pathSelector;
}
#region IHierarchicalEnumerable Members
/// <summary>
/// Wraps the enumeratedItem object in a HierarchyDataItem
/// </summary>
/// <param name="enumeratedItem">The data item</param>
/// <returns>an instance of HierarchyDataItem<T></returns>
public IHierarchyData GetHierarchyData(object enumeratedItem)
{
return new HierarchyDataItem<T>(
enumeratedItem as T,
this.ChildSelector,
this.ParentSelector,
this.PathSelector);
}
#endregion
}
}
}
I'm wrapping all of it in a static class: the public static method allows me to infer the generic type parameters.
Here's how you use it:
// The root node for my folder structure
var root = Folders.GetRoot();
// Setting the delegates for getting
// children, parent and path value
var folderData = HierarchyData.GetData(
root,
d => d.SubFolders,
d => d.ParentFolder,
d => d.Name);
tree = new TreeView();
this.Controls.Add(tree);
if (!this.Page.IsPostBack)
{
// now just assign datasource and bindings
tree.DataSource = folderData;
tree.DataBindings.Add(new TreeNodeBinding()
{
TextField = "Name",
ValueField = "Key"
});
tree.DataBind();
}
And here's the download link: http://www.tabeoka.be/downloads/HierarchyData.zip .
I hope this helps someone, somewhere. I welcome comments and criticism.
Menno
No comments:
Post a Comment