using System; using System.Collections; using System.ComponentModel; using System.Diagnostics; namespace BrightIdeasSoftware { /// /// A TreeDataSourceAdapter knows how to build a tree structure from a binding list. /// /// To build a tree public class TreeDataSourceAdapter : DataSourceAdapter { #region Life and death /// /// Create a data source adaptor that knows how to build a tree structure /// /// public TreeDataSourceAdapter(DataTreeListView tlv) : base(tlv) { this.treeListView = tlv; this.treeListView.CanExpandGetter = delegate(object model) { return this.CalculateHasChildren(model); }; this.treeListView.ChildrenGetter = delegate(object model) { return this.CalculateChildren(model); }; } #endregion #region Properties /// /// Gets or sets the name of the property/column that uniquely identifies each row. /// /// /// /// The value contained by this column must be unique across all rows /// in the data source. Odd and unpredictable things will happen if two /// rows have the same id. /// /// Null cannot be a valid key value. /// public virtual string KeyAspectName { get { return keyAspectName; } set { if (keyAspectName == value) return; keyAspectName = value; this.keyMunger = new Munger(this.KeyAspectName); this.InitializeDataSource(); } } private string keyAspectName; /// /// Gets or sets the name of the property/column that contains the key of /// the parent of a row. /// /// /// /// The test condition for deciding if one row is the parent of another is functionally /// equivilent to this: /// /// Object.Equals(candidateParentRow[this.KeyAspectName], row[this.ParentKeyAspectName]) /// /// /// Unlike key value, parent keys can be null but a null parent key can only be used /// to identify root objects. /// public virtual string ParentKeyAspectName { get { return parentKeyAspectName; } set { if (parentKeyAspectName == value) return; parentKeyAspectName = value; this.parentKeyMunger = new Munger(this.ParentKeyAspectName); this.InitializeDataSource(); } } private string parentKeyAspectName; /// /// Gets or sets the value that identifies a row as a root object. /// When the ParentKey of a row equals the RootKeyValue, that row will /// be treated as root of the TreeListView. /// /// /// /// The test condition for deciding a root object is functionally /// equivilent to this: /// /// Object.Equals(candidateRow[this.ParentKeyAspectName], this.RootKeyValue) /// /// /// The RootKeyValue can be null. /// public virtual object RootKeyValue { get { return rootKeyValue; } set { if (Equals(rootKeyValue, value)) return; rootKeyValue = value; this.InitializeDataSource(); } } private object rootKeyValue; /// /// Gets or sets whether or not the key columns (id and parent id) should /// be shown to the user. /// /// This must be set before the DataSource is set. It has no effect /// afterwards. public virtual bool ShowKeyColumns { get { return showKeyColumns; } set { showKeyColumns = value; } } private bool showKeyColumns = true; #endregion #region Implementation properties /// /// Gets the DataTreeListView that is being managed /// protected DataTreeListView TreeListView { get { return treeListView; } } private readonly DataTreeListView treeListView; #endregion #region Implementation /// /// /// protected override void InitializeDataSource() { base.InitializeDataSource(); this.TreeListView.RebuildAll(true); } /// /// /// protected override void SetListContents() { this.TreeListView.Roots = this.CalculateRoots(); } /// /// /// /// /// protected override bool ShouldCreateColumn(PropertyDescriptor property) { // If the property is a key column, and we aren't supposed to show keys, don't show it if (!this.ShowKeyColumns && (property.Name == this.KeyAspectName || property.Name == this.ParentKeyAspectName)) return false; return base.ShouldCreateColumn(property); } /// /// /// /// protected override void HandleListChangedItemChanged(System.ComponentModel.ListChangedEventArgs e) { // If the id or the parent id of a row changes, we just rebuild everything. // We can't do anything more specific. We don't know what the previous values, so we can't // tell the previous parent to refresh itself. If the id itself has changed, things that used // to be children will no longer be children. Just rebuild everything. // It seems PropertyDescriptor is only filled in .NET 4 :( if (e.PropertyDescriptor != null && (e.PropertyDescriptor.Name == this.KeyAspectName || e.PropertyDescriptor.Name == this.ParentKeyAspectName)) this.InitializeDataSource(); else base.HandleListChangedItemChanged(e); } /// /// /// /// protected override void ChangePosition(int index) { // We can't use our base method directly, since the normal position management // doesn't know about our tree structure. They treat our dataset as a flat list // but we have a collapsable structure. This means that the 5'th row to them // may not even be visible to us // To display the n'th row, we have to make sure that all its ancestors // are expanded. Then we will be able to select it. object model = this.CurrencyManager.List[index]; object parent = this.CalculateParent(model); while (parent != null && !this.TreeListView.IsExpanded(parent)) { this.TreeListView.Expand(parent); parent = this.CalculateParent(parent); } base.ChangePosition(index); } private IEnumerable CalculateRoots() { foreach (object x in this.CurrencyManager.List) { object parentKey = this.GetParentValue(x); if (Object.Equals(this.RootKeyValue, parentKey)) yield return x; } } private bool CalculateHasChildren(object model) { object keyValue = this.GetKeyValue(model); if (keyValue == null) return false; foreach (object x in this.CurrencyManager.List) { object parentKey = this.GetParentValue(x); if (Object.Equals(keyValue, parentKey)) return true; } return false; } private IEnumerable CalculateChildren(object model) { object keyValue = this.GetKeyValue(model); if (keyValue != null) { foreach (object x in this.CurrencyManager.List) { object parentKey = this.GetParentValue(x); if (Object.Equals(keyValue, parentKey)) yield return x; } } } private object CalculateParent(object model) { object parentValue = this.GetParentValue(model); if (parentValue == null) return null; foreach (object x in this.CurrencyManager.List) { object key = this.GetKeyValue(x); if (Object.Equals(parentValue, key)) return x; } return null; } private object GetKeyValue(object model) { return this.keyMunger == null ? null : this.keyMunger.GetValue(model); } private object GetParentValue(object model) { return this.parentKeyMunger == null ? null : this.parentKeyMunger.GetValue(model); } #endregion private Munger keyMunger; private Munger parentKeyMunger; } }