Code:
/ Dotnetfx_Vista_SP2 / Dotnetfx_Vista_SP2 / 8.0.50727.4016 / DEVDIV / depot / DevDiv / releases / Orcas / QFE / wpf / src / Framework / System / Windows / Controls / Primitives / TabPanel.cs / 1 / TabPanel.cs
//---------------------------------------------------------------------------- // // Copyright (C) Microsoft Corporation. All rights reserved. // //--------------------------------------------------------------------------- using System; using MS.Internal; using MS.Utility; using System.Collections; using System.ComponentModel; using System.Diagnostics; using System.Reflection; using System.Windows.Threading; using System.Windows.Media; using System.Windows.Input; namespace System.Windows.Controls.Primitives { ////// TabPanel is a Panel designed to handle the intricacies of laying out the tab buttons in a TabControl. Specically, it handles: /// Serving as an ItemsHost for TabItems within a TabControl /// Determining correct sizing and positioning for TabItems /// Handling the logic associated with MultiRow scenarios, namely: /// Calculating row breaks in a collection of TabItems /// Laying out TabItems in multiple rows based on those breaks /// Performing specific layout for a selected item to indicate selection, namely: /// Bringing the selected tab to the front, or, in other words, making the selected tab appear to be in front of other tabs. /// Increasing the size pre-layout size of a selected item (note that this is not a transform, but rather an increase in the size allotted to the element in which to perform layout). /// Bringing the selected tab to the front /// Exposing attached properties that allow TabItems to be styled based on their placement within the TabPanel. /// public class TabPanel : Panel { //------------------------------------------------------------------- // // Constructors // //------------------------------------------------------------------- #region Constructors ////// Default DependencyObject constructor /// ////// Automatic determination of current Dispatcher. Use alternative constructor /// that accepts a Dispatcher for best performance. /// public TabPanel() : base() { } static TabPanel() { KeyboardNavigation.TabNavigationProperty.OverrideMetadata(typeof(TabPanel), new FrameworkPropertyMetadata(KeyboardNavigationMode.Once)); KeyboardNavigation.DirectionalNavigationProperty.OverrideMetadata(typeof(TabPanel), new FrameworkPropertyMetadata(KeyboardNavigationMode.Cycle)); } #endregion //-------------------------------------------------------------------- // // Public Methods // //------------------------------------------------------------------- #region Public Methods #endregion //-------------------------------------------------------------------- // // Protected Methods // //-------------------------------------------------------------------- #region Protected Methods ////// Updates DesiredSize of the TabPanel. Called by parent UIElement. This is the first pass of layout. /// ////// TabPanel /// /// Constraint size is an "upper limit" that TabPanel should not exceed. ///TabPanel' desired size. protected override Size MeasureOverride(Size constraint) { Size contentSize = new Size(); Dock tabAlignment = TabStripPlacement; _numRows = 1; _numHeaders = 0; _rowHeight = 0; // For top and bottom placement the panel flow its children to calculate the number of rows and // desired vertical size if (tabAlignment == Dock.Top || tabAlignment == Dock.Bottom) { int numInCurrentRow = 0; double currentRowWidth = 0; double maxRowWidth = 0; foreach (UIElement child in InternalChildren) { if (child.Visibility == Visibility.Collapsed) continue; _numHeaders++; // Helper measures child, and deals with Min, Max, and base Width & Height properties. // Helper returns the size a child needs to take up (DesiredSize or property specified size). child.Measure(constraint); Size childSize = GetDesiredSizeWithoutMargin(child); if (_rowHeight < childSize.Height) _rowHeight = childSize.Height; if (currentRowWidth + childSize.Width > constraint.Width && numInCurrentRow > 0) { // If child does not fit in the current row - create a new row if (maxRowWidth < currentRowWidth) maxRowWidth = currentRowWidth; currentRowWidth = childSize.Width; numInCurrentRow = 1; _numRows++; } else { currentRowWidth += childSize.Width; numInCurrentRow++; } } if (maxRowWidth < currentRowWidth) maxRowWidth = currentRowWidth; contentSize.Height = _rowHeight * _numRows; // If we don't have constraint or content wisth is smaller than constraint width then size to content if (double.IsInfinity(contentSize.Width) || DoubleUtil.IsNaN(contentSize.Width) || maxRowWidth < constraint.Width) contentSize.Width = maxRowWidth; else contentSize.Width = constraint.Width; } else if (tabAlignment == Dock.Left || tabAlignment == Dock.Right) { foreach (UIElement child in InternalChildren) { if (child.Visibility == Visibility.Collapsed) continue; _numHeaders++; // Helper measures child, and deals with Min, Max, and base Width & Height properties. // Helper returns the size a child needs to take up (DesiredSize or property specified size). child.Measure(constraint); Size childSize = GetDesiredSizeWithoutMargin(child); if (contentSize.Width < childSize.Width) contentSize.Width = childSize.Width; contentSize.Height += childSize.Height; } } // Returns our minimum size & sets DesiredSize. return contentSize; } ////// TabPanel arranges each of its children. /// /// Size that TabPanel will assume to position children. protected override Size ArrangeOverride(Size arrangeSize) { Dock tabAlignment = TabStripPlacement; if (tabAlignment == Dock.Top || tabAlignment == Dock.Bottom) { ArrangeHorizontal(arrangeSize); } else if (tabAlignment == Dock.Left || tabAlignment == Dock.Right) { ArrangeVertical(arrangeSize); } return arrangeSize; } ////// Override of ///. /// Geometry to use as additional clip in case when element is larger then available space protected override Geometry GetLayoutClip(Size layoutSlotSize) { return null; } #endregion Protected Methods //------------------------------------------------------------------- // // Private Methods // //-------------------------------------------------------------------- #region Private Methods private Size GetDesiredSizeWithoutMargin(UIElement element) { Thickness margin = (Thickness)element.GetValue(MarginProperty); Size desiredSizeWithoutMargin = new Size(); desiredSizeWithoutMargin.Height = Math.Max(0d, element.DesiredSize.Height - margin.Top - margin.Bottom); desiredSizeWithoutMargin.Width = Math.Max(0d, element.DesiredSize.Width - margin.Left - margin.Right); return desiredSizeWithoutMargin; } private double[] GetHeadersSize() { double[] headerSize = new double[_numHeaders]; int childIndex = 0; foreach (UIElement child in InternalChildren) { if (child.Visibility == Visibility.Collapsed) continue; Size childSize = GetDesiredSizeWithoutMargin(child); headerSize[childIndex] = childSize.Width; childIndex++; } return headerSize; } private void ArrangeHorizontal(Size arrangeSize) { Dock tabAlignment = TabStripPlacement; bool isMultiRow = _numRows > 1; int activeRow = 0; int[] solution = new int[0]; Vector childOffset = new Vector(); double[] headerSize = GetHeadersSize(); // If we have multirows, then calculate the best header distribution if (isMultiRow) { solution = CalculateHeaderDistribution(arrangeSize.Width, headerSize); activeRow = GetActiveRow(solution); // TabPanel starts to layout children depend on activeRow which should be always on bottom (top) // The first row should start from Y = (_numRows - 1 - activeRow) * _rowHeight if (tabAlignment == Dock.Top) childOffset.Y = (_numRows - 1 - activeRow) * _rowHeight; if (tabAlignment == Dock.Bottom && activeRow != 0) childOffset.Y = (_numRows - activeRow) * _rowHeight; } int childIndex = 0; int separatorIndex = 0; foreach (UIElement child in InternalChildren) { if (child.Visibility == Visibility.Collapsed) continue; Thickness margin = (Thickness)child.GetValue(MarginProperty); double leftOffset = margin.Left; double rightOffset = margin.Right; double topOffset = margin.Top; double bottomOffset = margin.Bottom; bool lastHeaderInRow = isMultiRow && (separatorIndex < solution.Length && solution[separatorIndex] == childIndex || childIndex == _numHeaders - 1); //Length left, top, right, bottom; Size cellSize = new Size(headerSize[childIndex], _rowHeight); // Align the last header in the row; If headers are not aligned directional nav would not work correctly if (lastHeaderInRow) { cellSize.Width = arrangeSize.Width - childOffset.X; } child.Arrange(new Rect(childOffset.X, childOffset.Y, cellSize.Width, cellSize.Height)); Size childSize = cellSize; childSize.Height = Math.Max(0d, childSize.Height - topOffset - bottomOffset); childSize.Width = Math.Max(0d, childSize.Width - leftOffset - rightOffset); // Calculate the offset for the next child childOffset.X += cellSize.Width; if (lastHeaderInRow) { if ((separatorIndex == activeRow && tabAlignment == Dock.Top) || (separatorIndex == activeRow - 1 && tabAlignment == Dock.Bottom)) childOffset.Y = 0d; else childOffset.Y += _rowHeight; childOffset.X = 0d; separatorIndex++; } childIndex++; } } private void ArrangeVertical(Size arrangeSize) { double childOffsetY = 0d; foreach (UIElement child in InternalChildren) { if (child.Visibility != Visibility.Collapsed) { Size childSize = GetDesiredSizeWithoutMargin(child); child.Arrange(new Rect(0, childOffsetY, arrangeSize.Width, childSize.Height)); // Calculate the offset for the next child childOffsetY += childSize.Height; } } } // Returns the row which contain the child with IsSelected==true private int GetActiveRow(int[] solution) { int activeRow = 0; int childIndex = 0; if (solution.Length > 0) { foreach (UIElement child in InternalChildren) { if (child.Visibility == Visibility.Collapsed) continue; bool isActiveTab = (bool)child.GetValue(Selector.IsSelectedProperty); if (isActiveTab) { return activeRow; } if (activeRow < solution.Length && solution[activeRow] == childIndex) { activeRow++; } childIndex++; } } // If the is no selected element and aligment is Top - then the active row is the last row if (TabStripPlacement == Dock.Top) { activeRow = _numRows - 1; } return activeRow; } /* TabPanel layout calculation: After measure call we have: rowWidthLimit - width of the TabPanel Header[0..n-1] - headers headerWidth[0..n-1] - header width Calculated values: numSeparators - number of separators between numSeparators+1 rows rowWidth[0..numSeparators] - row width rowHeaderCount[0..numSeparators] - Row Count = number of headers on that row rowAverageGap[0..numSeparators] - Average Gap for the row i = (rowWidth - rowWidth[i])/rowHeaderCount[i] currentSolution[0..numSeparators-1] - separator currentSolution[i]=x means Header[x] and h[x+1] are separated with new line bestSolution[0..numSeparators-1] - keep the last Best Solution bestSolutionRowAverageGap - keep the last Best Solution Average Gap Between all separators distribution the best solution have minimum Average Gap - this is the amount of pixels added to the header (to justify) in the row How does it work: First we flow the headers to calculate the number of necessary rows (numSeparators+1). That means we need to insert numSeparators separators between n headers (numSeparatorsrowWidthLimit && numberOfHeadersInCurrentRow > 0) { // if we cannot add next header - flow to next row // Store current row before we go to the next rowWidth[currentRowIndex] = currentRowWidth; // Store the current row width rowHeaderCount[currentRowIndex] = numberOfHeadersInCurrentRow; // For each row we store the number os headers inside currentAverageGap = Math.Max(0d, (rowWidthLimit - currentRowWidth) / numberOfHeadersInCurrentRow); // The amout of width that should be added to justify the header rowAverageGap[currentRowIndex] = currentAverageGap; currentSolution[currentRowIndex] = index - 1; // Separator points to the last header in the row if (bestSolutionMaxRowAverageGap < currentAverageGap) // Remember the maximum of all currentAverageGap bestSolutionMaxRowAverageGap = currentAverageGap; // Iterate to next row currentRowIndex++; currentRowWidth = headerWidth[index]; // Accumulate header widths on the same row numberOfHeadersInCurrentRow = 1; } else { currentRowWidth += headerWidth[index]; // Accumulate header widths on the same row // Increase the number of headers only if they are not collapsed (width=0) if (headerWidth[index] != 0) numberOfHeadersInCurrentRow++; } } // If everithing fit in 1 row then exit (no separators needed) if (currentRowIndex == 0) return new int[0]; // Add the last row rowWidth[currentRowIndex] = currentRowWidth; rowHeaderCount[currentRowIndex] = numberOfHeadersInCurrentRow; currentAverageGap = (rowWidthLimit - currentRowWidth) / numberOfHeadersInCurrentRow; rowAverageGap[currentRowIndex] = currentAverageGap; if (bestSolutionMaxRowAverageGap < currentAverageGap) bestSolutionMaxRowAverageGap = currentAverageGap; currentSolution.CopyTo(bestSolution, 0); // Remember the first solution as initial bestSolution rowAverageGap.CopyTo(bestSolutionRowAverageGap, 0); // bestSolutionRowAverageGap is used in ArrangeOverride to calculate header sizes // Search for the best solution // The exit condition if when we cannot move header to the next row while (true) { // Find the row with maximum AverageGap int worstRowIndex = 0; // Keep the row index with maximum AverageGap double maxAG = 0; for (int i = 0; i < _numRows; i++) // for all rows { if (maxAG < rowAverageGap[i]) { maxAG = rowAverageGap[i]; worstRowIndex = i; } } // If we are on the first row - cannot move from previous if (worstRowIndex == 0) break; // From the row with maximum AverageGap we try to move a header from previous row int moveToRow = worstRowIndex; int moveFromRow = moveToRow - 1; int moveHeader = currentSolution[moveFromRow]; double movedHeaderWidth = headerWidth[moveHeader]; rowWidth[moveToRow] += movedHeaderWidth; // If the moved header cannot fit - exit. We have the best solution already. if (rowWidth[moveToRow] > rowWidthLimit) break; // If header is moved successfully to the worst row // we update the arrays keeping the row state currentSolution[moveFromRow]--; rowHeaderCount[moveToRow]++; rowWidth[moveFromRow] -= movedHeaderWidth; rowHeaderCount[moveFromRow]--; rowAverageGap[moveFromRow] = (rowWidthLimit - rowWidth[moveFromRow]) / rowHeaderCount[moveFromRow]; rowAverageGap[moveToRow] = (rowWidthLimit - rowWidth[moveToRow]) / rowHeaderCount[moveToRow]; // EvaluateSolution: // If the current solution is better than bestSolution - keep it in bestSolution maxAG = 0; for (int i = 0; i < _numRows; i++) // for all rows { if (maxAG < rowAverageGap[i]) { maxAG = rowAverageGap[i]; } } if (maxAG < bestSolutionMaxRowAverageGap) { bestSolutionMaxRowAverageGap = maxAG; currentSolution.CopyTo(bestSolution, 0); rowAverageGap.CopyTo(bestSolutionRowAverageGap, 0); } } // Each header size should be increased so headers in the row stretch to fit the row currentRowIndex = 0; for (int index = 0; index < numHeaders; index++) { headerWidth[index] += bestSolutionRowAverageGap[currentRowIndex]; if (currentRowIndex < numSeparators && bestSolution[currentRowIndex] == index) currentRowIndex++; } // Use the best solution bestSolution[0..numSeparators-1] to layout return bestSolution; } private Dock TabStripPlacement { get { Dock placement = Dock.Top; TabControl tc = TemplatedParent as TabControl; if (tc != null) placement = tc.TabStripPlacement; return placement; } } #endregion //------------------------------------------------------------------- // // Private Data // //------------------------------------------------------------------- #region Private data private int _numRows = 1; // Nubmer of row calculated in measure and used in arrange private int _numHeaders = 0; // Number of headers excluding the collapsed items private double _rowHeight = 0; // Maximum of all headers height #endregion } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved. //---------------------------------------------------------------------------- // // Copyright (C) Microsoft Corporation. All rights reserved. // //--------------------------------------------------------------------------- using System; using MS.Internal; using MS.Utility; using System.Collections; using System.ComponentModel; using System.Diagnostics; using System.Reflection; using System.Windows.Threading; using System.Windows.Media; using System.Windows.Input; namespace System.Windows.Controls.Primitives { /// /// TabPanel is a Panel designed to handle the intricacies of laying out the tab buttons in a TabControl. Specically, it handles: /// Serving as an ItemsHost for TabItems within a TabControl /// Determining correct sizing and positioning for TabItems /// Handling the logic associated with MultiRow scenarios, namely: /// Calculating row breaks in a collection of TabItems /// Laying out TabItems in multiple rows based on those breaks /// Performing specific layout for a selected item to indicate selection, namely: /// Bringing the selected tab to the front, or, in other words, making the selected tab appear to be in front of other tabs. /// Increasing the size pre-layout size of a selected item (note that this is not a transform, but rather an increase in the size allotted to the element in which to perform layout). /// Bringing the selected tab to the front /// Exposing attached properties that allow TabItems to be styled based on their placement within the TabPanel. /// public class TabPanel : Panel { //------------------------------------------------------------------- // // Constructors // //------------------------------------------------------------------- #region Constructors ////// Default DependencyObject constructor /// ////// Automatic determination of current Dispatcher. Use alternative constructor /// that accepts a Dispatcher for best performance. /// public TabPanel() : base() { } static TabPanel() { KeyboardNavigation.TabNavigationProperty.OverrideMetadata(typeof(TabPanel), new FrameworkPropertyMetadata(KeyboardNavigationMode.Once)); KeyboardNavigation.DirectionalNavigationProperty.OverrideMetadata(typeof(TabPanel), new FrameworkPropertyMetadata(KeyboardNavigationMode.Cycle)); } #endregion //-------------------------------------------------------------------- // // Public Methods // //------------------------------------------------------------------- #region Public Methods #endregion //-------------------------------------------------------------------- // // Protected Methods // //-------------------------------------------------------------------- #region Protected Methods ////// Updates DesiredSize of the TabPanel. Called by parent UIElement. This is the first pass of layout. /// ////// TabPanel /// /// Constraint size is an "upper limit" that TabPanel should not exceed. ///TabPanel' desired size. protected override Size MeasureOverride(Size constraint) { Size contentSize = new Size(); Dock tabAlignment = TabStripPlacement; _numRows = 1; _numHeaders = 0; _rowHeight = 0; // For top and bottom placement the panel flow its children to calculate the number of rows and // desired vertical size if (tabAlignment == Dock.Top || tabAlignment == Dock.Bottom) { int numInCurrentRow = 0; double currentRowWidth = 0; double maxRowWidth = 0; foreach (UIElement child in InternalChildren) { if (child.Visibility == Visibility.Collapsed) continue; _numHeaders++; // Helper measures child, and deals with Min, Max, and base Width & Height properties. // Helper returns the size a child needs to take up (DesiredSize or property specified size). child.Measure(constraint); Size childSize = GetDesiredSizeWithoutMargin(child); if (_rowHeight < childSize.Height) _rowHeight = childSize.Height; if (currentRowWidth + childSize.Width > constraint.Width && numInCurrentRow > 0) { // If child does not fit in the current row - create a new row if (maxRowWidth < currentRowWidth) maxRowWidth = currentRowWidth; currentRowWidth = childSize.Width; numInCurrentRow = 1; _numRows++; } else { currentRowWidth += childSize.Width; numInCurrentRow++; } } if (maxRowWidth < currentRowWidth) maxRowWidth = currentRowWidth; contentSize.Height = _rowHeight * _numRows; // If we don't have constraint or content wisth is smaller than constraint width then size to content if (double.IsInfinity(contentSize.Width) || DoubleUtil.IsNaN(contentSize.Width) || maxRowWidth < constraint.Width) contentSize.Width = maxRowWidth; else contentSize.Width = constraint.Width; } else if (tabAlignment == Dock.Left || tabAlignment == Dock.Right) { foreach (UIElement child in InternalChildren) { if (child.Visibility == Visibility.Collapsed) continue; _numHeaders++; // Helper measures child, and deals with Min, Max, and base Width & Height properties. // Helper returns the size a child needs to take up (DesiredSize or property specified size). child.Measure(constraint); Size childSize = GetDesiredSizeWithoutMargin(child); if (contentSize.Width < childSize.Width) contentSize.Width = childSize.Width; contentSize.Height += childSize.Height; } } // Returns our minimum size & sets DesiredSize. return contentSize; } ////// TabPanel arranges each of its children. /// /// Size that TabPanel will assume to position children. protected override Size ArrangeOverride(Size arrangeSize) { Dock tabAlignment = TabStripPlacement; if (tabAlignment == Dock.Top || tabAlignment == Dock.Bottom) { ArrangeHorizontal(arrangeSize); } else if (tabAlignment == Dock.Left || tabAlignment == Dock.Right) { ArrangeVertical(arrangeSize); } return arrangeSize; } ////// Override of ///. /// Geometry to use as additional clip in case when element is larger then available space protected override Geometry GetLayoutClip(Size layoutSlotSize) { return null; } #endregion Protected Methods //------------------------------------------------------------------- // // Private Methods // //-------------------------------------------------------------------- #region Private Methods private Size GetDesiredSizeWithoutMargin(UIElement element) { Thickness margin = (Thickness)element.GetValue(MarginProperty); Size desiredSizeWithoutMargin = new Size(); desiredSizeWithoutMargin.Height = Math.Max(0d, element.DesiredSize.Height - margin.Top - margin.Bottom); desiredSizeWithoutMargin.Width = Math.Max(0d, element.DesiredSize.Width - margin.Left - margin.Right); return desiredSizeWithoutMargin; } private double[] GetHeadersSize() { double[] headerSize = new double[_numHeaders]; int childIndex = 0; foreach (UIElement child in InternalChildren) { if (child.Visibility == Visibility.Collapsed) continue; Size childSize = GetDesiredSizeWithoutMargin(child); headerSize[childIndex] = childSize.Width; childIndex++; } return headerSize; } private void ArrangeHorizontal(Size arrangeSize) { Dock tabAlignment = TabStripPlacement; bool isMultiRow = _numRows > 1; int activeRow = 0; int[] solution = new int[0]; Vector childOffset = new Vector(); double[] headerSize = GetHeadersSize(); // If we have multirows, then calculate the best header distribution if (isMultiRow) { solution = CalculateHeaderDistribution(arrangeSize.Width, headerSize); activeRow = GetActiveRow(solution); // TabPanel starts to layout children depend on activeRow which should be always on bottom (top) // The first row should start from Y = (_numRows - 1 - activeRow) * _rowHeight if (tabAlignment == Dock.Top) childOffset.Y = (_numRows - 1 - activeRow) * _rowHeight; if (tabAlignment == Dock.Bottom && activeRow != 0) childOffset.Y = (_numRows - activeRow) * _rowHeight; } int childIndex = 0; int separatorIndex = 0; foreach (UIElement child in InternalChildren) { if (child.Visibility == Visibility.Collapsed) continue; Thickness margin = (Thickness)child.GetValue(MarginProperty); double leftOffset = margin.Left; double rightOffset = margin.Right; double topOffset = margin.Top; double bottomOffset = margin.Bottom; bool lastHeaderInRow = isMultiRow && (separatorIndex < solution.Length && solution[separatorIndex] == childIndex || childIndex == _numHeaders - 1); //Length left, top, right, bottom; Size cellSize = new Size(headerSize[childIndex], _rowHeight); // Align the last header in the row; If headers are not aligned directional nav would not work correctly if (lastHeaderInRow) { cellSize.Width = arrangeSize.Width - childOffset.X; } child.Arrange(new Rect(childOffset.X, childOffset.Y, cellSize.Width, cellSize.Height)); Size childSize = cellSize; childSize.Height = Math.Max(0d, childSize.Height - topOffset - bottomOffset); childSize.Width = Math.Max(0d, childSize.Width - leftOffset - rightOffset); // Calculate the offset for the next child childOffset.X += cellSize.Width; if (lastHeaderInRow) { if ((separatorIndex == activeRow && tabAlignment == Dock.Top) || (separatorIndex == activeRow - 1 && tabAlignment == Dock.Bottom)) childOffset.Y = 0d; else childOffset.Y += _rowHeight; childOffset.X = 0d; separatorIndex++; } childIndex++; } } private void ArrangeVertical(Size arrangeSize) { double childOffsetY = 0d; foreach (UIElement child in InternalChildren) { if (child.Visibility != Visibility.Collapsed) { Size childSize = GetDesiredSizeWithoutMargin(child); child.Arrange(new Rect(0, childOffsetY, arrangeSize.Width, childSize.Height)); // Calculate the offset for the next child childOffsetY += childSize.Height; } } } // Returns the row which contain the child with IsSelected==true private int GetActiveRow(int[] solution) { int activeRow = 0; int childIndex = 0; if (solution.Length > 0) { foreach (UIElement child in InternalChildren) { if (child.Visibility == Visibility.Collapsed) continue; bool isActiveTab = (bool)child.GetValue(Selector.IsSelectedProperty); if (isActiveTab) { return activeRow; } if (activeRow < solution.Length && solution[activeRow] == childIndex) { activeRow++; } childIndex++; } } // If the is no selected element and aligment is Top - then the active row is the last row if (TabStripPlacement == Dock.Top) { activeRow = _numRows - 1; } return activeRow; } /* TabPanel layout calculation: After measure call we have: rowWidthLimit - width of the TabPanel Header[0..n-1] - headers headerWidth[0..n-1] - header width Calculated values: numSeparators - number of separators between numSeparators+1 rows rowWidth[0..numSeparators] - row width rowHeaderCount[0..numSeparators] - Row Count = number of headers on that row rowAverageGap[0..numSeparators] - Average Gap for the row i = (rowWidth - rowWidth[i])/rowHeaderCount[i] currentSolution[0..numSeparators-1] - separator currentSolution[i]=x means Header[x] and h[x+1] are separated with new line bestSolution[0..numSeparators-1] - keep the last Best Solution bestSolutionRowAverageGap - keep the last Best Solution Average Gap Between all separators distribution the best solution have minimum Average Gap - this is the amount of pixels added to the header (to justify) in the row How does it work: First we flow the headers to calculate the number of necessary rows (numSeparators+1). That means we need to insert numSeparators separators between n headers (numSeparatorsrowWidthLimit && numberOfHeadersInCurrentRow > 0) { // if we cannot add next header - flow to next row // Store current row before we go to the next rowWidth[currentRowIndex] = currentRowWidth; // Store the current row width rowHeaderCount[currentRowIndex] = numberOfHeadersInCurrentRow; // For each row we store the number os headers inside currentAverageGap = Math.Max(0d, (rowWidthLimit - currentRowWidth) / numberOfHeadersInCurrentRow); // The amout of width that should be added to justify the header rowAverageGap[currentRowIndex] = currentAverageGap; currentSolution[currentRowIndex] = index - 1; // Separator points to the last header in the row if (bestSolutionMaxRowAverageGap < currentAverageGap) // Remember the maximum of all currentAverageGap bestSolutionMaxRowAverageGap = currentAverageGap; // Iterate to next row currentRowIndex++; currentRowWidth = headerWidth[index]; // Accumulate header widths on the same row numberOfHeadersInCurrentRow = 1; } else { currentRowWidth += headerWidth[index]; // Accumulate header widths on the same row // Increase the number of headers only if they are not collapsed (width=0) if (headerWidth[index] != 0) numberOfHeadersInCurrentRow++; } } // If everithing fit in 1 row then exit (no separators needed) if (currentRowIndex == 0) return new int[0]; // Add the last row rowWidth[currentRowIndex] = currentRowWidth; rowHeaderCount[currentRowIndex] = numberOfHeadersInCurrentRow; currentAverageGap = (rowWidthLimit - currentRowWidth) / numberOfHeadersInCurrentRow; rowAverageGap[currentRowIndex] = currentAverageGap; if (bestSolutionMaxRowAverageGap < currentAverageGap) bestSolutionMaxRowAverageGap = currentAverageGap; currentSolution.CopyTo(bestSolution, 0); // Remember the first solution as initial bestSolution rowAverageGap.CopyTo(bestSolutionRowAverageGap, 0); // bestSolutionRowAverageGap is used in ArrangeOverride to calculate header sizes // Search for the best solution // The exit condition if when we cannot move header to the next row while (true) { // Find the row with maximum AverageGap int worstRowIndex = 0; // Keep the row index with maximum AverageGap double maxAG = 0; for (int i = 0; i < _numRows; i++) // for all rows { if (maxAG < rowAverageGap[i]) { maxAG = rowAverageGap[i]; worstRowIndex = i; } } // If we are on the first row - cannot move from previous if (worstRowIndex == 0) break; // From the row with maximum AverageGap we try to move a header from previous row int moveToRow = worstRowIndex; int moveFromRow = moveToRow - 1; int moveHeader = currentSolution[moveFromRow]; double movedHeaderWidth = headerWidth[moveHeader]; rowWidth[moveToRow] += movedHeaderWidth; // If the moved header cannot fit - exit. We have the best solution already. if (rowWidth[moveToRow] > rowWidthLimit) break; // If header is moved successfully to the worst row // we update the arrays keeping the row state currentSolution[moveFromRow]--; rowHeaderCount[moveToRow]++; rowWidth[moveFromRow] -= movedHeaderWidth; rowHeaderCount[moveFromRow]--; rowAverageGap[moveFromRow] = (rowWidthLimit - rowWidth[moveFromRow]) / rowHeaderCount[moveFromRow]; rowAverageGap[moveToRow] = (rowWidthLimit - rowWidth[moveToRow]) / rowHeaderCount[moveToRow]; // EvaluateSolution: // If the current solution is better than bestSolution - keep it in bestSolution maxAG = 0; for (int i = 0; i < _numRows; i++) // for all rows { if (maxAG < rowAverageGap[i]) { maxAG = rowAverageGap[i]; } } if (maxAG < bestSolutionMaxRowAverageGap) { bestSolutionMaxRowAverageGap = maxAG; currentSolution.CopyTo(bestSolution, 0); rowAverageGap.CopyTo(bestSolutionRowAverageGap, 0); } } // Each header size should be increased so headers in the row stretch to fit the row currentRowIndex = 0; for (int index = 0; index < numHeaders; index++) { headerWidth[index] += bestSolutionRowAverageGap[currentRowIndex]; if (currentRowIndex < numSeparators && bestSolution[currentRowIndex] == index) currentRowIndex++; } // Use the best solution bestSolution[0..numSeparators-1] to layout return bestSolution; } private Dock TabStripPlacement { get { Dock placement = Dock.Top; TabControl tc = TemplatedParent as TabControl; if (tc != null) placement = tc.TabStripPlacement; return placement; } } #endregion //------------------------------------------------------------------- // // Private Data // //------------------------------------------------------------------- #region Private data private int _numRows = 1; // Nubmer of row calculated in measure and used in arrange private int _numHeaders = 0; // Number of headers excluding the collapsed items private double _rowHeight = 0; // Maximum of all headers height #endregion } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu
This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- TemplateKeyConverter.cs
- AttributeCollection.cs
- SharedPersonalizationStateInfo.cs
- UIElement.cs
- ConfigurationSettings.cs
- EpmSyndicationContentSerializer.cs
- BitmapEncoder.cs
- BitConverter.cs
- XmlCollation.cs
- WeakEventTable.cs
- SettingsBindableAttribute.cs
- LogLogRecordHeader.cs
- PublisherIdentityPermission.cs
- ImmutablePropertyDescriptorGridEntry.cs
- ConfigXmlCDataSection.cs
- DataContractSerializer.cs
- MarginsConverter.cs
- OperationFormatUse.cs
- Array.cs
- SingleSelectRootGridEntry.cs
- HttpBrowserCapabilitiesBase.cs
- ParseChildrenAsPropertiesAttribute.cs
- InkCanvasSelectionAdorner.cs
- _ConnectOverlappedAsyncResult.cs
- LinkButton.cs
- PolicyLevel.cs
- complextypematerializer.cs
- EmptyQuery.cs
- DuplicateDetector.cs
- PageCache.cs
- XmlHelper.cs
- ResourceKey.cs
- AddValidationError.cs
- AppSettingsExpressionEditor.cs
- ConfigurationElementCollection.cs
- NetStream.cs
- XmlQueryTypeFactory.cs
- ColorKeyFrameCollection.cs
- FamilyMapCollection.cs
- XmlSchemaAnyAttribute.cs
- DataGridRow.cs
- MiniModule.cs
- GetPageNumberCompletedEventArgs.cs
- ListComponentEditorPage.cs
- WorkflowServiceNamespace.cs
- AdornerHitTestResult.cs
- Padding.cs
- ProgressPage.cs
- ResourceExpressionBuilder.cs
- WebDescriptionAttribute.cs
- ViewBox.cs
- DockingAttribute.cs
- _LazyAsyncResult.cs
- EntityDataSourceChangingEventArgs.cs
- DbProviderServices.cs
- XmlAnyElementAttribute.cs
- cookiecontainer.cs
- OleDbTransaction.cs
- TaiwanCalendar.cs
- GridViewHeaderRowPresenterAutomationPeer.cs
- DisplayClaim.cs
- SequentialActivityDesigner.cs
- StopStoryboard.cs
- ImageIndexConverter.cs
- TiffBitmapDecoder.cs
- RegistrySecurity.cs
- MsmqReceiveParameters.cs
- XmlWrappingReader.cs
- ProfileParameter.cs
- PointHitTestParameters.cs
- CodeCompileUnit.cs
- ParserStreamGeometryContext.cs
- SettingsPropertyWrongTypeException.cs
- Int64Storage.cs
- FileInfo.cs
- ConfigurationSectionGroupCollection.cs
- Property.cs
- ListViewInsertionMark.cs
- ImageInfo.cs
- CdpEqualityComparer.cs
- XmlDigitalSignatureProcessor.cs
- FlowDocumentPaginator.cs
- WebPart.cs
- DataSourceConverter.cs
- HttpListenerPrefixCollection.cs
- FrameworkTemplate.cs
- DataGridColumnStyleMappingNameEditor.cs
- StringExpressionSet.cs
- ListViewDeletedEventArgs.cs
- StateWorkerRequest.cs
- CompareValidator.cs
- PseudoWebRequest.cs
- IndexerNameAttribute.cs
- MaskPropertyEditor.cs
- OSFeature.cs
- Event.cs
- StyleCollection.cs
- FontStretches.cs
- HostingEnvironmentException.cs
- BuildResultCache.cs