更新
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace WPFDark.StandardControls.Internal
|
||||
{
|
||||
internal class ExpanderMultiplyConverter : IMultiValueConverter
|
||||
{
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
var headerSiteHeight = (double)values[0];
|
||||
var expandSiteHeight = (double)values[1];
|
||||
var scale = (double)values[2];
|
||||
|
||||
var result = headerSiteHeight + expandSiteHeight * scale;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
568
WPFDark/StandardControls/Internal/TabPanelInternal.cs
Normal file
568
WPFDark/StandardControls/Internal/TabPanelInternal.cs
Normal file
@@ -0,0 +1,568 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace WPFDark.StandardControls.Internal
|
||||
{
|
||||
// Based on this code
|
||||
// https://github.com/dotnet/wpf/blob/master/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/Primitives/TabPanel.cs
|
||||
|
||||
public class TabPanelInternal : TabPanel
|
||||
{
|
||||
static TabPanelInternal()
|
||||
{
|
||||
KeyboardNavigation.TabNavigationProperty.OverrideMetadata(typeof(TabPanelInternal), new FrameworkPropertyMetadata(KeyboardNavigationMode.Once));
|
||||
KeyboardNavigation.DirectionalNavigationProperty.OverrideMetadata(typeof(TabPanelInternal), new FrameworkPropertyMetadata(KeyboardNavigationMode.Cycle));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates DesiredSize of the TabPanelInternal. Called by parent UIElement. This is the first pass of layout.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// TabPanelInternal
|
||||
/// </remarks>
|
||||
/// <param name="constraint">Constraint size is an "upper limit" that TabPanelInternal should not exceed.</param>
|
||||
/// <returns>TabPanelInternal' desired size.</returns>
|
||||
protected override Size MeasureOverride(Size constraint)
|
||||
{
|
||||
var contentSize = new Size();
|
||||
var tabAlignment = TabStripPlacement;
|
||||
|
||||
_numRows = 1;
|
||||
_numHeaders = 0;
|
||||
_rowHeight = 0;
|
||||
|
||||
switch (tabAlignment)
|
||||
{
|
||||
// For top and bottom placement the panel flow its children to calculate the number of rows and
|
||||
// desired vertical size
|
||||
case Dock.Top:
|
||||
case Dock.Bottom:
|
||||
{
|
||||
var numInCurrentRow = 0;
|
||||
var currentRowWidth = 0.0;
|
||||
var maxRowWidth = 0.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);
|
||||
var 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 width is smaller than constraint width then size to content
|
||||
if (double.IsInfinity(contentSize.Width) || double.IsNaN(contentSize.Width) ||
|
||||
maxRowWidth < constraint.Width)
|
||||
contentSize.Width = maxRowWidth;
|
||||
else
|
||||
contentSize.Width = constraint.Width;
|
||||
break;
|
||||
}
|
||||
|
||||
case Dock.Left:
|
||||
case 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);
|
||||
|
||||
var childSize = GetDesiredSizeWithoutMargin(child);
|
||||
|
||||
if (contentSize.Width < childSize.Width)
|
||||
contentSize.Width = childSize.Width;
|
||||
|
||||
contentSize.Height += childSize.Height;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
// Returns our minimum size & sets DesiredSize.
|
||||
return contentSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TabPanelInternal arranges each of its children.
|
||||
/// </summary>
|
||||
/// <param name="arrangeSize">Size that TabPanelInternal will assume to position children.</param>
|
||||
protected override Size ArrangeOverride(Size arrangeSize)
|
||||
{
|
||||
var tabAlignment = TabStripPlacement;
|
||||
|
||||
switch (tabAlignment)
|
||||
{
|
||||
case Dock.Top:
|
||||
case Dock.Bottom:
|
||||
ArrangeHorizontal(arrangeSize);
|
||||
break;
|
||||
|
||||
case Dock.Left:
|
||||
case Dock.Right:
|
||||
ArrangeVertical(arrangeSize);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
return arrangeSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override of <seealso cref="UIElement.GetLayoutClip"/>.
|
||||
/// </summary>
|
||||
/// <returns>Geometry to use as additional clip in case when element is larger then available space</returns>
|
||||
protected override Geometry GetLayoutClip(Size layoutSlotSize)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Size GetDesiredSizeWithoutMargin(UIElement element)
|
||||
{
|
||||
var margin = (Thickness) element.GetValue(MarginProperty);
|
||||
Size desiredSizeWithoutMargin = default;
|
||||
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 void GetHeadersSize(Span<double> headerSize)
|
||||
{
|
||||
var childIndex = 0;
|
||||
|
||||
foreach (UIElement child in InternalChildren)
|
||||
{
|
||||
if (child.Visibility == Visibility.Collapsed)
|
||||
continue;
|
||||
|
||||
var childSize = GetDesiredSizeWithoutMargin(child);
|
||||
headerSize[childIndex] = Math.Floor(childSize.Width);
|
||||
childIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
private void ArrangeHorizontal(Size arrangeSize)
|
||||
{
|
||||
var numSeparators = _numRows - 1;
|
||||
|
||||
// ReSharper disable MergeConditionalExpression
|
||||
var bufferSize =
|
||||
Unsafe.SizeOf<double>() * _numHeaders +
|
||||
Unsafe.SizeOf<int>() * numSeparators;
|
||||
|
||||
var bufferArray =
|
||||
bufferSize >= 512
|
||||
? ArrayPool<byte>.Shared.Rent(bufferSize)
|
||||
: null;
|
||||
|
||||
var buffer =
|
||||
bufferArray != null
|
||||
? bufferArray.AsSpan(0, bufferSize)
|
||||
: stackalloc byte[bufferSize];
|
||||
// ReSharper restore MergeConditionalExpression
|
||||
|
||||
var headerSize = MemoryMarshal.Cast<byte, double>(buffer.Slice(0, Unsafe.SizeOf<double>() * _numHeaders));
|
||||
var solution = MemoryMarshal.Cast<byte, int>(buffer.Slice(Unsafe.SizeOf<double>() * _numHeaders, Unsafe.SizeOf<int>() * numSeparators));
|
||||
|
||||
try
|
||||
{
|
||||
var tabAlignment = TabStripPlacement;
|
||||
var isMultiRow = _numRows > 1;
|
||||
var activeRow = 0;
|
||||
var childOffset = new Vector();
|
||||
|
||||
GetHeadersSize(headerSize);
|
||||
|
||||
// If we have multirows, then calculate the best header distribution
|
||||
if (isMultiRow)
|
||||
{
|
||||
solution = CalculateHeaderDistribution(arrangeSize.Width, headerSize, solution);
|
||||
activeRow = GetActiveRow(solution);
|
||||
|
||||
childOffset.Y = tabAlignment switch
|
||||
{
|
||||
// TabPanelInternal 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
|
||||
Dock.Top => ((_numRows - 1 - activeRow) * _rowHeight),
|
||||
Dock.Bottom when activeRow != 0 => ((_numRows - activeRow) * _rowHeight),
|
||||
_ => childOffset.Y
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
solution = Array.Empty<int>();
|
||||
}
|
||||
|
||||
var childIndex = 0;
|
||||
var separatorIndex = 0;
|
||||
|
||||
foreach (UIElement child in InternalChildren)
|
||||
{
|
||||
if (child.Visibility == Visibility.Collapsed)
|
||||
continue;
|
||||
|
||||
var margin = (Thickness) child.GetValue(MarginProperty);
|
||||
var leftOffset = margin.Left;
|
||||
var rightOffset = margin.Right;
|
||||
var topOffset = margin.Top;
|
||||
var bottomOffset = margin.Bottom;
|
||||
|
||||
var lastHeaderInRow = isMultiRow &&
|
||||
(separatorIndex < solution.Length && solution[separatorIndex] == childIndex ||
|
||||
childIndex == _numHeaders - 1);
|
||||
|
||||
//Length left, top, right, bottom;
|
||||
var 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));
|
||||
|
||||
var 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++;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (bufferArray != null)
|
||||
ArrayPool<byte>.Shared.Return(bufferArray);
|
||||
}
|
||||
}
|
||||
|
||||
private void ArrangeVertical(Size arrangeSize)
|
||||
{
|
||||
var childOffsetY = 0d;
|
||||
|
||||
foreach (UIElement child in InternalChildren)
|
||||
{
|
||||
if (child.Visibility != Visibility.Collapsed)
|
||||
{
|
||||
var 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(Span<int> solution)
|
||||
{
|
||||
var activeRow = 0;
|
||||
var childIndex = 0;
|
||||
|
||||
if (solution.Length > 0)
|
||||
{
|
||||
foreach (UIElement child in InternalChildren)
|
||||
{
|
||||
if (child.Visibility == Visibility.Collapsed)
|
||||
continue;
|
||||
|
||||
var 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 alignment is Top - then the active row is the last row
|
||||
if (TabStripPlacement == Dock.Top)
|
||||
activeRow = _numRows - 1;
|
||||
|
||||
return activeRow;
|
||||
}
|
||||
|
||||
/* TabPanelInternal layout calculation:
|
||||
|
||||
After measure call we have:
|
||||
rowWidthLimit - width of the TabPanelInternal
|
||||
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<n always).
|
||||
For each current state rowAverageGap[1..numSeparators+1] are calculated for each row.
|
||||
Current state rowAverageGap = MAX (rowAverageGap[1..numSeparators+1]).
|
||||
Our goal is to find the solution with MIN (rowAverageGap).
|
||||
On each iteration step we move a header from a previous row to the row with maximum rowAverageGap.
|
||||
We continue the iterations only if we move to better solution, i.e. rowAverageGap is smaller.
|
||||
Maximum iteration steps are less the number of headers.
|
||||
|
||||
*/
|
||||
// Input: Row width and width of all headers
|
||||
// Output: int array which size is the number of separators and contains each separator position
|
||||
private Span<int> CalculateHeaderDistribution(double rowWidthLimit, Span<double> headerWidth, Span<int> bestSolution)
|
||||
{
|
||||
var numSeparators = _numRows - 1;
|
||||
|
||||
// ReSharper disable MergeConditionalExpression
|
||||
var doubleArraySize = _numRows + _numRows + _numRows;
|
||||
var intArraySize = numSeparators + _numRows;
|
||||
|
||||
var doubleArray =
|
||||
doubleArraySize > 128
|
||||
? ArrayPool<double>.Shared.Rent(_numRows + _numRows + _numRows)
|
||||
: null;
|
||||
|
||||
var intArray =
|
||||
intArraySize > 128
|
||||
? ArrayPool<int>.Shared.Rent(numSeparators + _numRows)
|
||||
: null;
|
||||
|
||||
var doubleBuffer =
|
||||
doubleArray != null
|
||||
? doubleArray
|
||||
: stackalloc double[doubleArraySize];
|
||||
|
||||
var intBuffer =
|
||||
intArray != null
|
||||
? intArray
|
||||
: stackalloc int[intArraySize];
|
||||
// ReSharper restore MergeConditionalExpression
|
||||
|
||||
var rowWidth = doubleBuffer.Slice(0, _numRows);
|
||||
var rowAverageGap = doubleBuffer.Slice(_numRows, _numRows);
|
||||
var bestSolutionRowAverageGap = doubleBuffer.Slice(_numRows + _numRows, _numRows);
|
||||
var currentSolution = intBuffer.Slice(0, numSeparators);
|
||||
var rowHeaderCount = intBuffer.Slice(numSeparators, _numRows);
|
||||
|
||||
try
|
||||
{
|
||||
var bestSolutionMaxRowAverageGap = 0.0;
|
||||
var numHeaders = headerWidth.Length;
|
||||
var currentRowWidth = 0.0;
|
||||
var numberOfHeadersInCurrentRow = 0;
|
||||
double currentAverageGap;
|
||||
|
||||
// Initialize the current state; Do the initial flow of the headers
|
||||
var currentRowIndex = 0;
|
||||
|
||||
for (var index = 0; index < numHeaders; index++)
|
||||
{
|
||||
if (currentRowWidth + headerWidth[index] > 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 amount 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)
|
||||
// ReSharper disable once CompareOfFloatsByEqualityOperator
|
||||
if (headerWidth[index] != 0)
|
||||
numberOfHeadersInCurrentRow++;
|
||||
}
|
||||
}
|
||||
|
||||
// If everything fit in 1 row then exit (no separators needed)
|
||||
if (currentRowIndex == 0)
|
||||
return bestSolution.Slice(0, 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); // Remember the first solution as initial bestSolution
|
||||
rowAverageGap.CopyTo(bestSolutionRowAverageGap); // 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
|
||||
var worstRowIndex = 0; // Keep the row index with maximum AverageGap
|
||||
var maxAg = 0.0;
|
||||
|
||||
for (var 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
|
||||
var moveToRow = worstRowIndex;
|
||||
var moveFromRow = moveToRow - 1;
|
||||
var moveHeader = currentSolution[moveFromRow];
|
||||
var 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 (var i = 0; i < _numRows; i++) // for all rows
|
||||
{
|
||||
if (maxAg < rowAverageGap[i])
|
||||
{
|
||||
maxAg = rowAverageGap[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (maxAg < bestSolutionMaxRowAverageGap)
|
||||
{
|
||||
bestSolutionMaxRowAverageGap = maxAg;
|
||||
currentSolution.CopyTo(bestSolution);
|
||||
rowAverageGap.CopyTo(bestSolutionRowAverageGap);
|
||||
}
|
||||
}
|
||||
|
||||
// Each header size should be increased so headers in the row stretch to fit the row
|
||||
currentRowIndex = 0;
|
||||
|
||||
for (var index = 0; index < numHeaders; index++)
|
||||
{
|
||||
headerWidth[index] += bestSolutionRowAverageGap[currentRowIndex];
|
||||
if (currentRowIndex < numSeparators && bestSolution[currentRowIndex] == index)
|
||||
currentRowIndex++;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (doubleArray != null)
|
||||
ArrayPool<double>.Shared.Return(doubleArray);
|
||||
|
||||
if (intArray != null)
|
||||
ArrayPool<int>.Shared.Return(intArray);
|
||||
}
|
||||
|
||||
return bestSolution;
|
||||
}
|
||||
|
||||
private Dock TabStripPlacement => (TemplatedParent is TabControl tc)
|
||||
? tc.TabStripPlacement
|
||||
: Dock.Top;
|
||||
|
||||
private int _numRows = 1; // Number of row calculated in measure and used in arrange
|
||||
private int _numHeaders; // Number of headers excluding the collapsed items
|
||||
private double _rowHeight; // Maximum of all headers height
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user