/// <summary> /// Recursively expand the branch at the given location. /// </summary> /// <param name="row">The row coordinate.</param> /// <param name="column">The native column coordinate.</param> public void ExpandRecurse(int row, int column) { // Turn off the event temporarily. We'll do all of the tracking in this routine instead of // firing multiple events. myTree.ItemCountChanged -= OnItemCountChanged; try { if (myTree.IsExpandable(row, column)) { bool allowRecursion; int change; if (myTree.IsExpanded(row, column)) { allowRecursion = myTree.GetDescendantItemCount(row, column, false, false) > 0; change = 0; } else { var toggle = myTree.ToggleExpansion(row, column); change = toggle.Change; allowRecursion = toggle.AllowRecursion; if (change > 0) { if (!allowRecursion) { // Fire the event manually // UNDONE_NOW: The blank expansion is wrong OnToggleExpansion(myTree, row, column, change, 0, null, true); } } else if (change == 0) { if (GetStyleFlag(VTCStyleFlags.HasButtons)) { // Make sure the plus/minus changes InvalidateItem(row, column, NativeMethods.RedrawWindowFlags.Invalidate); } allowRecursion = false; } } if (allowRecursion) { var stateTracker = new ListBoxStateTracker(this); if (change != 0) { // UNDONE_NOW: The blank expansion is wrong. We need to pass real data here. stateTracker.ApplyChange(row, column, change, 0, myTree.VisibleItemCount, true); } DoExpandRecurse(row, column, stateTracker); BeginUpdate(); ItemCount = myTree.VisibleItemCount; stateTracker.Restore(this); var iTop = TopIndex; if (iTop > 0 && row < iTop) { TopIndex = row; } ScrollBarsAfterSetWidth(VirtualTreeConstant.NullIndex); EndUpdate(); } } } finally { myTree.ItemCountChanged += OnItemCountChanged; } }
private void DoExpandRecurse(int absIndex, int column, ListBoxStateTracker stateTracker) { if (absIndex != VirtualTreeConstant.NullIndex) { // Converted from a recursive function to iterative (TFS 235913). // The recursive function expanded the branches from top // to bottom. This iterative version expands from bottom to // top. Expanding a branch causes the absIndex of items below // the expansion to change. Expanding from bottom to top means // our remaining stack always contains absIndexes not affected // by any expansions. var remaining = new Stack<int>(256); remaining.Push(absIndex); while (remaining.Count > 0) { absIndex = remaining.Pop(); //This will always be called with an index corresponding //to an open, expanded list, so Index + 1 will always //be a valid Index. This returns the list for //descendants of this item. var branch = myTree.GetExpandedBranch(absIndex, column).Branch; if (0 != (branch.Features & BranchFeatures.Expansions)) { var itemCount = branch.VisibleItemCount; bool recurse; int relIndex; absIndex += myTree.GetSubItemCount(absIndex, column) + 1; for (relIndex = 0; relIndex < itemCount; ++relIndex, ++absIndex) { if (branch.IsExpandable(relIndex, column)) { if (myTree.IsExpanded(absIndex, column)) { recurse = true; } else { var toggle = myTree.ToggleExpansion(absIndex, column); recurse = toggle.AllowRecursion; // UNDONE_NOW: The blank expansion is wrong. ToggleExpansionAbsolute should // return this data. stateTracker.ApplyChange(absIndex, column, toggle.Change, 0, myTree.VisibleItemCount, true); } if (recurse) { // We used to recurse here. We now mark the branch for later expansion. remaining.Push(absIndex); } absIndex += myTree.GetDescendantItemCount(absIndex, column, true, false); } else { absIndex += myTree.GetSubItemCount(absIndex, column); } } } } } }
private void OnItemMoved(object sender, ItemMovedEventArgs e) { BeginUpdate(); var stateTracker = new ListBoxStateTracker(this); stateTracker.ApplyChange(ref e); stateTracker.Restore(this); EndUpdate(); // UNDONE: Do some ScrollWindowEx and ValidateRect optimizations here to avoid redrawing // the whole tree. Make sure the TopIndex doesn't change and that there aren't pending redraws // in optimized regions. This should be done along with the OnToggleExpansion optimizations }
private void OnToggleExpansion( int visibleItemCount, int absIndex, int column, int change, int blanksAboveChange, SubItemColumnAdjustmentCollection subItemChanges, bool isExpansionToggle) { var checkLeftVertScroll = LeftScrollBar; var beforeVertScroll = HasVerticalScrollBar; var colPerm = myColumnPermutation; var adjustAfter = absIndex; var adjustChange = change; var adjustBlanksAboveChange = blanksAboveChange; var totalChange = change; var fullRowChangeBegin = -1; if (subItemChanges != null) { SubItemColumnAdjustment columnAdjustment; var changeCount = subItemChanges.Count; var selIndex = CurrentIndex; int displayColumnDummy; var nativeSelectionColumn = -1; for (var i = 0; i < changeCount; ++i) { columnAdjustment = subItemChanges[i]; if (Math.Abs(totalChange) < Math.Abs(columnAdjustment.Change)) { totalChange = columnAdjustment.Change; } if (i == (changeCount - 1)) { if (selIndex > absIndex && selIndex <= (absIndex + columnAdjustment.ItemsBelowAnchor)) { if (nativeSelectionColumn == -1) { ResolveSelectionColumn(selIndex, out displayColumnDummy, out nativeSelectionColumn); } if (columnAdjustment.Column != nativeSelectionColumn) { adjustAfter = absIndex + columnAdjustment.ItemsBelowAnchor; adjustBlanksAboveChange = 0; } else { adjustChange = totalChange; } } fullRowChangeBegin = absIndex + Math.Abs(columnAdjustment.Change - change) + 1; } } } if (totalChange == 0) { OnDisplayDataChanged(VirtualTreeDisplayDataChanges.ItemButton, absIndex, column, 1); return; } var hWnd = Handle; NativeMethods.RECT pendingUpdateRect; var paintPending = NativeMethods.GetUpdateRect(hWnd, out pendingUpdateRect, false); BeginUpdate(); // We need the top index for both cases to determine if it has changed. // A top index change indicates that the whole window needs refreshing, // no change indicates we only need to update the portion below absIndex. var stateTracker = new ListBoxStateTracker(this); // The window's listbox doesn't allow a mechanism for inserting // items in a no-data listbox, so everything vital (selection, caret, // and top index) must be explicitly maintained. LB_DELETESTRING does // work (unlike LB_INSERTSTRING), but there are performance penalties // with multiple WM_DELETEITEM messages due to the callbacks to the parent // object, so we handle deletion in a similar fashion. // Adjust cached settings stateTracker.ApplyChange(adjustAfter, column, adjustChange, adjustBlanksAboveChange, visibleItemCount, isExpansionToggle); try { // Change the ItemCount. This clears the selection, scroll state, etc. Set RestoringSelection // flag so that we don't fire events for this, since we will restore it below. SetStateFlag(VTCStateFlags.RestoringSelection, true); ItemCount = visibleItemCount; } finally { SetStateFlag(VTCStateFlags.RestoringSelection, false); } // see if there was enough room for the expansion if (isExpansionToggle && change > 0) { var spaceNeeded = totalChange + blanksAboveChange - (myFullyVisibleCount - 1 - (absIndex - stateTracker.restoreTop)); if (spaceNeeded > 0) { var newCaret = stateTracker.restoreTop + spaceNeeded; if (newCaret > absIndex) { newCaret = absIndex; } stateTracker.restoreTop = newCaret; } if (absIndex < stateTracker.restoreTop) { stateTracker.restoreTop = absIndex; } } // Restore the previous state. Only do this if we're not in the middle of a list shuffle, // as in that case the shuffle will take care of this when it finishes. if (myTree != null && !myTree.ListShuffle) { stateTracker.Restore(this); } EndUpdate(); // UNDONE: If the scrollbar has appeared/disappeared when it is on the left, // then we want vertical gridlines to not change. Unfortunately, the listbox // automatically scrolls the window for us, so column 0 is correct, and the rest // are shifted by a scrollbar width. This means that we should be able to validate // large portions of column 0. var invalidatePending = false; var subsequentSubItems = false; // only perfom the optimization if we arent horizontally scrolled. (bug 38565) if (change != 0 && myXPos == 0) { // If the expanded item is below the top, and the top // hasn't moved, then we can get away with only repainting // a portion (possibly none) of the window. Make sure we // only attempt to Validate regions if there is no paint // event pending when this occurs. var afterVertScroll = HasVerticalScrollBar; if (absIndex >= stateTracker.startTop && (!checkLeftVertScroll || afterVertScroll == beforeVertScroll) && stateTracker.startTop == TopIndex) { NativeMethods.RECT itemRect; NativeMethods.RECT anchorRect; NativeMethods.RECT clientRect; var offsetAnchor = fullRowChangeBegin != -1; NativeMethods.SendMessage(hWnd, NativeMethods.LB_GETITEMRECT, absIndex, out anchorRect); itemRect = anchorRect; if (offsetAnchor) { // The full row change begin may be after the current number of items in the control, so // we can't use LB_GETITEMRECT to pick it up. Use an offset from absIndex instead. var offsetBy = (fullRowChangeBegin - absIndex) * itemRect.height; if (offsetBy != 0) { itemRect.bottom += offsetBy; itemRect.top += offsetBy; } if (HasHorizontalGridLines) { // Make sure the preceding gridline is redraw itemRect.top -= 1; } } NativeMethods.GetClientRect(hWnd, out clientRect); var restoreXPos = stateTracker.restoreXPos; if (anchorRect.bottom >= clientRect.top && anchorRect.top <= clientRect.bottom) { // The top of the client area doesn't change. // UNDONE: This can be further optimized with ScrollWindowEx, but this // is a pretty good painting optimization for a first go round. clientRect.bottom = itemRect.top - 1; if (clientRect.width >= restoreXPos) { // The scrollbar acrobatics above leave the upper right portion // of the window blank, adjust the rect accordingly clientRect.right -= restoreXPos; // TFS 166541: We used to adjust the clientRect for the scrollbar width // only. However, that did not work if the last column contained right // aligned content. So, we now adjust the clientRect for the entire last // column width (which includes the scrollbar width change). if (afterVertScroll != beforeVertScroll) { var numColumns = ColumnCount; var lastColumn = numColumns - 1; var columnRect = Rectangle.FromLTRB(clientRect.left, clientRect.top, clientRect.right, clientRect.bottom); LimitRectToColumn(lastColumn, ref columnRect); clientRect.right -= columnRect.Width; } NativeMethods.ValidateRect(hWnd, ref clientRect); invalidatePending = paintPending; } } else { // None of the client area changes NativeMethods.ValidateRect(hWnd, IntPtr.Zero); invalidatePending = paintPending; } } subsequentSubItems = true; } if (subItemChanges != null) { if (subsequentSubItems || stateTracker.startTop == TopIndex) { // Since the total item count did not change, we don't have to worry // about scrollbars coming/going here if (!subsequentSubItems) { NativeMethods.ValidateRect(hWnd, IntPtr.Zero); invalidatePending = paintPending; } SubItemColumnAdjustment columnAdjustment; var changeCount = subItemChanges.Count; for (var i = 0; i < changeCount; ++i) { columnAdjustment = subItemChanges[i]; var displayColumn = columnAdjustment.Column; if (colPerm != null) { displayColumn = colPerm.GetPermutedColumn(displayColumn); if (displayColumn == -1) { continue; } } var columnRect = GetItemRectangle(absIndex, displayColumn); var leftColumn = displayColumn; var rightColumn = displayColumn; if (colPerm != null) { var columnExpansion = colPerm.GetColumnExpansion(displayColumn, columnAdjustment.LastColumnOnRow); if (columnExpansion.Width > 1) { leftColumn = columnExpansion.LeftColumn; rightColumn = columnExpansion.RightColumn; } } else if (displayColumn == columnAdjustment.LastColumnOnRow) { var lastColumn = myMctree.ColumnCount - 1; if (displayColumn < lastColumn) { rightColumn = lastColumn; } } if (leftColumn != rightColumn) { int columnLeft; int columnWidth; GetColumnBounds(leftColumn, rightColumn, out columnLeft, out columnWidth); columnRect.Width = columnWidth; columnRect.X = columnLeft; } NativeMethods.RECT rect; // UNDONE: Use the Change and TrailingItems values to optimize this. // Easy for now: Just redraw the rest of the column var changeRows = columnAdjustment.ItemsBelowAnchor + Math.Abs(columnAdjustment.Change - change) + 1; if (changeRows > 0) { if (changeRows > 1) { columnRect.Inflate(0, columnRect.Height * (changeRows - 1)); } rect = new NativeMethods.RECT(columnRect); NativeMethods.InvalidateRect(hWnd, ref rect, false); } } } } if (invalidatePending) { NativeMethods.InvalidateRect(hWnd, ref pendingUpdateRect, false); } if (totalChange > 0) { ScrollBarsAfterExpand(absIndex, totalChange); } else if (totalChange < 0) { ScrollBarsAfterCollapse(absIndex, -totalChange); } if (VirtualTreeAccEvents.ShouldNotify(VirtualTreeAccEvents.eventObjectStateChange, this)) { VirtualTreeAccEvents.Notify( VirtualTreeAccEvents.eventObjectStateChange, absIndex, 0, this); } }
public ListBoxStateTrackerClass(VirtualTreeControl ctl) { Inner = new ListBoxStateTracker(ctl); }