WPF-как я могу центрировать все элементы в WrapPanel?
Я использую WrapPanel
как ItemsPanel
на ItemsControl
. Сейчас, предметы в обруч контроль такой:
|1234567 |
|890 |
Я бы хотел, чтобы они обернулись так:
| 1234567 |
| 890 |
концептуально процесс компоновки должен выровнять каждую строку элементов таким образом, чтобы она была центрирована в WrapPanel
С границы.
может кто-нибудь объяснить, как это возможно, пожалуйста?
6 ответов
построен в 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;
}
}
}
к сожалению, stock WrapPanel не будет делать то, что вы просите, потому что его намерение состоит в том, чтобы заполнить все доступное пространство по горизонтали (или по вертикали) перед упаковкой. Вероятно, вам придется создать свою собственную WrapPanel для обработки этого случая. Вы можете начать с этот пример показывает, как создать свой собственный WrapPanel.
простой ответ заключается в том, что вы не можете центрировать содержимое WrapPanel. Вы можете центрировать выравнивание самой панели, но последняя строка все равно останется выровненной внутри панели.
Alterate предложения:
используйте сетку со строками и столбцами. Если вы не добавляете элементы в коллекцию динамически, это может работать хорошо.
создайте свою собственную версию WrapPanel, которая работает так, как вам нужно. этот документ MSDN описывает как работают панели и включает в себя раздел о создании пользовательских панелей. Он также имеет ссылку на образец пользовательской панели.
версия с 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); }
}
}
вот версия 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;
}
}
}
Мне нужен WrapPanel, который может растягивать его содержимое, поэтому на основе https://stackoverflow.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;
}
}
}