/// <summary> /// Expands a node recursively till all child/grandchild nodes are expanded. /// All its parent nodes up to root must already be expanded. /// Otherwise there is no effect. /// </summary> /// <param name="data">The data object that the target node contains</param> public void ExpandItemRecursive(object data) { int index; TreeGridViewItemInfo itemInfo = this.FindItemInfoForData(data, out index); ExpandItemRecursiveInternal(itemInfo, index); }
/// <summary> /// Collapse a node containing the given data. /// All its parent nodes up to root must already be expanded, including itself. /// Otherwise there is no effect. /// </summary> /// <param name="data">The data object that the target node contains</param> public void CollapseItem(object data) { // Collapse a node is a simple operation as to remove any following nodes whose level // is bigger than this one, until a node with equal or smaller level value is reached. // in the process if a removed node was expanded, we keep it in orphanExpandedItemsStore int index; TreeGridViewItemInfo itemInfo = this.FindItemInfoForData(data, out index); bool focusRemoved = false; if (itemInfo != null && itemInfo.IsExpanded) { int n = index + 1; int count = 0; while (n < this.itemInfoFlatList.Count) { var childInfo = this.itemInfoFlatList[n]; n++; if (childInfo.Level > itemInfo.Level) { count++; // before remove it, check if its selected or focused on if (base.SelectedItem == childInfo) { // selected item is removed, select the parent item instead base.SelectedItem = itemInfo; } if (this.focusItemInfo == childInfo) { this.focusItemInfo = null; focusRemoved = true; } if (childInfo.IsExpanded) { this.orphanExpandedItemsStore.Add(childInfo.Data); } } else { // equal or smaller Level value reached, no more child nodes break; } } if (count > 0) { itemInfo.IsExpanded = false; if (focusRemoved) { this.restoreFocusItemInfo = itemInfo; } else { this.restoreFocusItemInfo = this.focusItemInfo; } this.itemInfoFlatList.RemoveRange(index + 1, count); } } }
/// <summary> /// Clean up the item container /// </summary> /// <param name="element">The TreeGridViewItem to be discarded</param> /// <param name="item">The associated data object (TreeGridViewItemInfo)</param> protected override void ClearContainerForItemOverride(DependencyObject element, object item) { TreeGridViewItemInfo info = item as TreeGridViewItemInfo; TreeGridViewItem tgvi = element as TreeGridViewItem; tgvi.ParentView = null; base.ClearContainerForItemOverride(element, item); }
public void ScrollItemIntoView(object data) { int index; // Consider optimizing this for large lists... TreeGridViewItemInfo info = this.FindItemInfoForData(data, out index); if (info != null) { this.ScrollIntoView(info); } }
void ExpandFully() { if (this.ParentView != null) { TreeGridViewItemInfo info = this.DataContext as TreeGridViewItemInfo; if (info != null) { this.ParentView.ExpandItemRecursive(info.Data); this.IsExpanded = true; } } }
void ExpandItemRecursiveInternal(TreeGridViewItemInfo itemInfo, int index) { // Expand a node recursively till the whole subtree under it are expanded // this occurs when user press '*' on a node // handling should be careful since the subtree may already be partially expanded int partialExpandedCount = 0; if (itemInfo != null) { var itemsToInsert = new List <TreeGridViewItemInfo>(); if (!itemInfo.IsExpanded) { this.GetChildItemInfoRecursive(itemInfo, itemsToInsert); partialExpandedCount = 0; } else { int x = index + 1; while (x < this.itemInfoFlatList.Count) { var childInfo = this.itemInfoFlatList[x]; x++; if (childInfo.Level > itemInfo.Level) { partialExpandedCount++; itemsToInsert.Add(childInfo); if (!childInfo.IsExpanded) { this.GetChildItemInfoRecursive(childInfo, itemsToInsert); } } else { break; } } } if (partialExpandedCount < itemsToInsert.Count) { // save focus this.restoreFocusItemInfo = this.focusItemInfo; // first collapse the item (in case it is partially expanded already) if (partialExpandedCount > 0) { this.itemInfoFlatList.RemoveRange(index + 1, partialExpandedCount); } // insert the whole sub tree this.itemInfoFlatList.InsertRange(index + 1, itemsToInsert); } } }
// find parent item of specified item; // look back to find the first item whose Level value is smaller than this one private TreeGridViewItemInfo GetParentItemInfo(TreeGridViewItemInfo childInfo) { int childIndex = this.itemInfoFlatList.IndexOf(childInfo); if (childIndex >= 0 && childIndex < this.itemInfoFlatList.Count) { for (int i = childIndex - 1; i >= 0; i--) { var parentInfo = this.itemInfoFlatList[i]; if (parentInfo.Level < childInfo.Level) { return(parentInfo); } } } return(null); }
/// <summary> /// Prepare the item container. /// This is after ItemContainerGenerator created a TreeGridViewItem, and we have a chance to /// do some intialization work /// </summary> /// <param name="element">The TreeGridViewItem that is newly created (or recycled)</param> /// <param name="item">The data object associated (a TreeGridViewItemInfo object)</param> protected override void PrepareContainerForItemOverride(DependencyObject element, object item) { TreeGridViewItemInfo info = item as TreeGridViewItemInfo; TreeGridViewItem container = element as TreeGridViewItem; if (info != null) { container.ParentView = this; // initialize necessary properties container.Level = info.Level; container.IsExpanded = info.IsExpanded; container.HasChildren = this.DataHasChildren(info.Data); base.PrepareContainerForItemOverride(element, info.Data); } }
private void GetChildAndOrphanItemInfo(TreeGridViewItemInfo itemInfo, List <TreeGridViewItemInfo> appendList) { List <TreeGridViewItemInfo> childrenInfo = this.CreateChildItemInfoCollection(itemInfo); if (childrenInfo != null && childrenInfo.Count > 0) { itemInfo.IsExpanded = true; foreach (var c in childrenInfo) { appendList.Add(c); if (this.orphanExpandedItemsStore.Contains(c.Data)) { this.orphanExpandedItemsStore.Remove(c.Data); this.GetChildAndOrphanItemInfo(c, appendList); } } } }
private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e) { if (this.ItemContainerGenerator.Status == System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated) { if (this.restoreFocusItemInfo != null) { TreeGridViewItem tgvi = this.ItemContainerGenerator.ContainerFromItem(this.restoreFocusItemInfo) as TreeGridViewItem; if (tgvi != null) { if (this.IsFocused) { tgvi.Focus(); this.restoreFocusItemInfo = null; } } } } }
/// <summary> /// Move focus to parent item /// </summary> /// <param name="childItem">The currently focused item</param> /// <returns>true if succeeds; otherwise false</returns> internal bool FocusToParentItem(TreeGridViewItem childItem) { TreeGridViewItemInfo childInfo = this.ItemContainerGenerator.ItemFromContainer(childItem) as TreeGridViewItemInfo; if (childInfo != null) { var parentInfo = this.GetParentItemInfo(childInfo); if (parentInfo != null) { this.ScrollIntoView(parentInfo); TreeGridViewItem parentContainer = this.ItemContainerGenerator.ContainerFromItem(parentInfo) as TreeGridViewItem; if (parentContainer != null) { return(parentContainer.Focus()); } } } return(false); }
/// <summary> /// Expands a node containing the given data. /// All its parent/grandparent nodes up to root must already be expanded. /// Otherwise there is no effect. /// </summary> /// <param name="data">The data object that the target node contains</param> /// <returns>true if the item is already expanded, or the expand succeeds</returns> public bool ExpandItem(object data) { /* * additionally handles a special scenario: * Assume a tree like this (-> denotes parent-child relationship): * A(expanded) -> B(expanded) -> C * * now user collapse A, all child/grandchild nodes (including B and C) are removed from the flat list; * later user expands A again, B is created (but default collapsed) and * inserted after A into the list, but user expects B to be expanded as it was. * So we need to furthur expand B as well. * orphanExpandedItems is used to remember nodes like B (expanded before removed), * whenever an item is expanded, we'll check in to orphanExpandedItems to see if any * of the child items need further expansion, and so on. * (as a matter of fact WPF TreeView internally does similar things.) */ int index; TreeGridViewItemInfo itemInfo = this.FindItemInfoForData(data, out index); if (itemInfo == null) { return(false); } if (itemInfo.IsExpanded) { return(true); } var itemsToInsert = new List <TreeGridViewItemInfo>(); this.GetChildAndOrphanItemInfo(itemInfo, itemsToInsert); if (itemsToInsert.Count > 0) { // save focus and restore later this.restoreFocusItemInfo = this.focusItemInfo; this.itemInfoFlatList.InsertRange(index + 1, itemsToInsert); } return(true); }
/// <summary> /// Select/unselect a node. /// All parent nodes up to root must already be expanded. /// Otherwise there is no effect. /// </summary> /// <param name="data">The data that the target node contains</param> /// <param name="selected">true: select the node; false: unselect the node</param> public void SelectItem(object data, bool selected) { int index; TreeGridViewItemInfo info = this.FindItemInfoForData(data, out index); if (info != null) { this.ScrollIntoView(info); this.Dispatcher.BeginInvoke( (Action)(() => { TreeGridViewItem container = this.ItemContainerGenerator.ContainerFromIndex(index) as TreeGridViewItem; if (container != null) { container.IsSelected = selected; } }), System.Windows.Threading.DispatcherPriority.Background); } }
// Get children items of an item; // The binding path is expected from TreeGridView.ItemsTemplate which should be a HierarchicalDataTemplate, // Use reflection to get the property value private List <TreeGridViewItemInfo> CreateChildItemInfoCollection(TreeGridViewItemInfo info) { List <TreeGridViewItemInfo> result = null; if (info == null || info.Data == null) { return(result); } IEnumerable childCollection = this.GetChildCollection(info.Data); if (childCollection != null) { foreach (var c in childCollection) { if (result == null) { result = new List <TreeGridViewItemInfo>(); } result.Add(new TreeGridViewItemInfo(c, info.Level + 1)); } } return(result); }
/// <summary> /// Called by an item to notify the view when it gets focus /// The view has to know the currently focused item /// </summary> /// <param name="childItem">the child item got focus</param> internal void NotifyItemGotFocus(TreeGridViewItem childItem) { this.focusItemInfo = this.ItemContainerGenerator.ItemFromContainer(childItem) as TreeGridViewItemInfo; // clear the pending focus restore since a new focus is set by user this.restoreFocusItemInfo = null; }