WPF - 如何在 WrapPanel 中居中所有项目?
Posted
技术标签:
【中文标题】WPF - 如何在 WrapPanel 中居中所有项目?【英文标题】:WPF - How can I center all items in a WrapPanel? 【发布时间】:2010-10-22 20:22:51 【问题描述】:我使用WrapPanel
作为ItemsControl
的ItemsPanel
。现在,控件中的项目如下所示:
|1234567 |
|890 |
我希望它们像这样包装:
| 1234567 |
| 890 |
从概念上讲,布局过程应对齐每行项目,使其在WrapPanel
的边界内居中。
有人能解释一下这是怎么可能的吗?
【问题讨论】:
【参考方案1】:内置的WrapPanel
不允许你对齐它的内容——只有它自己。这是一种允许您设置HorizontalContentAlignment
的技术:
using System;
using System.Windows;
using System.Windows.Controls;
public class AlignableWrapPanel : Panel
public HorizontalAlignment HorizontalContentAlignment
get return (HorizontalAlignment)GetValue(HorizontalContentAlignmentProperty);
set SetValue(HorizontalContentAlignmentProperty, value);
public static readonly DependencyProperty HorizontalContentAlignmentProperty =
DependencyProperty.Register("HorizontalContentAlignment", typeof(HorizontalAlignment), typeof(AlignableWrapPanel), new FrameworkPropertyMetadata(HorizontalAlignment.Left, FrameworkPropertyMetadataOptions.AffectsArrange));
protected override Size MeasureOverride(Size constraint)
Size curLineSize = new Size();
Size panelSize = new Size();
UIElementCollection children = base.InternalChildren;
for (int i = 0; i < children.Count; i++)
UIElement child = children[i] as UIElement;
// Flow passes its own constraint to children
child.Measure(constraint);
Size sz = child.DesiredSize;
if (curLineSize.Width + sz.Width > constraint.Width) //need to switch to another line
panelSize.Width = Math.Max(curLineSize.Width, panelSize.Width);
panelSize.Height += curLineSize.Height;
curLineSize = sz;
if (sz.Width > constraint.Width) // if the element is wider then the constraint - give it a separate line
panelSize.Width = Math.Max(sz.Width, panelSize.Width);
panelSize.Height += sz.Height;
curLineSize = new Size();
else //continue to accumulate a line
curLineSize.Width += sz.Width;
curLineSize.Height = Math.Max(sz.Height, curLineSize.Height);
// the last line size, if any need to be added
panelSize.Width = Math.Max(curLineSize.Width, panelSize.Width);
panelSize.Height += curLineSize.Height;
return panelSize;
protected override Size ArrangeOverride(Size arrangeBounds)
int firstInLine = 0;
Size curLineSize = new Size();
double accumulatedHeight = 0;
UIElementCollection children = this.InternalChildren;
for (int i = 0; i < children.Count; i++)
Size sz = children[i].DesiredSize;
if (curLineSize.Width + sz.Width > arrangeBounds.Width) //need to switch to another line
ArrangeLine(accumulatedHeight, curLineSize, arrangeBounds.Width, firstInLine, i);
accumulatedHeight += curLineSize.Height;
curLineSize = sz;
if (sz.Width > arrangeBounds.Width) //the element is wider then the constraint - give it a separate line
ArrangeLine(accumulatedHeight, sz, arrangeBounds.Width, i, ++i);
accumulatedHeight += sz.Height;
curLineSize = new Size();
firstInLine = i;
else //continue to accumulate a line
curLineSize.Width += sz.Width;
curLineSize.Height = Math.Max(sz.Height, curLineSize.Height);
if (firstInLine < children.Count)
ArrangeLine(accumulatedHeight, curLineSize, arrangeBounds.Width, firstInLine, children.Count);
return arrangeBounds;
private void ArrangeLine(double y, Size lineSize, double boundsWidth, int start, int end)
double x = 0;
if (this.HorizontalContentAlignment == HorizontalAlignment.Center)
x = (boundsWidth - lineSize.Width) / 2;
else if (this.HorizontalContentAlignment == HorizontalAlignment.Right)
x = (boundsWidth - lineSize.Width);
UIElementCollection children = InternalChildren;
for (int i = start; i < end; i++)
UIElement child = children[i];
child.Arrange(new Rect(x, y, child.DesiredSize.Width, lineSize.Height));
x += child.DesiredSize.Width;
【讨论】:
好课,谢谢!如果孩子有一个,我已经对其进行了调整以尊重FrameworkElement.HorizontalAlignment
的值:gist.github.com/gmanny/7450651(仅更改了ArrangeLine
方法)。
对于拉伸对齐: if (i == end - 1 && HorizontalContentAlignment == HorizontalAlignment.Stretch) child.Arrange(new Rect(x, y, boundsWidth - x, lineSize.Height)); else child.Arrange(new Rect(x, y, child.DesiredSize.Width, lineSize.Height)); x += child.DesiredSize.Width;
完美的一个。谢谢
是否可以从中提取一个新方法,该方法在调用时会插入换行符?当我做了一些hacky代码来欺骗MeasureOverride认为它需要包装但我希望它在side方法中正常工作时,我只能让它这样做。
对不起。我是个十足的菜鸟。我如何使用它?【参考方案2】:
不幸的是,普通 WrapPanel 无法满足您的要求,因为它的目的是在包装之前水平(或垂直)填充所有可用空间。您可能必须设计自己的 WrapPanel 来处理这种情况。你可以从this sample that shows how to create your own WrapPanel开始。
【讨论】:
【参考方案3】:VerticalContentAlignment 版本(用于垂直面板):
public class AlignableWrapPanel : Panel
public AlignableWrapPanel()
_orientation = Orientation.Horizontal;
private static bool IsWidthHeightValid(object value)
var v = (double)value;
return (double.IsNaN(v)) || (v >= 0.0d && !double.IsPositiveInfinity(v));
public static readonly DependencyProperty ItemWidthProperty = DependencyProperty.Register("ItemWidth", typeof(double),
typeof(AlignableWrapPanel), new FrameworkPropertyMetadata(double.NaN, FrameworkPropertyMetadataOptions.AffectsMeasure),
IsWidthHeightValid);
[TypeConverter(typeof(LengthConverter))]
public double ItemWidth
get return (double)GetValue(ItemWidthProperty);
set SetValue(ItemWidthProperty, value);
public static readonly DependencyProperty ItemHeightProperty = DependencyProperty.Register("ItemHeight", typeof(double),
typeof(AlignableWrapPanel), new FrameworkPropertyMetadata(double.NaN, FrameworkPropertyMetadataOptions.AffectsMeasure),
IsWidthHeightValid);
[TypeConverter(typeof(LengthConverter))]
public double ItemHeight
get return (double)GetValue(ItemHeightProperty);
set SetValue(ItemHeightProperty, value);
public static readonly DependencyProperty OrientationProperty = StackPanel.OrientationProperty.AddOwner(typeof(AlignableWrapPanel),
new FrameworkPropertyMetadata(Orientation.Horizontal, FrameworkPropertyMetadataOptions.AffectsMeasure, OnOrientationChanged));
public Orientation Orientation
get return _orientation;
set SetValue(OrientationProperty, value);
private static void OnOrientationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
var p = (AlignableWrapPanel)d;
p._orientation = (Orientation)e.NewValue;
private Orientation _orientation;
private struct UvSize
internal UvSize(Orientation orientation, double width, double height)
U = V = 0d;
_orientation = orientation;
Width = width;
Height = height;
internal UvSize(Orientation orientation)
U = V = 0d;
_orientation = orientation;
internal double U;
internal double V;
private readonly Orientation _orientation;
internal double Width
get return (_orientation == Orientation.Horizontal ? U : V);
private set if (_orientation == Orientation.Horizontal) U = value; else V = value;
internal double Height
get return (_orientation == Orientation.Horizontal ? V : U);
private set if (_orientation == Orientation.Horizontal) V = value; else U = value;
protected override Size MeasureOverride(Size constraint)
var curLineSize = new UvSize(Orientation);
var panelSize = new UvSize(Orientation);
var uvConstraint = new UvSize(Orientation, constraint.Width, constraint.Height);
var itemWidth = ItemWidth;
var itemHeight = ItemHeight;
var itemWidthSet = !double.IsNaN(itemWidth);
var itemHeightSet = !double.IsNaN(itemHeight);
var childConstraint = new Size(
(itemWidthSet ? itemWidth : constraint.Width),
(itemHeightSet ? itemHeight : constraint.Height));
var children = InternalChildren;
for (int i = 0, count = children.Count; i < count; i++)
var child = children[i];
if (child == null) continue;
//Flow passes its own constrint to children
child.Measure(childConstraint);
//this is the size of the child in UV space
var sz = new UvSize(
Orientation,
(itemWidthSet ? itemWidth : child.DesiredSize.Width),
(itemHeightSet ? itemHeight : child.DesiredSize.Height));
if (curLineSize.U + sz.U > uvConstraint.U)
//need to switch to another line
panelSize.U = Math.Max(curLineSize.U, panelSize.U);
panelSize.V += curLineSize.V;
curLineSize = sz;
if (!(sz.U > uvConstraint.U)) continue;
//the element is wider then the constrint - give it a separate line
panelSize.U = Math.Max(sz.U, panelSize.U);
panelSize.V += sz.V;
curLineSize = new UvSize(Orientation);
else
//continue to accumulate a line
curLineSize.U += sz.U;
curLineSize.V = Math.Max(sz.V, curLineSize.V);
//the last line size, if any should be added
panelSize.U = Math.Max(curLineSize.U, panelSize.U);
panelSize.V += curLineSize.V;
//go from UV space to W/H space
return new Size(panelSize.Width, panelSize.Height);
protected override Size ArrangeOverride(Size finalSize)
var firstInLine = 0;
var itemWidth = ItemWidth;
var itemHeight = ItemHeight;
double accumulatedV = 0;
var itemU = (Orientation == Orientation.Horizontal ? itemWidth : itemHeight);
var curLineSize = new UvSize(Orientation);
var uvFinalSize = new UvSize(Orientation, finalSize.Width, finalSize.Height);
var itemWidthSet = !double.IsNaN(itemWidth);
var itemHeightSet = !double.IsNaN(itemHeight);
var useItemU = (Orientation == Orientation.Horizontal ? itemWidthSet : itemHeightSet);
var children = InternalChildren;
for (int i = 0, count = children.Count; i < count; i++)
var child = children[i];
if (child == null) continue;
var sz = new UvSize(
Orientation,
(itemWidthSet ? itemWidth : child.DesiredSize.Width),
(itemHeightSet ? itemHeight : child.DesiredSize.Height));
if (curLineSize.U + sz.U > uvFinalSize.U)
//need to switch to another line
ArrangeLine(finalSize, accumulatedV, curLineSize, firstInLine, i, useItemU, itemU);
accumulatedV += curLineSize.V;
curLineSize = sz;
if (sz.U > uvFinalSize.U)
//the element is wider then the constraint - give it a separate line
//switch to next line which only contain one element
ArrangeLine(finalSize, accumulatedV, sz, i, ++i, useItemU, itemU);
accumulatedV += sz.V;
curLineSize = new UvSize(Orientation);
firstInLine = i;
else
//continue to accumulate a line
curLineSize.U += sz.U;
curLineSize.V = Math.Max(sz.V, curLineSize.V);
//arrange the last line, if any
if (firstInLine < children.Count)
ArrangeLine(finalSize, accumulatedV, curLineSize, firstInLine, children.Count, useItemU, itemU);
return finalSize;
private void ArrangeLine(Size finalSize, double v, UvSize line, int start, int end, bool useItemU, double itemU)
double u;
var isHorizontal = Orientation == Orientation.Horizontal;
if (_orientation == Orientation.Vertical)
switch (VerticalContentAlignment)
case VerticalAlignment.Center:
u = (finalSize.Height - line.U) / 2;
break;
case VerticalAlignment.Bottom:
u = finalSize.Height - line.U;
break;
default:
u = 0;
break;
else
switch (HorizontalContentAlignment)
case HorizontalAlignment.Center:
u = (finalSize.Width - line.U) / 2;
break;
case HorizontalAlignment.Right:
u = finalSize.Width - line.U;
break;
default:
u = 0;
break;
var children = InternalChildren;
for (var i = start; i < end; i++)
var child = children[i];
if (child == null) continue;
var childSize = new UvSize(Orientation, child.DesiredSize.Width, child.DesiredSize.Height);
var layoutSlotU = (useItemU ? itemU : childSize.U);
child.Arrange(new Rect(
isHorizontal ? u : v,
isHorizontal ? v : u,
isHorizontal ? layoutSlotU : line.V,
isHorizontal ? line.V : layoutSlotU));
u += layoutSlotU;
public static readonly DependencyProperty HorizontalContentAlignmentProperty = DependencyProperty.Register(nameof(HorizontalContentAlignment), typeof(HorizontalAlignment),
typeof(AlignableWrapPanel), new FrameworkPropertyMetadata(HorizontalAlignment.Left, FrameworkPropertyMetadataOptions.AffectsArrange));
public HorizontalAlignment HorizontalContentAlignment
get return (HorizontalAlignment)GetValue(HorizontalContentAlignmentProperty);
set SetValue(HorizontalContentAlignmentProperty, value);
public static readonly DependencyProperty VerticalContentAlignmentProperty = DependencyProperty.Register(nameof(VerticalContentAlignment), typeof(VerticalAlignment),
typeof(AlignableWrapPanel), new FrameworkPropertyMetadata(VerticalAlignment.Top, FrameworkPropertyMetadataOptions.AffectsArrange));
public VerticalAlignment VerticalContentAlignment
get return (VerticalAlignment)GetValue(VerticalContentAlignmentProperty);
set SetValue(VerticalContentAlignmentProperty, value);
【讨论】:
由于某种原因,此解决方案有效,但接受的答案无效。谢谢你。【参考方案4】:简单的答案是您无法将 WrapPanel 的内容居中对齐。您可以将面板本身居中对齐,但最后一行仍将在面板内左对齐。
改变建议:
对行和列使用网格。如果您不是动态地将项目添加到集合中,这可能会很好地工作。
创建您自己的 WrapPanel 版本,按照您需要的方式工作。 This MSDN document 描述了面板的工作原理,并包含有关创建自定义面板的部分。它还有一个指向示例自定义面板的链接。
【讨论】:
【参考方案5】:这是 Silverlight 版本
特别感谢@DTig
,
using System.Windows.Controls;
using System.Windows;
using Telerik.Windows;
using System;
using System.Linq;
public class AlignableWrapPanel : Panel
public HorizontalAlignment HorizontalContentAlignment
get return (HorizontalAlignment)GetValue(HorizontalContentAlignmentProperty);
set SetValue(HorizontalContentAlignmentProperty, value);
public static readonly DependencyProperty HorizontalContentAlignmentProperty =
DependencyProperty.Register("HorizontalContentAlignment", typeof(HorizontalAlignment), typeof(AlignableWrapPanel), new FrameworkPropertyMetadata(HorizontalAlignment.Left, FrameworkPropertyMetadataOptions.AffectsArrange));
protected override Size MeasureOverride(Size constraint)
Size curLineSize = new Size();
Size panelSize = new Size();
UIElementCollection children = base.Children;
for (int i = 0; i < children.Count; i++)
UIElement child = children[i] as UIElement;
// Flow passes its own constraint to children
child.Measure(constraint);
Size sz = child.DesiredSize;
if (curLineSize.Width + sz.Width > constraint.Width) //need to switch to another line
panelSize.Width = Math.Max(curLineSize.Width, panelSize.Width);
panelSize.Height += curLineSize.Height;
curLineSize = sz;
if (sz.Width > constraint.Width) // if the element is wider then the constraint - give it a separate line
panelSize.Width = Math.Max(sz.Width, panelSize.Width);
panelSize.Height += sz.Height;
curLineSize = new Size();
else //continue to accumulate a line
curLineSize.Width += sz.Width;
curLineSize.Height = Math.Max(sz.Height, curLineSize.Height);
// the last line size, if any need to be added
panelSize.Width = Math.Max(curLineSize.Width, panelSize.Width);
panelSize.Height += curLineSize.Height;
return panelSize;
protected override Size ArrangeOverride(Size arrangeBounds)
int firstInLine = 0;
Size curLineSize = new Size();
double accumulatedHeight = 0;
UIElementCollection children = this.Children;
for (int i = 0; i < children.Count; i++)
Size sz = children[i].DesiredSize;
if (curLineSize.Width + sz.Width > arrangeBounds.Width) //need to switch to another line
ArrangeLine(accumulatedHeight, curLineSize, arrangeBounds.Width, firstInLine, i);
accumulatedHeight += curLineSize.Height;
curLineSize = sz;
if (sz.Width > arrangeBounds.Width) //the element is wider then the constraint - give it a separate line
ArrangeLine(accumulatedHeight, sz, arrangeBounds.Width, i, ++i);
accumulatedHeight += sz.Height;
curLineSize = new Size();
firstInLine = i;
else //continue to accumulate a line
curLineSize.Width += sz.Width;
curLineSize.Height = Math.Max(sz.Height, curLineSize.Height);
if (firstInLine < children.Count)
ArrangeLine(accumulatedHeight, curLineSize, arrangeBounds.Width, firstInLine, children.Count);
return arrangeBounds;
private void ArrangeLine(double y, Size lineSize, double boundsWidth, int start, int end)
double x = 0;
if (this.HorizontalContentAlignment == HorizontalAlignment.Center)
x = (boundsWidth - lineSize.Width) / 2;
else if (this.HorizontalContentAlignment == HorizontalAlignment.Right)
x = (boundsWidth - lineSize.Width);
UIElementCollection children = Children;
for (int i = start; i < end; i++)
UIElement child = children[i];
var rect = new System.Windows.Rect(x, y, child.DesiredSize.Width, lineSize.Height);
child.Arrange(rect);
x += child.DesiredSize.Width;
【讨论】:
【参考方案6】:我需要一个可以拉伸其内容的 WrapPanel,因此基于https://***.com/a/7747002/121122 和一些摆弄我想出了这个:
public class AlignableWrapPanel : Panel
public HorizontalAlignment HorizontalContentAlignment
get => (HorizontalAlignment)GetValue(HorizontalContentAlignmentProperty);
set => SetValue(HorizontalContentAlignmentProperty, value);
public static readonly DependencyProperty HorizontalContentAlignmentProperty = DependencyProperty.Register(
nameof(HorizontalContentAlignment),
typeof(HorizontalAlignment),
typeof(AlignableWrapPanel),
new FrameworkPropertyMetadata(HorizontalAlignment.Left, FrameworkPropertyMetadataOptions.AffectsArrange));
protected override Size MeasureOverride(Size constraint)
var curLineSize = new Size();
var panelSize = new Size();
var children = InternalChildren;
for (var i = 0; i < children.Count; i++)
var child = children[i];
// flow passes its own constraint to children
child.Measure(constraint);
var sz = child.DesiredSize;
if (curLineSize.Width + sz.Width > constraint.Width) // need to switch to another line
panelSize.Width = Math.Max(curLineSize.Width, panelSize.Width);
panelSize.Height += curLineSize.Height;
curLineSize = sz;
if (sz.Width > constraint.Width) // if the element is wider then the constraint - give it a separate line
panelSize.Width = Math.Max(sz.Width, panelSize.Width);
panelSize.Height += sz.Height;
curLineSize = new Size();
else // continue to add to the line
curLineSize.Width += sz.Width;
curLineSize.Height = Math.Max(sz.Height, curLineSize.Height);
// the last line size, if any need to be added
panelSize.Width = Math.Max(curLineSize.Width, panelSize.Width);
panelSize.Height += curLineSize.Height;
return panelSize;
protected override Size ArrangeOverride(Size arrangeBounds)
var firstInLine = 0;
var curLineSize = new Size();
var accumulatedHeight = 0D;
var children = InternalChildren;
for (var i = 0; i < children.Count; i++)
var desiredSize = children[i].DesiredSize;
if (curLineSize.Width + desiredSize.Width > arrangeBounds.Width) // need to switch to another line
ArrangeLine(accumulatedHeight, curLineSize, arrangeBounds.Width, firstInLine, i);
accumulatedHeight += curLineSize.Height;
curLineSize = desiredSize;
if(
desiredSize.Width > arrangeBounds.Width ||
children[i] is FrameworkElement element && element.HorizontalAlignment == HorizontalAlignment.Stretch)
// the element is wider then the constraint or it stretches - give it a separate line
ArrangeLine(accumulatedHeight, desiredSize, arrangeBounds.Width, i, ++i);
accumulatedHeight += desiredSize.Height;
curLineSize = new Size();
firstInLine = i;
else // continue to add to the line
curLineSize.Width += desiredSize.Width;
curLineSize.Height = Math.Max(desiredSize.Height, curLineSize.Height);
if (firstInLine < children.Count)
ArrangeLine(accumulatedHeight, curLineSize, arrangeBounds.Width, firstInLine, children.Count);
return arrangeBounds;
private void ArrangeLine(double y, Size lineSize, double boundsWidth, int start, int end)
var children = InternalChildren;
var x = 0D;
if (HorizontalContentAlignment == HorizontalAlignment.Center)
x = (boundsWidth - lineSize.Width) / 2;
else if (HorizontalContentAlignment == HorizontalAlignment.Right)
x = boundsWidth - lineSize.Width;
var stretchChildren = new List<UIElement>();
for (var i = start; i < end; i++)
var child = children[i];
if (child is FrameworkElement element && element.HorizontalAlignment == HorizontalAlignment.Stretch)
stretchChildren.Add(child);
var spaceAvailableForStretchChildren = boundsWidth - lineSize.Width;
var spaceAvailablePerStretchChild = 0D;
if (stretchChildren.Any())
x = 0; // all available space will be filled so start at 0
spaceAvailablePerStretchChild = spaceAvailableForStretchChildren / stretchChildren.Count;
spaceAvailablePerStretchChild = spaceAvailablePerStretchChild >= 0 ? spaceAvailablePerStretchChild : 0;
for (var i = start; i < end; i++)
var child = children[i];
double itemWidth;
if(stretchChildren.Contains(child))
itemWidth = child.DesiredSize.Width + spaceAvailablePerStretchChild;
else
itemWidth = child.DesiredSize.Width;
child.Arrange(new Rect(x, y, itemWidth, lineSize.Height));
x += itemWidth;
【讨论】:
以上是关于WPF - 如何在 WrapPanel 中居中所有项目?的主要内容,如果未能解决你的问题,请参考以下文章
WPF: WrapPanel 容器的数据绑定(动态生成控件遍历)