Im trying to create a WrapPanel
that stretches its children horizontally when there's space. I've used the solution provided here: How to make WPF wrappanel child items to stretch?, althought it doesn't work, so i came up with similar solution, only to encounter same effect. At the end, i tried this: A custom auto-sizing WPF Panel class, but two-measure solution fails even more. Children of my panel are 3 StackPanel
s, each has MinWidth and double of it MaxWidth, but without them there's no change. Custom panels arrange these panels with proper values, but strange thing that's happenning is that only the first one accepts these values a bit (in example: panel's desiredWidth was 440, provided it with width of 500, it grew to around 470), and two others return just their previous DesiredSize. Arrange is then called again and situation repeats, till all empty space is taken by first panel. Strange tho, when i minimize window a bit so last panel is wrapped to next line, it grows and centers properly, and so do two upper panels, althought first panel is still bigger. I have no frickin' idea what's going on. I tried returning a Size(0,0) and/or passing Double.PositiveInfinity as width in measureOverride with no effect.
Here's my current solution:
protected override Size MeasureOverride(Size constraint)
{
float lineU = 0;
float lineV = 0;
float panelU = 0;
float panelV = 0;
float constraintWidth = (float)constraint.Width;
var children = base.InternalChildren;
for (int i = 0; i < children.Count; i++)
{
var child = children[i];
child.Measure(constraint);
var desiredSize = child.DesiredSize;
var itemWidth = (float)desiredSize.Width;
var itemHeight = (float)desiredSize.Height;
if (lineU + itemWidth > constraintWidth - 10 * float.Epsilon)
{
panelU = Math.Max(lineU, panelU);
panelV += lineV;
lineU = itemWidth;
lineV = itemHeight;
if (itemWidth > constraintWidth - 10 * float.Epsilon)
{
panelU = Math.Max(itemWidth, panelU);
panelV += itemHeight;
lineU = 0;
lineV = 0;
}
}
else
{
lineU += itemWidth;
lineV = Math.Max(itemHeight, lineV);
}
}
panelU = Math.Max(lineU, panelU);
panelV += lineV;
return new Size(panelU, panelV);
}
protected override Size ArrangeOverride(Size finalSize)
{
var finalSizeWidth = (float)finalSize.Width;
int num = 0;
var currentHeight = 0f;
float lineU = 0;
float lineV = 0;
var children = base.InternalChildren;
for (int i = 0; i < children.Count; i++)
{
var desiredSize = children[i].DesiredSize;
float width = (float)desiredSize.Width;
float height = (float)desiredSize.Height;
// when formed line is too large with current element
if (lineU + width > finalSizeWidth - 10 * float.Epsilon)
{
arrangeLine(currentHeight, lineV, num, i, children, lineU, finalSizeWidth);
currentHeight += lineV;
lineU = width;
lineV = height;
// when current element wants to be larger than available space - put him single
if (width > finalSizeWidth - 10 * float.Epsilon)
{
arrangeLine(currentHeight, height, i, ++i, children, finalSizeWidth, finalSizeWidth);
currentHeight += height;
lineU = 0;
lineV = 0;
}
num = i;
}
else // just normal forming line
{
lineU += width;
lineV = Math.Max(height, lineV);
}
}
if (num < children.Count)
{
var lineWidth = lineU;
arrangeLine(currentHeight, lineV, num, children.Count, children, lineWidth, finalSizeWidth);
}
return finalSize;
}
private void arrangeLine(float v, float lineV, int start, int end,
UIElementCollection children, float lineWidth, float availableWidth)
{
float num = 0f;
float multiplierU = availableWidth / lineWidth;
for (int i = start; i < end; i++)
{
UIElement uIElement = children[i];
if (uIElement != null)
{
var width = (float)uIElement.DesiredSize.Width;
float itemU = width * multiplierU;
uIElement.Arrange(new Rect(num, v, itemU, lineV));
num += itemU;
}
}
}
And here's two-measure solution:
private List<KeyValuePair<int, float>> _lineMuls;
protected override Size MeasureOverride(Size constraint)
{
_lineMuls = new List<KeyValuePair<int, float>>();
float lineU = 0;
float lineV = 0;
float panelU = 0;
float panelV = 0;
int lineCount = 0;
//float constraintWidth = horizontal ? (float)constraint.Width : (float)constraint.Height;
float constraintWidth = (float)constraint.Width;
var children = base.InternalChildren;
Size encouragingConstraint = new Size(Double.PositiveInfinity, constraint.Height);
for (int i = 0; i < children.Count; i++)
{
UIElement child = children[i];
if (child == null)
continue;
child.Measure(constraint);
var desiredSize = child.DesiredSize;
var itemWidth = (float)desiredSize.Width;
var itemHeight = (float)desiredSize.Height;
if (lineU + itemWidth > constraintWidth - 2* float.Epsilon)
{
panelU = Math.Max(lineU, panelU);
panelV += lineV;
lineU = itemWidth;
lineV = itemHeight;
if (itemWidth > constraintWidth - 2* float.Epsilon)
{
panelU = Math.Max(itemWidth, panelU);
panelV += itemHeight;
child.Measure(new Size(constraintWidth - 1, itemHeight));
_lineMuls.Add(new KeyValuePair<int, float>(1, 1));
lineU = 0;
lineV = 0;
lineCount = 0;
}
else
{
MeasureLine(constraintWidth, lineU, i, lineCount, children);
lineCount = 1;
}
}
else
{
lineCount++;
lineU += itemWidth;
lineV = Math.Max(itemHeight, lineV);
}
}
if (lineCount > 0)
{
MeasureLine(constraintWidth, lineU, children.Count, lineCount, children);
}
panelU = Math.Max(lineU, panelU);
panelV += lineV;
return new Size(panelU, panelV);
}
private void MeasureLine(float constraintWidth, float lineU, int i, int lineCount, UIElementCollection children)
{
var mulU = (constraintWidth - 1) / (lineU + 1);
mulU = mulU < 1.02 ? 1 : mulU;
_lineMuls.Add(new KeyValuePair<int, float>(lineCount, mulU));
for (int j = i - lineCount; j < i; j++)
{
var child = children[j];
if (child == null)
continue;
var desiredSize = child.DesiredSize;
float itemWidth = (float)desiredSize.Width * mulU;
var itemHeight = (float)desiredSize.Height;
child.Measure(new Size(itemWidth, itemHeight));
}
}
protected override Size ArrangeOverride(Size finalSize)
{
var children = base.InternalChildren;
int childCount = 0;
float height = 0f;
for (int i = 0; i < _lineMuls.Count; i++)
{
var line = _lineMuls[i];
var offsetU = 0f;
var offsetV = 0f;
for (int e = childCount; e < childCount + line.Key; e++)
{
UIElement child = children[e];
if (child == null)
continue;
var desiredSize = child.DesiredSize;
var itemWidth = (float)desiredSize.Width * line.Value;
var itemHeight = (float)desiredSize.Height;
child.Arrange(new Rect(offsetU, height, itemWidth, itemHeight));
offsetV = Math.Max(offsetV, itemHeight);
offsetU += itemWidth;
}
childCount += line.Key;
height += offsetV;
}
//_lineMuls = null;
return finalSize;
}
I kindly appreciate all help.