Silverlight自定义控件
最近需要实现一个立方体的功能,刚开始用的第三方控件Telerik中的RadCube,但是发现很难用,似乎长高宽都要设置为一致,而且不能
控制立方体转动的方式,所以自己想写个组件来实现。去看了下Silverlight的组件化,就开始动手了。
Silverlight的组件化,需要2个文件一个是组建的类文件:MyCube.cs,另外一个是Generic.xaml文件。
MyCube.cs
代码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);
}
}
}
}
Generic.xaml
代码
<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类的时候会调用到这个方法。
代码
<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>
通过_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面。
这样就大致实现所需要的功能了,这里可以看下示例
但是在用到这个控件的时候格式必须为
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.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>
如果想像用其他控件那样方便,就要写UI自动化了,我简单看了下,没什么头绪,这里还请知道的朋友解决下这个问题。另外,写在MyCube 中的子元素,
就算命名了业不能在后台代码中设置这个子元素,所以我写了写个Public UIElement属性来get;set;这个也是写 了 UI自动化就能解决的。