1 新建wpf项目
2 新建wpf UserControl类库
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace CustomControl
{
public class WrapBreakPanel : Panel
{
public static bool GetLineBreakBefore(DependencyObject obj)
{
return (bool)obj.GetValue(LineBreakBeforeProperty);
}
public static void SetLineBreakBefore(DependencyObject obj, bool value)
{
obj.SetValue(LineBreakBeforeProperty, value);
}
public static readonly DependencyProperty LineBreakBeforeProperty;
static WrapBreakPanel()
{
FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata();
metadata.AffectsArrange = true;
metadata.AffectsMeasure = true;
LineBreakBeforeProperty =
DependencyProperty.RegisterAttached("LineBreakBefore", typeof(bool), typeof(WrapBreakPanel), metadata);
}
protected override Size MeasureOverride(Size availableSize)
{
Size currentLineSize = new Size();
Size panelSize = new Size();
foreach (UIElement element in base.InternalChildren)
{
element.Measure(availableSize);
Size desiredSize = element.DesiredSize;
if (GetLineBreakBefore(element) ||
currentLineSize.Width + desiredSize.Width > availableSize.Width)
{
panelSize.Width = Math.Max(currentLineSize.Width, panelSize.Width);
panelSize.Height += currentLineSize.Height;
currentLineSize = desiredSize;
if (desiredSize.Width > availableSize.Width)
{
panelSize.Width = Math.Max(desiredSize.Width, panelSize.Width);
panelSize.Height += desiredSize.Height;
currentLineSize = new Size();
}
}
else
{
currentLineSize.Width += desiredSize.Width;
currentLineSize.Height = Math.Max(desiredSize.Height, currentLineSize.Height);
}
}
panelSize.Width = Math.Max(currentLineSize.Width, panelSize.Width);
panelSize.Height += currentLineSize.Height;
return panelSize;
}
protected override Size ArrangeOverride(Size arrangeBounds)
{
int firstInLine = 0;
Size currentLineSize = new Size();
double accumulatedHeight = 0;
UIElementCollection elements = base.InternalChildren;
for (int i = 0; i < elements.Count; i++)
{
Size desiredSize = elements[i].DesiredSize;
if (GetLineBreakBefore(elements[i]) || currentLineSize.Width + desiredSize.Width > arrangeBounds.Width) //need to switch to another line
{
arrangeLine(accumulatedHeight, currentLineSize.Height, firstInLine, i);
accumulatedHeight += currentLineSize.Height;
currentLineSize = desiredSize;
if (desiredSize.Width > arrangeBounds.Width)
{
arrangeLine(accumulatedHeight, desiredSize.Height, i, ++i);
accumulatedHeight += desiredSize.Height;
currentLineSize = new Size();
}
firstInLine = i;
}
else //continue to accumulate a line
{
currentLineSize.Width += desiredSize.Width;
currentLineSize.Height = Math.Max(desiredSize.Height, currentLineSize.Height);
}
}
if (firstInLine < elements.Count)
arrangeLine(accumulatedHeight, currentLineSize.Height, firstInLine, elements.Count);
return arrangeBounds;
}
private void arrangeLine(double y, double lineHeight, int start, int end)
{
double x = 0;
UIElementCollection children = InternalChildren;
for (int i = start; i < end; i++)
{
UIElement child = children[i];
child.Arrange(new Rect(x, y, child.DesiredSize.Width, lineHeight));
x += child.DesiredSize.Width;
}
}
}
}
3 主程序
<Window x:Class="CustomControlApp.WrapBreakPanelTest"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:CustomControlApp"
xmlns:lib="clr-namespace:CustomControl;assembly=CustomControl"
mc:Ignorable="d"
Title="WrapBreakPanelTest" Height="450" Width="800">
<Grid>
<StackPanel>
<StackPanel.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Margin" Value="3"></Setter>
<Setter Property="Padding" Value="3"></Setter>
</Style>
</StackPanel.Resources>
<TextBlock Padding="5" Background="LightGray">Content above the WrapBreakPanel.</TextBlock>
<lib:WrapBreakPanel>
<Button>No Break Here</Button>
<Button>No Break Here</Button>
<Button>No Break Here</Button>
<Button>No Break Here</Button>
<Button lib:WrapBreakPanel.LineBreakBefore="True" FontWeight="Bold">Button with Break</Button>
<Button>No Break Here</Button>
<Button>No Break Here</Button>
<Button>No Break Here</Button>
<Button>No Break Here</Button>
</lib:WrapBreakPanel>
<TextBlock Padding="5" Background="LightGray">Content below the WrapBreakPanel.</TextBlock>
</StackPanel>
</Grid>
</Window>