Silverlight:利用Panel实现自定义布局

虽然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 >

 

运行效果图:
Silverlight:利用Panel实现自定义布局
上面我们把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

你可能感兴趣的:(silverlight)