这是我阅读《Silverlight5 in Action》中一部分的笔记整理,原著中的代码有部分错误,而且注释不多,其中有些细节部分我也没搞太清楚。先做个笔记留作以后查看。
这里的实例是构建一个轨道布局的Panel,就是Panel中的所有控件是分布在一个圆形轨道上的。最终效果如下:
构建自定义布局,肯定需要先理解布局系统的工作原理,然后才能去构建。布局面板需要经过两个阶段才能完成布局,分别是测量阶段和排列阶段。布局面板的基类Panel提供了MeasureOverride和ArrangeOverride两个方法,供子类继承实现特定的布局行为。 在测量布局阶段,会对面板中Children集合InternalChildren的每个子元素(child)进行计算,测量大小。此过程是通过调用child的Measure方法来完成。 在排列布局阶段,同样会对面板中Children集合InteralChildren的每个元素调用Arrange放来完成。
首先定义自己的布局类:
在布局类中首先是定义属性,包括依赖属性和附加属性定义的方法。然后是重写MeasureOverride和ArrangeOverride方法。
代码语言:javascript复制namespace ControlsLib
{
public class OrbitPanel : Panel
{
/// <summary>
/// 这是一个依赖属性,表示的是OribtPanel中轨道(Orbit)的个数
/// </summary>
/// <value>
/// The number of orbits.
/// </value>
public int Orbits
{
get { return (int)GetValue(OrbitsProperty); }
set { SetValue(OrbitsProperty, value); }
}
/// <summary>
/// 用DependencyProperty的Register方法注册依赖属性OrbitsProperty
/// </summary>
public static readonly DependencyProperty OrbitsProperty = DependencyProperty.Register("Orbits", typeof(int), typeof(OrbitPanel), new PropertyMetadata(1, OnOrbitsChanged));
public static void OnOrbitsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((int)e.NewValue < 1)
{
throw new ArgumentException("Orbits must be greater than or equal to 1");
}
}
//The Orbit attached property in the OrbitPanel class
//GetOrbit和SetOrbit定义OrbitPanel的附加属性(Attached Property)Orbit
//Orbit表示控件位于第几个轨道
public static int GetOrbit(DependencyObject obj)
{
return (int)obj.GetValue(OrbitProperty);
}
public static void SetOrbit(DependencyObject obj, int value)
{
obj.SetValue(OrbitProperty, value);
}
/// <summary>
/// 依赖属性Orbit利用DependencyProperty提供的RegisterAttached的方法进行注册
/// </summary>
public static readonly DependencyProperty OrbitProperty = DependencyProperty.RegisterAttached("Orbit", typeof(int), typeof(OrbitPanel), new PropertyMetadata(0));
private double CalculateOrbitSpacing(Size availableSize)
{
double constrainingSize = Math.Min(availableSize.Width, availableSize.Height);
double space = constrainingSize / 2;
return space / Orbits;
}
private List<UIElement>[] SortElements()
{
var list = new List<UIElement>[Orbits];
for (int i = 0; i < Orbits; i )
{
if (i == Orbits - 1)
{
list[i] = (from UIElement child in Children where GetOrbit(child) >= i select child).ToList<UIElement>();
}
else
{
list[i] = (from UIElement child in Children where GetOrbit(child) == i select child).ToList<UIElement>();
}
}
return list;
}
protected override Size MeasureOverride(Size availableSize)
{
var sortedItems = SortElements();
double max = 0.0;
foreach (List<UIElement> orbitItems in sortedItems)
{
if (orbitItems.Count > 0)
{
foreach (UIElement element in orbitItems)
{
element.Measure(availableSize);
if (element.DesiredSize.Width > max)
{
max = element.DesiredSize.Width;
}
if (element.DesiredSize.Height > max)
{
max = element.DesiredSize.Height;
}
}
}
}
Size desiredSize = new Size(max * Orbits * 2, max * Orbits * 2);
if (Double.IsInfinity(availableSize.Height) || Double.IsInfinity(availableSize.Width))
{
return desiredSize;
}
else
{
return availableSize;
}
}
protected override Size ArrangeOverride(Size finalSize)
{
var sortedItems = SortElements();
double orbitSpacing = CalculateOrbitSpacing(finalSize);
for (int i = 0; i < sortedItems.Length; i )
{
List<UIElement> orbitItems = sortedItems[i];
int count = orbitItems.Count;
if (count > 0)
{
//计算每个轨道的周长
double circumference = 2 * Math.PI * orbitSpacing * (i 1);
double slotSize = Math.Min(orbitSpacing, circumference / count);
double maxSize = Math.Min(orbitSpacing, slotSize);
double angleIncrement = 360 / count;
double currentAngle = 0;
Point centerPoint = new Point(finalSize.Width / 2, finalSize.Height / 2);
for (int j = 0; j < orbitItems.Count; j )
{
UIElement element = orbitItems[j];
double angle = Math.PI / 180 * (currentAngle - 90);
double left = orbitSpacing * (i 1) * Math.Cos(angle);
double top = orbitSpacing * (i 1) * Math.Sin(angle);
Rect finalRect = new Rect(centerPoint.X left - element.DesiredSize.Width / 2, centerPoint.Y top - element.DesiredSize.Height / 2, element.DesiredSize.Width, element.DesiredSize.Height);
element.Arrange(finalRect);
currentAngle = angleIncrement;
}
}
}
return base.ArrangeOverride(finalSize);
}
}
}
测试代码如下:
代码语言:javascript复制<UserControl x:Class="CustomControls.MainPage"
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:clib="clr-namespace:ControlsLib;assembly=ControlsLib"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.Resources>
<Style TargetType="Button">
<Setter Property="Width" Value="100" />
<Setter Property="Height" Value="30" />
</Style>
</Grid.Resources>
<clib:OrbitPanel Orbits="3">
<Button Content="Button 1" Background="Orange"
clib:OrbitPanel.Orbit="0" />
<Button Content="Button 2" Background="Orange"
clib:OrbitPanel.Orbit="0" />
<Button Content="Button 3" Background="Orange"
clib:OrbitPanel.Orbit="0" />
<Button Content="Button 4" Background="Orange"
clib:OrbitPanel.Orbit="0" />
<Button Content="Button 5" Background="Orange"
clib:OrbitPanel.Orbit="0" />
<Button Content="Button 6" Background="Orange"
clib:OrbitPanel.Orbit="0" />
<Button Content="Button 7" Background="Orange"
clib:OrbitPanel.Orbit="0" />
<Button Content="Button 8" Background="Orange"
clib:OrbitPanel.Orbit="0" />
<Button Content="Button 9" Background="Orange"
clib:OrbitPanel.Orbit="0" />
<Button Content="Button 10" Background="Blue"
clib:OrbitPanel.Orbit="1" />
<Button Content="Button 11" Background="Blue"
clib:OrbitPanel.Orbit="1" />
<Button Content="Button 12" Background="Blue"
clib:OrbitPanel.Orbit="1" />
<Button Content="Button 13" Background="Blue"
clib:OrbitPanel.Orbit="1" />
<Button Content="Button 14" Background="Blue"
clib:OrbitPanel.Orbit="1" />
</clib:OrbitPanel>
</Grid>
</UserControl>