/// <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);
 }