Code:
/ Dotnetfx_Win7_3.5.1 / Dotnetfx_Win7_3.5.1 / 3.5.1 / DEVDIV / depot / DevDiv / releases / Orcas / NetFXw7 / 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 (numSeparators rowWidthLimit && 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 (numSeparators rowWidthLimit && 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
- MatrixCamera.cs
- StrokeDescriptor.cs
- StringConcat.cs
- Vector.cs
- StrongNamePublicKeyBlob.cs
- ProxyGenerationError.cs
- RecipientInfo.cs
- IntranetCredentialPolicy.cs
- EventLogQuery.cs
- OleDbRowUpdatedEvent.cs
- SQLMoneyStorage.cs
- DataRelationPropertyDescriptor.cs
- MetadataItem.cs
- FlowPosition.cs
- CodeChecksumPragma.cs
- ThreadNeutralSemaphore.cs
- AutomationPatternInfo.cs
- EventPrivateKey.cs
- RotateTransform.cs
- ResizeGrip.cs
- FlowLayoutPanel.cs
- ReceiveCompletedEventArgs.cs
- LogManagementAsyncResult.cs
- returneventsaver.cs
- XmlILModule.cs
- SQLByteStorage.cs
- LayoutEditorPart.cs
- TypeDescriptionProvider.cs
- JulianCalendar.cs
- CodeDOMUtility.cs
- XmlIterators.cs
- KeyboardNavigation.cs
- Queue.cs
- GeometryModel3D.cs
- TrailingSpaceComparer.cs
- ConstructorBuilder.cs
- IncrementalCompileAnalyzer.cs
- HtmlInputControl.cs
- DPTypeDescriptorContext.cs
- NamespaceInfo.cs
- DbProviderFactories.cs
- CompiledIdentityConstraint.cs
- ExpressionBinding.cs
- InheritanceRules.cs
- MultiView.cs
- EmptyReadOnlyDictionaryInternal.cs
- WindowInteropHelper.cs
- MemoryFailPoint.cs
- HelpFileFileNameEditor.cs
- FormViewPageEventArgs.cs
- ErrorFormatterPage.cs
- ServiceHostingEnvironmentSection.cs
- StringCollection.cs
- ToolStripDesigner.cs
- SqlCacheDependency.cs
- AnnotationDocumentPaginator.cs
- DataSvcMapFile.cs
- StylusDevice.cs
- DataGridViewCellStyleChangedEventArgs.cs
- _NetRes.cs
- XpsSerializationManager.cs
- FullTextState.cs
- HitTestWithPointDrawingContextWalker.cs
- CompressEmulationStream.cs
- IndexedGlyphRun.cs
- WebPartZoneCollection.cs
- EncryptionUtility.cs
- FontNamesConverter.cs
- BufferBuilder.cs
- SqlTopReducer.cs
- ThrowHelper.cs
- Activity.cs
- BrowserCapabilitiesCompiler.cs
- TreeWalkHelper.cs
- SyndicationSerializer.cs
- PassportIdentity.cs
- XmlnsDictionary.cs
- Timeline.cs
- RegisteredArrayDeclaration.cs
- DataPager.cs
- ImageCodecInfoPrivate.cs
- XPathDocumentNavigator.cs
- SQLMembershipProvider.cs
- CalendarDateRange.cs
- CustomPopupPlacement.cs
- Internal.cs
- ColorConvertedBitmapExtension.cs
- FormsAuthenticationCredentials.cs
- ResumeStoryboard.cs
- ExceptionAggregator.cs
- Trustee.cs
- FontUnit.cs
- MeasureData.cs
- ThrowHelper.cs
- ShellProvider.cs
- GenericTypeParameterBuilder.cs
- ElementMarkupObject.cs
- CodeTypeParameter.cs
- ReadOnlyDictionary.cs
- ArrayExtension.cs