虽然Silverlight提供了几种基本的布局方式,比如Canvas,Grid,StackPanel,Border...,但有时候可能仍然会觉得不够用。
这时候我们可以通过继承Panel,并重写MeasureOverride 和ArrangeOverride 方法,以实现自己的布局,事实上Canvas,Grid,StackPanel就是继承自Panel,并自行实现这二个方法实现的。
布局过程中,有二个关键的步骤:测量和排列子元素,正好对应MeasureOverride 与ArrangeOverride 二个方法.
MeasureOverride 中必须遍历所有子元素,并调用子元素的Measure 方法,以便让布局系统确定每个子元素的 DesiredSize(即:子元素自身希望占据的空间大小),这是在调用 Measure 之后在系统内部发生的(开发人员无法干预),该值将在后面排列过程期间使用。
ArrangeOverride 中同样也必须遍历所有子元素,并调用子元素的Arrange方法,以便让布局系统会告诉 Panel 可用于它及其子元素的 finalSize(即最终实际占据的空间大小)。
下面的示例,修改自SL3的官方文档,它将一个400 * 400大小的Panel,划分成16个小单元格(每个正好100*100),放置在其中的元素,将自动依次填充到这些单元格的正中央,如果元素多于16个,将被忽略。
MyPanel.cs代码:
代码
using
System.Windows;
using
System.Windows.Controls;
using
System.Windows.Media;
using
System;
namespace
CustomPanel
{
public
class
MyPanel : Panel
{
const
int
_CellSize
=
100
;
//
每个单元格宽高均为100
const
int
_CellCount
=
4
;
//
4*4的正方形单元格
public
MyPanel() :
base
()
{
}
///
<summary>
///
先测量
///
</summary>
///
<param name="availableSize"></param>
///
<returns></returns>
protected
override
Size MeasureOverride(Size availableSize)
{
for
(
int
i
=
0
; i
<
this
.Children.Count; i
++
)
{
if
(i
<
_CellCount
*
_CellCount)
{
this
.Children[i].Measure(
new
Size(_CellSize, _CellSize));
}
else
{
//
this.Children[i].Measure(new Size(0, 0));
//
超出元素不分配空间,其实这行也可以不要,因为ArrangeOverride中对超出元素最终并未安排空间
}
}
return
availableSize;
}
///
<summary>
///
再排列
///
</summary>
///
<param name="finalSize"></param>
///
<returns></returns>
protected
override
Size ArrangeOverride(Size finalSize)
{
for
(
int
i
=
0
; i
<
this
.Children.Count; i
++
)
{
if
(i
<
_CellCount
*
_CellCount)
{
Point cellOrigin
=
GetCellOriginPoint(i, _CellCount,
new
Size(_CellSize, _CellSize));
double
dw
=
this
.Children[i].DesiredSize.Width;
double
dh
=
this
.Children[i].DesiredSize.Height;
this
.Children[i].Arrange(
new
Rect(cellOrigin.X
+
(_CellSize
-
dw)
/
2
, cellOrigin.Y
+
(_CellSize
-
dh)
/
2
, dw, dh));
//
每个子元素都放在单元格正中央
}
else
{
this
.Children[i].Arrange(
new
Rect(
0
,
0
,
0
,
0
));
//
超出的元素,不安排空间(即不显示)
}
}
return
new
Size(_CellSize
*
_CellCount, _CellSize
*
_CellCount);
//
下面这二行,演示了如何将可视区域设置为仅100*100大小
//
this.Clip = new RectangleGeometry() { Rect = new Rect(0,0,100,100) };
//
return new Size(100, 100);
}
///
<summary>
///
取得每个单元格的(左上)起始点坐标
///
</summary>
///
<param name="cellIndex"></param>
///
<param name="cellCount"></param>
///
<param name="itemSize"></param>
///
<returns></returns>
protected
Point GetCellOriginPoint(
int
cellIndex,
int
cellCount, Size itemSize)
{
int
row
=
(
int
)Math.Floor(cellIndex
/
cellCount);
int
col
=
cellIndex
-
cellCount
*
row;
Point origin
=
new
Point(itemSize.Width
*
col, itemSize.Height
*
row);
return
origin;
}
}
}
测试页MainPage.Xaml
代码
<
UserControl
x:Class
="CustomPanel.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:l
="clr-namespace:CustomPanel"
xmlns:mc
="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable
="d"
d:DesignWidth
="640"
d:DesignHeight
="480"
>
<
l:MyPanel
x:Name
="myPnl"
Background
="#FFEFEFEF"
HorizontalAlignment
="Center"
VerticalAlignment
="Center"
>
<
Button
Content
="01"
></
Button
>
<
Button
Content
="02"
></
Button
>
<
TextBlock
Text
="03"
></
TextBlock
>
<
Button
Content
="04"
></
Button
>
<
Button
Content
="05"
></
Button
>
<
TextBox
Text
="06"
></
TextBox
>
<
Button
Content
="07"
></
Button
>
<
Button
Content
="08"
></
Button
>
<
Button
Content
="09"
></
Button
>
<
Button
Content
="10"
></
Button
>
<
Button
Content
="11"
></
Button
>
<
Button
Content
="12"
></
Button
>
<
TextBlock
Text
="13"
></
TextBlock
>
<
Button
Content
="14"
></
Button
>
<
Button
Content
="15"
></
Button
>
<
Button
Content
="16"
></
Button
>
<
Button
Content
="17"
></
Button
>
</
l:MyPanel
>
</
UserControl
>
运行效果图:
上面我们把Panel的尺寸定死为400 * 400了,如果想实现自由扩展,还要稍微再修改一下:
自由扩展的4 * 4格Panel
using
System.Windows;
using
System.Windows.Controls;
using
System.Windows.Media;
using
System;
namespace
CustomPanel
{
public
class
MyPanel : Panel
{
int
_CellHeight
=
100
;
//
每个单元格的高度(初始为100)
int
_CellWidth
=
100
;
//
每个单元格的宽度(初始为100)
const
int
_CellCount
=
4
;
//
4*4的正方形单元格
public
MyPanel() :
base
()
{
}
///
<summary>
///
先测量
///
</summary>
///
<param name="availableSize"></param>
///
<returns></returns>
protected
override
Size MeasureOverride(Size availableSize)
{
//
重新每个单元格的尺寸
_CellWidth
=
(
int
)availableSize.Width
/
_CellCount;
_CellHeight
=
(
int
)availableSize.Height
/
_CellCount;
for
(
int
i
=
0
; i
<
this
.Children.Count; i
++
)
{
if
(i
<
_CellCount
*
_CellCount)
{
this
.Children[i].Measure(
new
Size(_CellWidth, _CellHeight));
}
else
{
//
this.Children[i].Measure(new Size(0, 0));
//
超出元素不分配空间,其实这行也可以不要,因为ArrangeOverride中对超出元素最终并未安排空间
}
}
return
availableSize;
}
///
<summary>
///
再排列
///
</summary>
///
<param name="finalSize"></param>
///
<returns></returns>
protected
override
Size ArrangeOverride(Size finalSize)
{
for
(
int
i
=
0
; i
<
this
.Children.Count; i
++
)
{
if
(i
<
_CellCount
*
_CellCount)
{
Point cellOrigin
=
GetCellOriginPoint(i, _CellCount,
new
Size(_CellWidth, _CellHeight));
double
dw
=
this
.Children[i].DesiredSize.Width;
double
dh
=
this
.Children[i].DesiredSize.Height;
this
.Children[i].Arrange(
new
Rect(cellOrigin.X
+
(_CellWidth
-
dw)
/
2
, cellOrigin.Y
+
(_CellHeight
-
dh)
/
2
, dw, dh));
//
每个子元素都放在单元格正中央
}
else
{
this
.Children[i].Arrange(
new
Rect(
0
,
0
,
0
,
0
));
//
超出的元素,不安排空间(即不显示)
}
}
return
new
Size(_CellWidth
*
_CellCount, _CellHeight
*
_CellCount);
//
下面这二行,演示了如何将可视区域设置为仅100*100大小
//
this.Clip = new RectangleGeometry() { Rect = new Rect(0,0,100,100) };
//
return new Size(100, 100);
}
///
<summary>
///
取得每个单元格的(左上)起始点坐标
///
</summary>
///
<param name="cellIndex"></param>
///
<param name="cellCount"></param>
///
<param name="itemSize"></param>
///
<returns></returns>
protected
Point GetCellOriginPoint(
int
cellIndex,
int
cellCount, Size itemSize)
{
int
row
=
(
int
)Math.Floor(cellIndex
/
cellCount);
int
col
=
cellIndex
-
cellCount
*
row;
Point origin
=
new
Point(itemSize.Width
*
col, itemSize.Height
*
row);
return
origin;
}
}
}
CodeProject上还有几篇扩展Panel的不错文章:
http://www.codeproject.com/KB/WPF/Panels.aspx 鱼眼效果布局的实现
http://www.codeproject.com/KB/silverlight/advancedcanvas.aspx 9宫格的实现
这一篇更强大:
http://dotnet.org.za/rudi/archive/2008/04/15/why-wpf-rocks-custom-layout-panel-showcase.aspx