最近需要实现一个立方体的功能,刚开始用的第三方控件Telerik中的RadCube,但是发现很难用,似乎长高宽都要设置为一致,而且不能
控制立方体转动的方式,所以自己想写个组件来实现。去看了下Silverlight的组件化,就开始动手了。
Silverlight的组件化,需要2个文件一个是组建的类文件:MyCube.cs,另外一个是Generic.xaml文件。
MyCube.cs
![ContractedBlock.gif](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
using
System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace Tecamo.Silverlight
{
[TemplatePart(Name = MyCube.RootElement, Type = typeof (FrameworkElement))]
[TemplatePart(Name = MyCube.segment1, Type = typeof (Grid))]
[TemplatePart(Name = MyCube.segment2, Type = typeof (Grid))]
[TemplatePart(Name = MyCube.segment3, Type = typeof (Grid))]
[TemplatePart(Name = MyCube.storyboard, Type = typeof (Storyboard))]
public class MyCube : ContentControl
{
private const string RootElement = " RootElement " ;
private const string segment1 = " segment1 " ;
private const string segment2 = " segment2 " ;
private const string segment3 = " segment3 " ;
private const string storyboard = " storyboard " ;
private FrameworkElement _root;
private Grid _segment1;
private Grid _segment2;
private Grid _segment3;
private Storyboard _storyboard;
private DoubleAnimation _DAnimation1;
private DoubleAnimation _DAnimation2;
private DoubleAnimation _DAnimation3;
private PlaneProjection _projection1;
private PlaneProjection _projection2;
private PlaneProjection _projection3;
public UIElement Slide1 { get ; set ; }
public UIElement Slide2 { get ; set ; }
public UIElement Slide3 { get ; set ; }
private int temp;
private bool noMoreTriggers;
public MyCube()
{
DefaultStyleKey = typeof (MyCube);
noMoreTriggers = false ;
temp = 1 ;
this .SizeChanged += new SizeChangedEventHandler(MyCube_SizeChanged);
this .Loaded += new RoutedEventHandler(MyCube_Loaded);
}
void MyCube_Loaded( object sender, RoutedEventArgs e)
{
double d = ( this ._root.ActualWidth) * 0.2885 ;
this ._projection1.CenterOfRotationZ = - d;
this ._projection2.CenterOfRotationZ = - d;
this ._projection3.CenterOfRotationZ = - d;
}
void MyCube_SizeChanged( object sender, SizeChangedEventArgs e)
{
double d = ( this ._root.ActualWidth) * 0.2885 ;
this ._projection1.CenterOfRotationZ = - d;
this ._projection2.CenterOfRotationZ = - d;
this ._projection3.CenterOfRotationZ = - d;
}
public void Spin( int x, int y)
{
if ( this .noMoreTriggers)
return ;
noMoreTriggers = true ;
temp = y;
if ((x - y) == 1 || (x - y) == - 2 )
{
this ._DAnimation1.By = - 120 ;
this ._DAnimation2.By = - 120 ;
this ._DAnimation3.By = - 120 ;
}
else if ((x - y) == - 1 || (x - y) == 2 )
{
this ._DAnimation1.By = 120 ;
this ._DAnimation2.By = 120 ;
this ._DAnimation3.By = 120 ;
}
switch (y)
{
case 1 :
Canvas.SetZIndex( this ._segment1, 2 );
break ;
case 2 :
Canvas.SetZIndex( this ._segment2, 2 );
break ;
case 3 :
Canvas.SetZIndex( this ._segment3, 2 );
break ;
default :
break ;
}
this ._storyboard.Begin();
}
private void OnStoryboardCompleted1( object sender, EventArgs args)
{
this .noMoreTriggers = false ;
}
public override void OnApplyTemplate()
{
base .OnApplyTemplate();
_root = (FrameworkElement)GetTemplateChild(MyCube.RootElement);
_segment1 = (Grid)GetTemplateChild(MyCube.segment1);
_segment2 = (Grid)GetTemplateChild(MyCube.segment2);
_segment3 = (Grid)GetTemplateChild(MyCube.segment3);
if (_segment1 != null )
{
Canvas.SetZIndex(_segment1, 2 );
_projection1 = new PlaneProjection();
_segment1.Projection = _projection1;
_DAnimation1 = new DoubleAnimation();
_DAnimation1.By = 120 ;
Storyboard.SetTarget(_DAnimation1, _projection1);
Storyboard.SetTargetProperty(_DAnimation1, new PropertyPath(PlaneProjection.RotationYProperty));
if (_segment1.Children.Count > 0 )
this .Slide1 = _segment1.Children[ 0 ];
}
if (_segment2 != null )
{
Canvas.SetZIndex(_segment2, 1 );
_projection2 = new PlaneProjection();
_projection2.RotationY = - 120 ;
_segment2.Projection = _projection2;
_DAnimation2 = new DoubleAnimation();
_DAnimation2.By = 120 ;
Storyboard.SetTarget(_DAnimation2, _projection2);
Storyboard.SetTargetProperty(_DAnimation2, new PropertyPath(PlaneProjection.RotationYProperty));
if (_segment2.Children.Count > 0 )
this .Slide2 = _segment2.Children[ 0 ];
}
if (_segment3 != null )
{
Canvas.SetZIndex(_segment3, 0 );
_projection3 = new PlaneProjection();
_projection3.RotationY = 120 ;
_segment3.Projection = _projection3;
_DAnimation3 = new DoubleAnimation();
_DAnimation3.By = 120 ;
Storyboard.SetTarget(_DAnimation3, _projection3);
Storyboard.SetTargetProperty(_DAnimation3, new PropertyPath(PlaneProjection.RotationYProperty));
if (_segment3.Children.Count > 0 )
this .Slide3 = _segment3.Children[ 0 ];
}
if (_root != null )
{
_storyboard = new Storyboard();
_storyboard.Children.Clear();
if (_DAnimation1 != null )
_storyboard.Children.Add(_DAnimation1);
if (_DAnimation2 != null )
_storyboard.Children.Add(_DAnimation2);
if (_DAnimation3 != null )
_storyboard.Children.Add(_DAnimation3);
this ._storyboard.Completed += new EventHandler( this .OnStoryboardCompleted1);
}
}
}
}
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace Tecamo.Silverlight
{
[TemplatePart(Name = MyCube.RootElement, Type = typeof (FrameworkElement))]
[TemplatePart(Name = MyCube.segment1, Type = typeof (Grid))]
[TemplatePart(Name = MyCube.segment2, Type = typeof (Grid))]
[TemplatePart(Name = MyCube.segment3, Type = typeof (Grid))]
[TemplatePart(Name = MyCube.storyboard, Type = typeof (Storyboard))]
public class MyCube : ContentControl
{
private const string RootElement = " RootElement " ;
private const string segment1 = " segment1 " ;
private const string segment2 = " segment2 " ;
private const string segment3 = " segment3 " ;
private const string storyboard = " storyboard " ;
private FrameworkElement _root;
private Grid _segment1;
private Grid _segment2;
private Grid _segment3;
private Storyboard _storyboard;
private DoubleAnimation _DAnimation1;
private DoubleAnimation _DAnimation2;
private DoubleAnimation _DAnimation3;
private PlaneProjection _projection1;
private PlaneProjection _projection2;
private PlaneProjection _projection3;
public UIElement Slide1 { get ; set ; }
public UIElement Slide2 { get ; set ; }
public UIElement Slide3 { get ; set ; }
private int temp;
private bool noMoreTriggers;
public MyCube()
{
DefaultStyleKey = typeof (MyCube);
noMoreTriggers = false ;
temp = 1 ;
this .SizeChanged += new SizeChangedEventHandler(MyCube_SizeChanged);
this .Loaded += new RoutedEventHandler(MyCube_Loaded);
}
void MyCube_Loaded( object sender, RoutedEventArgs e)
{
double d = ( this ._root.ActualWidth) * 0.2885 ;
this ._projection1.CenterOfRotationZ = - d;
this ._projection2.CenterOfRotationZ = - d;
this ._projection3.CenterOfRotationZ = - d;
}
void MyCube_SizeChanged( object sender, SizeChangedEventArgs e)
{
double d = ( this ._root.ActualWidth) * 0.2885 ;
this ._projection1.CenterOfRotationZ = - d;
this ._projection2.CenterOfRotationZ = - d;
this ._projection3.CenterOfRotationZ = - d;
}
public void Spin( int x, int y)
{
if ( this .noMoreTriggers)
return ;
noMoreTriggers = true ;
temp = y;
if ((x - y) == 1 || (x - y) == - 2 )
{
this ._DAnimation1.By = - 120 ;
this ._DAnimation2.By = - 120 ;
this ._DAnimation3.By = - 120 ;
}
else if ((x - y) == - 1 || (x - y) == 2 )
{
this ._DAnimation1.By = 120 ;
this ._DAnimation2.By = 120 ;
this ._DAnimation3.By = 120 ;
}
switch (y)
{
case 1 :
Canvas.SetZIndex( this ._segment1, 2 );
break ;
case 2 :
Canvas.SetZIndex( this ._segment2, 2 );
break ;
case 3 :
Canvas.SetZIndex( this ._segment3, 2 );
break ;
default :
break ;
}
this ._storyboard.Begin();
}
private void OnStoryboardCompleted1( object sender, EventArgs args)
{
this .noMoreTriggers = false ;
}
public override void OnApplyTemplate()
{
base .OnApplyTemplate();
_root = (FrameworkElement)GetTemplateChild(MyCube.RootElement);
_segment1 = (Grid)GetTemplateChild(MyCube.segment1);
_segment2 = (Grid)GetTemplateChild(MyCube.segment2);
_segment3 = (Grid)GetTemplateChild(MyCube.segment3);
if (_segment1 != null )
{
Canvas.SetZIndex(_segment1, 2 );
_projection1 = new PlaneProjection();
_segment1.Projection = _projection1;
_DAnimation1 = new DoubleAnimation();
_DAnimation1.By = 120 ;
Storyboard.SetTarget(_DAnimation1, _projection1);
Storyboard.SetTargetProperty(_DAnimation1, new PropertyPath(PlaneProjection.RotationYProperty));
if (_segment1.Children.Count > 0 )
this .Slide1 = _segment1.Children[ 0 ];
}
if (_segment2 != null )
{
Canvas.SetZIndex(_segment2, 1 );
_projection2 = new PlaneProjection();
_projection2.RotationY = - 120 ;
_segment2.Projection = _projection2;
_DAnimation2 = new DoubleAnimation();
_DAnimation2.By = 120 ;
Storyboard.SetTarget(_DAnimation2, _projection2);
Storyboard.SetTargetProperty(_DAnimation2, new PropertyPath(PlaneProjection.RotationYProperty));
if (_segment2.Children.Count > 0 )
this .Slide2 = _segment2.Children[ 0 ];
}
if (_segment3 != null )
{
Canvas.SetZIndex(_segment3, 0 );
_projection3 = new PlaneProjection();
_projection3.RotationY = 120 ;
_segment3.Projection = _projection3;
_DAnimation3 = new DoubleAnimation();
_DAnimation3.By = 120 ;
Storyboard.SetTarget(_DAnimation3, _projection3);
Storyboard.SetTargetProperty(_DAnimation3, new PropertyPath(PlaneProjection.RotationYProperty));
if (_segment3.Children.Count > 0 )
this .Slide3 = _segment3.Children[ 0 ];
}
if (_root != null )
{
_storyboard = new Storyboard();
_storyboard.Children.Clear();
if (_DAnimation1 != null )
_storyboard.Children.Add(_DAnimation1);
if (_DAnimation2 != null )
_storyboard.Children.Add(_DAnimation2);
if (_DAnimation3 != null )
_storyboard.Children.Add(_DAnimation3);
this ._storyboard.Completed += new EventHandler( this .OnStoryboardCompleted1);
}
}
}
}
Generic.xaml
![ContractedBlock.gif](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
<
ResourceDictionary
xmlns ="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mycube ="clr-namespace:Tecamo.Silverlight;assembly=Tecamo.Silverlight" >
< Style TargetType ="mycube:MyCube" >
< Setter Property ="Width" Value ="380" />
< Setter Property ="Height" Value ="200" />
< Setter Property ="Template" >
< Setter.Value >
< ControlTemplate TargetType ="mycube:MyCube" >
< Grid x:Name ="RootElement" Background =" {TemplateBinding Background} "
Width =" {TemplateBinding Width} " Height =" {TemplateBinding Height} " >
< Grid Name ="segment1"
Background =" {TemplateBinding Background} " >
</ Grid >
< Grid Name ="segment2"
Background =" {TemplateBinding Background} " >
</ Grid >
< Grid Name ="segment3"
Background =" {TemplateBinding Background} " >
</ Grid >
</ Grid >
</ ControlTemplate >
</ Setter.Value >
</ Setter >
</ Style >
</ ResourceDictionary >
xmlns ="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mycube ="clr-namespace:Tecamo.Silverlight;assembly=Tecamo.Silverlight" >
< Style TargetType ="mycube:MyCube" >
< Setter Property ="Width" Value ="380" />
< Setter Property ="Height" Value ="200" />
< Setter Property ="Template" >
< Setter.Value >
< ControlTemplate TargetType ="mycube:MyCube" >
< Grid x:Name ="RootElement" Background =" {TemplateBinding Background} "
Width =" {TemplateBinding Width} " Height =" {TemplateBinding Height} " >
< Grid Name ="segment1"
Background =" {TemplateBinding Background} " >
</ Grid >
< Grid Name ="segment2"
Background =" {TemplateBinding Background} " >
</ Grid >
< Grid Name ="segment3"
Background =" {TemplateBinding Background} " >
</ Grid >
</ Grid >
</ ControlTemplate >
</ Setter.Value >
</ Setter >
</ Style >
</ ResourceDictionary >
MyCube.cs中写了个MyCube类,写了个OnApplyTemplate方法,当在其他的xaml中实现这个MyCube类的时候会调用到这个方法。
![ContractedBlock.gif](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
<
cube:MyCube
x:Name
="MyCube1"
Margin
="5,35,5,5"
>
< cube:MyCube.Template >
< ControlTemplate >
< Grid x:Name ="RootElement" >
< Grid x:Name ="segment1" Background ="#F5BAD5C2" >
< Button />
</ Grid >
< Grid x:Name ="segment2" Background ="#F5BAD5C2" >
< TextBlock />
</ Grid >
< Grid x:Name ="segment3" Background ="#F5BAD5C2" >
< Border /> </ Grid >
</ Grid >
</ ControlTemplate >
</ cube:MyCube.Template >
</ cube:MyCube >
< cube:MyCube.Template >
< ControlTemplate >
< Grid x:Name ="RootElement" >
< Grid x:Name ="segment1" Background ="#F5BAD5C2" >
< Button />
</ Grid >
< Grid x:Name ="segment2" Background ="#F5BAD5C2" >
< TextBlock />
</ Grid >
< Grid x:Name ="segment3" Background ="#F5BAD5C2" >
< Border /> </ Grid >
</ Grid >
</ ControlTemplate >
</ cube:MyCube.Template >
</ cube:MyCube >
通过_root = (FrameworkElement)GetTemplateChild(MyCube.RootElement);就可以取到这个Grid,当然也能取到它的Children。
segment1、segment2、segment3为这个控件的三个面,当然也可以是四个面,六个面,三个面的代码写起来最方便,所以我写的控件是个三棱柱。
通过设置这三个Grid的Projection属性,可以设置其RotationY属性,就是Y轴上的旋转角度,通过控制这个属性来做三棱柱的转动的动画。当然还需要设置Canvas.SetZIndex属性,这个属性决定哪个面显示在其他2个面的上方,值最大的在最上方。
另外还需要设置CenterOfRotationZ属性,这个是每个面离围绕的旋转轴的距离,这个属性只与三棱柱的每个面的宽有关,具体的运算公式为
CenterOfRotationZ=Width*0.2885 至于这个公式怎么来的,因为我数学很惨不忍睹,所以不知道这个公式的数学原理,其中Width为每个面的宽度。
而且在每次 控件的每个面的宽度发生变化时都要重新计算CenterOfRotationZ。要控制其旋转只要调用 Spin(1,2)方法就行了,带的参数为三棱柱的面
的编号,Spin(1,2)则是从第1面转动到第2面。
这样就大致实现所需要的功能了,这里可以看下示例
在线演示
但是在用到这个控件的时候格式必须为
![ContractedBlock.gif](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
<
cube:MyCube
x:Name
="MyCube1"
Margin
="5,35,5,5"
>
< cube:MyCube.Template >
< ControlTemplate >
< Grid x:Name ="RootElement" >
< Grid x:Name ="segment1" Background ="#F5BAD5C2" >
< Button />
</ Grid >
< Grid x:Name ="segment2" Background ="#F5BAD5C2" >
< TextBlock />
</ Grid >
< Grid x:Name ="segment3" Background ="#F5BAD5C2" >
< Border /> </ Grid >
</ Grid >
</ ControlTemplate >
</ cube:MyCube.Template >
</ cube:MyCube >
< cube:MyCube.Template >
< ControlTemplate >
< Grid x:Name ="RootElement" >
< Grid x:Name ="segment1" Background ="#F5BAD5C2" >
< Button />
</ Grid >
< Grid x:Name ="segment2" Background ="#F5BAD5C2" >
< TextBlock />
</ Grid >
< Grid x:Name ="segment3" Background ="#F5BAD5C2" >
< Border /> </ Grid >
</ Grid >
</ ControlTemplate >
</ cube:MyCube.Template >
</ cube:MyCube >
如果想像用其他控件那样方便,就要写UI自动化了,我简单看了下,没什么头绪,这里还请知道的朋友解决下这个问题。另外,写在MyCube 中的子元素,
就算命名了业不能在后台代码中设置这个子元素,所以我写了写个Public UIElement属性来get;set;这个也是写 了 UI自动化就能解决的。