具有覆盖的 OnVisualChildrenChanged 的自定义 WPF WrapPanel 将无法按预期工作
Cutom WPF WrapPanel with overridden OnVisualChildrenChanged won't work as expected
我正在 Windows 上开发一个应用程序,我想要一个像这样布局其子项的面板:
我在 WPF 面板中找到的最接近的是垂直方向的 WrapPanel,其布局如下:
到目前为止,我的解决方案是从 WrapPanel 创建派生的 class 并将其及其每个子项旋转 180 度,以便实现我的目标:
public class NotificationWrapPanel : WrapPanel
{
public NotificationWrapPanel()
{
this.Orientation = Orientation.Vertical;
this.RenderTransformOrigin = new Point(.5, .5);
this.RenderTransform = new RotateTransform(180);
}
protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved)
{
var addedChild = visualAdded as UIElement;
if (addedChild != null)
{
addedChild.RenderTransformOrigin = new Point(.5, .5);
addedChild.RenderTransform = new RotateTransform(180);
}
base.OnVisualChildrenChanged(addedChild, visualRemoved);
}
}
问题是覆盖的 OnVisualChildrenChanged
中的 addedChild.RenderTransform = new RotateTransform(180)
部分似乎不起作用。欢迎任何解决方案(即使与我的方法无关)。
更新:
我重新检查了我的代码并意识到我在其他地方更改了 RenderTransform,阻止了它的工作。所以我的问题解决了。但是,如果您提供任何可能更优雅的解决方案,我将不胜感激,例如使用 MeasureOverride
/ArrangeOverride
.
根据您的要求,下面是一个自定义面板,我认为它应该可以满足您的需求。它以从下到上然后从右到左的方式排列子元素,在一列中最多堆叠 MaxRows
个元素(如果是 null
,则堆叠所有元素)。所有插槽的大小都相同。它还确实考虑了元素的 Visibility
值,即如果一个项目是 Hidden
,它会留下一个空槽,如果它是 Collapsed
,它会被跳过并且下一个元素 "jumps" 到位。
public class NotificationWrapPanel : Panel
{
public static readonly DependencyProperty MaxRowsProperty =
DependencyProperty.Register(
name: nameof(MaxRows),
propertyType: typeof(int?),
ownerType: typeof(NotificationWrapPanel),
typeMetadata: new FrameworkPropertyMetadata
{
AffectsArrange = true,
AffectsMeasure = true,
},
validateValueCallback: o => o == null || (int)o >= 1);
public int? MaxRows
{
get { return (int?)GetValue(MaxRowsProperty); }
set { SetValue(MaxRowsProperty, value); }
}
protected override Size MeasureOverride(Size constraint)
{
var children = InternalChildren
.Cast<UIElement>()
.Where(e => e.Visibility != Visibility.Collapsed)
.ToList();
if (children.Count == 0) return new Size();
var rows = MaxRows.HasValue ?
Math.Min(MaxRows.Value, children.Count) :
children.Count;
var columns = children.Count / rows +
Math.Sign(children.Count % rows);
var childConstraint = new Size
{
Width = constraint.Width / columns,
Height = constraint.Height / rows,
};
foreach (UIElement child in children)
child.Measure(childConstraint);
return new Size
{
Height = rows * children
.Cast<UIElement>()
.Max(e => e.DesiredSize.Height),
Width = columns * children
.Cast<UIElement>()
.Max(e => e.DesiredSize.Width),
};
}
protected override Size ArrangeOverride(Size finalSize)
{
var children = InternalChildren
.Cast<UIElement>()
.Where(e => e.Visibility != Visibility.Collapsed)
.ToList();
if (children.Count == 0) return finalSize;
var rows = MaxRows.HasValue ?
Math.Min(MaxRows.Value, children.Count) :
children.Count;
var columns = children.Count / rows +
Math.Sign(children.Count % rows);
var childSize = new Size
{
Width = finalSize.Width / columns,
Height = finalSize.Height / rows
};
for (int i = 0; i < children.Count; i++)
{
var row = i % rows; //rows are numbered bottom-to-top
var col = i / rows; //columns are numbered right-to-left
var location = new Point
{
X = finalSize.Width - (col + 1) * childSize.Width,
Y = finalSize.Height - (row + 1) * childSize.Height,
};
children[i].Arrange(new Rect(location, childSize));
}
return finalSize;
}
}
我正在 Windows 上开发一个应用程序,我想要一个像这样布局其子项的面板:
我在 WPF 面板中找到的最接近的是垂直方向的 WrapPanel,其布局如下:
到目前为止,我的解决方案是从 WrapPanel 创建派生的 class 并将其及其每个子项旋转 180 度,以便实现我的目标:
public class NotificationWrapPanel : WrapPanel
{
public NotificationWrapPanel()
{
this.Orientation = Orientation.Vertical;
this.RenderTransformOrigin = new Point(.5, .5);
this.RenderTransform = new RotateTransform(180);
}
protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved)
{
var addedChild = visualAdded as UIElement;
if (addedChild != null)
{
addedChild.RenderTransformOrigin = new Point(.5, .5);
addedChild.RenderTransform = new RotateTransform(180);
}
base.OnVisualChildrenChanged(addedChild, visualRemoved);
}
}
问题是覆盖的 OnVisualChildrenChanged
中的 addedChild.RenderTransform = new RotateTransform(180)
部分似乎不起作用。欢迎任何解决方案(即使与我的方法无关)。
更新:
我重新检查了我的代码并意识到我在其他地方更改了 RenderTransform,阻止了它的工作。所以我的问题解决了。但是,如果您提供任何可能更优雅的解决方案,我将不胜感激,例如使用 MeasureOverride
/ArrangeOverride
.
根据您的要求,下面是一个自定义面板,我认为它应该可以满足您的需求。它以从下到上然后从右到左的方式排列子元素,在一列中最多堆叠 MaxRows
个元素(如果是 null
,则堆叠所有元素)。所有插槽的大小都相同。它还确实考虑了元素的 Visibility
值,即如果一个项目是 Hidden
,它会留下一个空槽,如果它是 Collapsed
,它会被跳过并且下一个元素 "jumps" 到位。
public class NotificationWrapPanel : Panel
{
public static readonly DependencyProperty MaxRowsProperty =
DependencyProperty.Register(
name: nameof(MaxRows),
propertyType: typeof(int?),
ownerType: typeof(NotificationWrapPanel),
typeMetadata: new FrameworkPropertyMetadata
{
AffectsArrange = true,
AffectsMeasure = true,
},
validateValueCallback: o => o == null || (int)o >= 1);
public int? MaxRows
{
get { return (int?)GetValue(MaxRowsProperty); }
set { SetValue(MaxRowsProperty, value); }
}
protected override Size MeasureOverride(Size constraint)
{
var children = InternalChildren
.Cast<UIElement>()
.Where(e => e.Visibility != Visibility.Collapsed)
.ToList();
if (children.Count == 0) return new Size();
var rows = MaxRows.HasValue ?
Math.Min(MaxRows.Value, children.Count) :
children.Count;
var columns = children.Count / rows +
Math.Sign(children.Count % rows);
var childConstraint = new Size
{
Width = constraint.Width / columns,
Height = constraint.Height / rows,
};
foreach (UIElement child in children)
child.Measure(childConstraint);
return new Size
{
Height = rows * children
.Cast<UIElement>()
.Max(e => e.DesiredSize.Height),
Width = columns * children
.Cast<UIElement>()
.Max(e => e.DesiredSize.Width),
};
}
protected override Size ArrangeOverride(Size finalSize)
{
var children = InternalChildren
.Cast<UIElement>()
.Where(e => e.Visibility != Visibility.Collapsed)
.ToList();
if (children.Count == 0) return finalSize;
var rows = MaxRows.HasValue ?
Math.Min(MaxRows.Value, children.Count) :
children.Count;
var columns = children.Count / rows +
Math.Sign(children.Count % rows);
var childSize = new Size
{
Width = finalSize.Width / columns,
Height = finalSize.Height / rows
};
for (int i = 0; i < children.Count; i++)
{
var row = i % rows; //rows are numbered bottom-to-top
var col = i / rows; //columns are numbered right-to-left
var location = new Point
{
X = finalSize.Width - (col + 1) * childSize.Width,
Y = finalSize.Height - (row + 1) * childSize.Height,
};
children[i].Arrange(new Rect(location, childSize));
}
return finalSize;
}
}