Silverlight中的使用装饰器模式的变换操作
在模式设计中,使用装饰器模式来给现有的对象作为附加的功能添加到原对象中,亦即在不修改源对象的情况下给源对象添加一些特定的行为,达到我们丰富源对象功能的目的,幸好WPF的创始人给我们设计了相应的类型,我们可以直接使用诸如Adorner,AdornerLayer,AdornerDecorator等类型很容易地达到这个目的。很遗憾的是到目前为止,笔者还没有找到在微软的.net专家们为Silverlight 开发了类似的类库。于是我在借鉴了国外的一些开发专家的经验,设计了一个类库来达到这个目的,下面将这个例子分享大家。
自定义的RotateAndResizingLib.dll类库的目的是为了实现两个操作,正如名字所说的亦即旋转和缩放对象操作。
由于我们的目的是要对可视的元素实现这样的操作,我们构建一个Adorner的用户控件,这个一个继承自UserControl的一个类型:我们的想法是在源对象上加入一个两个不同形状的Thumb对象来实现旋转和缩放操作。这两个Thumb分别添加到源对象的左上角和右下角上。
一、定义XAML形状的样式
1、为了实现这个目的,我们先定义了这样两个Thumb的样式。一个是ResizingStyle样式,这个样式可生成如下的形状
相应的样式的代码如下:【加入到App.xaml中】
<Style x:Key="ResizingStyle" TargetType="Thumb">
<Setter Property="Background" Value="#FF1F3B53"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="BorderBrush">
<Setter.Value>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFA3AEB9" Offset="0"/>
<GradientStop Color="#FF8399A9" Offset="0.375"/>
<GradientStop Color="#FF718597" Offset="0.375"/>
<GradientStop Color="#FF617584" Offset="1"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Thumb">
<Grid Width="10" Height="10">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver">
<Storyboard>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="BackgroundAnimation"/>
<ColorAnimation Duration="0" To="#F2FFFFFF" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)" Storyboard.TargetName="BackgroundGradient"/>
<ColorAnimation Duration="0" To="#CCFFFFFF" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[2].(GradientStop.Color)" Storyboard.TargetName="BackgroundGradient"/>
<ColorAnimation Duration="0" To="#7FFFFFFF" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[3].(GradientStop.Color)" Storyboard.TargetName="BackgroundGradient"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ColorAnimation Duration="0" To="#FF6DBDD1" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" Storyboard.TargetName="Background"/>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="BackgroundAnimation"/>
<ColorAnimation Duration="0" To="#D8FFFFFF" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[0].(GradientStop.Color)" Storyboard.TargetName="BackgroundGradient"/>
<ColorAnimation Duration="0" To="#C6FFFFFF" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)" Storyboard.TargetName="BackgroundGradient"/>
<ColorAnimation Duration="0" To="#8CFFFFFF" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[2].(GradientStop.Color)" Storyboard.TargetName="BackgroundGradient"/>
<ColorAnimation Duration="0" To="#3FFFFFFF" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[3].(GradientStop.Color)" Storyboard.TargetName="BackgroundGradient"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<DoubleAnimation Duration="0" To=".55" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="DisabledVisualElement"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Focused">
<Storyboard>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="FocusVisualElement"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Unfocused"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border x:Name="Background" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="White" >
<Grid Background="{TemplateBinding Background}" Margin="1">
<Border x:Name="BackgroundAnimation" Background="#FF448DCA" Opacity="0"/>
<Rectangle x:Name="BackgroundGradient">
<Rectangle.Fill>
<LinearGradientBrush EndPoint=".7,1" StartPoint=".7,0">
<GradientStop Color="#FFFFFFFF" Offset="0"/>
<GradientStop Color="#F9FFFFFF" Offset="0.375"/>
<GradientStop Color="#E5FFFFFF" Offset="0.625"/>
<GradientStop Color="#C6FFFFFF" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Path Data="M0,1.3335 L1.3335,0 L1.3335,0.66675001 L3.75,0.66675001 L3.75,2.0002501 L1.3335,2.0002501 L1.3335,2.6670001 z" Fill="#99000000" HorizontalAlignment="Left" Height="2.667" Margin="-0.208,-0.011,0,0" RenderTransformOrigin="0.5,0.5" Stretch="Fill" UseLayoutRounding="False" VerticalAlignment="Top" Width="3.75" >
<Path.RenderTransform>
<CompositeTransform Rotation="40.311"/>
</Path.RenderTransform>
</Path>
<Path Data="M4.7309999,1.3335 L3.3975,0 L3.3975,0.66675001 L0,0.66675001 L0,2.0002501 L3.3975,2.0002501 L3.3975,2.6670001 z" Fill="#99000000" HorizontalAlignment="Right" Height="2.667" Margin="0,0,2.468,-1.301" RenderTransformOrigin="0.917,0.333" Stretch="Fill" UseLayoutRounding="False" VerticalAlignment="Bottom" Width="4.731">
<Path.RenderTransform>
<CompositeTransform Rotation="122.849"/>
</Path.RenderTransform>
</Path>
</Grid>
</Border>
<Rectangle x:Name="DisabledVisualElement" Fill="#FFFFFFFF" IsHitTestVisible="false" Opacity="0" />
<Rectangle x:Name="FocusVisualElement" IsHitTestVisible="false" Margin="1" Opacity="0" Stroke="#FF6DBDD1" StrokeThickness="1"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
2、实现旋转操作的Thumb样式的XAML代码:
<Style x:Key="RotateStyle" TargetType="Thumb">
<Setter Property="Background" Value="#FF1F3B53"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="BorderBrush">
<Setter.Value>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFA3AEB9" Offset="0"/>
<GradientStop Color="#FF8399A9" Offset="0.375"/>
<GradientStop Color="#FF718597" Offset="0.375"/>
<GradientStop Color="#FF617584" Offset="1"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Thumb">
<Grid Width="10" Height="10" >
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver">
<Storyboard>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="BackgroundAnimation"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ColorAnimation Duration="0" To="#FF6DBDD1" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" Storyboard.TargetName="Background"/>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="BackgroundAnimation"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<DoubleAnimation Duration="0" To=".55" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="DisabledVisualElement"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Focused">
<Storyboard>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="FocusVisualElement"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Unfocused"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border x:Name="Background" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="White" CornerRadius="5">
<Grid Margin="1">
<Border x:Name="BackgroundAnimation" Opacity="0"/>
<Rectangle x:Name="BackgroundGradient" RadiusX="5" RadiusY="5">
<Rectangle.Fill>
<RadialGradientBrush>
<GradientStop Color="#FF939BE0" Offset="0"/>
<GradientStop Color="#F9FFFFFF" Offset="0.858"/>
<GradientStop Color="#E54B6391" Offset="0.446"/>
<GradientStop Color="#C68892F3" Offset="1"/>
</RadialGradientBrush>
</Rectangle.Fill>
</Rectangle>
</Grid>
</Border>
<Rectangle x:Name="DisabledVisualElement" Fill="#FFFFFFFF" IsHitTestVisible="false" Opacity="0" RadiusY="2" RadiusX="2"/>
<Rectangle x:Name="FocusVisualElement" IsHitTestVisible="false" Margin="1" Opacity="0" RadiusY="1" RadiusX="1" Stroke="#FF6DBDD1" StrokeThickness="1"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
3、现在我们需要打开VS2010中,新建一个Sivlerlight的类库项目,命名了RotateAndResizingLib,接着我们通过工程的添加--》新建项,添加一个命名为Adorner的UserControl控件。这里VS会为我们生成一个XAML文件和相应的后置为Adorner.cs代码文件。我们要在XAML文件定义元素的排列,在后置代码文件中定义逻辑。
首先定义XAML文件,将上面的样式代码添加到<UserControl.Resources></UserContol.Resource>之中,接着在控件在放置两个Thumb控件,并分别引用这两个样式,相应的代码如下:
<UserControl ……>
<UserControl.Resources>
……
</UserContol.Resource>
<Grid x:Name="LayoutRoot" RenderTransformOrigin="0,0" >
<Rectangle Stroke="DarkGray" StrokeDashArray="2 2" Margin="-6" />
<Thumb HorizontalAlignment="Right" x:Name="ResizingThumb" DragDelta="ResizingThumb_DragDelta" Height="10" VerticalAlignment="Bottom" Width="10" Style="{StaticResource ResizingStyle}" Margin="0,0,-10,-10"/>
<Thumb HorizontalAlignment="Left" x:Name="RotateThumb" DragDelta="RotateThumb_DragDelta" DragStarted="RotateThumb_DragStarted" Height="10" VerticalAlignment="Top" Width="10" Style="{StaticResource RotateStyle}" Margin="-10,-10,0,0"/>
</Grid>
</UserControl>
二、实现代码逻辑:
现在基本的外观已经成形,现在我们需要在后置代码中实现相应的操作逻辑。根据装饰器模式的思想,我们需要在这个自定义的控件中得到一个源对象的引用,所以我们定义一个FrameworkElement公共属性AdornedElement,这个属性暴露给外部的使用者,以添加源对象的引用。其代码如下:
private FrameworkElement current;
private bool IsDrag;
private Point dragAnchor;
public FrameworkElement AdornedElement
{//这个值是从XMAL中传过来的
set
{
if (current == value)//如果已经附加了,返回
return;
if (current != null)//如果当前已经选中,
{
Unsubscribe();
current.Effect = null;
}
if (value == null)
{
Visibility = Visibility.Collapsed;
ClearValue(TransformProperty);
ClearValue(WidthProperty);
ClearValue(HeightProperty);
current = null;
}
else
{
current = value;
Visibility = Visibility.Visible;
RenderTransformOrigin = current.RenderTransformOrigin;
if (current.RenderTransform == null)
current.RenderTransform = new MatrixTransform();
SetBinding(TransformProperty, new System.Windows.Data.Binding("RenderTransform") { Source = current });
SetBinding(WidthProperty, new System.Windows.Data.Binding("Width") { Source = current });
SetBinding(HeightProperty, new System.Windows.Data.Binding("Height") { Source = current });
Subscribe();
Visibility = Visibility.Visible;
current.Effect = new DropShadowEffect() { Opacity = 0.5, BlurRadius = 10, ShadowDepth = 0 };
}
}
get { return current; }
}
这个属性的目的在于源对象设定时将还要将源对象本身的变换附加过来,为了实现自动通知机制,这儿还要定义一个TransformProperty的依赖属性,在源对象的RenderTransform对象改变时自动通知自身实现变换。【在上面的代码中关联:SetBinding(TransformProperty, new System.Windows.Data.Binding("RenderTransform") { Source = current });】代码如下:
#region Transform Dependency Property
public Transform Transform {
get { return (Transform)GetValue(TransformProperty); }
set { SetValue(TransformProperty, value); }
}
public static readonly DependencyProperty TransformProperty = DependencyProperty.Register("Transform", typeof(Transform), typeof(Adorner), new PropertyMetadata(OnTransformChanged));
private static void OnTransformChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
((Adorner)sender).OnTransformChanged(e);
}
private void OnTransformChanged(DependencyPropertyChangedEventArgs e)
{
Transform r = e.NewValue as Transform;
RenderTransform = r;
}
#endregion
现在我们来实现具体的操作了:
1、实现对象的拖动:【添加源对象引用的MouseLeftButtonDown,MouseLeftButtonUp,MouseMove,MouseWheel事件】分别实现捕捉鼠标,释放鼠标、移动对象和滚轮缩放,相应的代码如下:
public void current_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if ((sender as FrameworkElement).CaptureMouse()) {
IsDrag = true;
dragAnchor = e.GetPosition(null);
}
}
void current_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (IsDrag)
(sender as FrameworkElement).ReleaseMouseCapture();
IsDrag = false;
}
void current_MouseMove(object sender, MouseEventArgs e)
{
if (IsDrag) {
Point pt = e.GetPosition(null);
Matrix mx = (current.RenderTransform as MatrixTransform).Matrix;
mx.OffsetX += pt.X - dragAnchor.X;
mx.OffsetY += pt.Y - dragAnchor.Y;
current.RenderTransform = new MatrixTransform { Matrix = mx };
dragAnchor = pt;
}
}
void current_MouseWheel(object sender, MouseWheelEventArgs e)
{
Point p = e.GetPosition(sender as UIElement);
p.X /= current.Width;
p.Y /= current.Height;
double w = Width * 0.005 * e.Delta;
double h = Height * 0.005 * e.Delta;
current.Width += w;
current.Height += h;
Point transf = new Point((RenderTransformOrigin.X - p.X) * w, (RenderTransformOrigin.Y - p.Y) * h);
transf = RenderTransform.Transform(transf);
Matrix mx = (current.RenderTransform as MatrixTransform).Matrix;
mx.OffsetX = transf.X;
mx.OffsetY = transf.Y;
current.RenderTransform = new MatrixTransform() { Matrix = mx };
}
2、实现resizingBar的缩放操作【添加ResizingThumb的DragDelta事件】,相应的代码如下:
private void ResizingThumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
Point p = new Point(e.HorizontalChange, e.VerticalChange);
double w=p.X*(1/(1-RenderTransformOrigin.X));
double h = p.Y * (1 / (1 - RenderTransformOrigin.Y));
if (current.Width + w < 10)
w = 0;
if (current.Height + h < 10)
h = 0;
current.Width += w;
current.Height += h;
}
3、实现rotateBar的旋转操作【添加RotateThumb的DragStarted和DragDelta事件,前一个事件的目的是保存对象的初始信息,后一个实现具体的旋转变换】,相应的代码如下:
private void RotateThumb_DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e)
{
start = new Point(e.HorizontalOffset, e.VerticalOffset);
cpt = start;
mx = (current.RenderTransform as MatrixTransform).Matrix;
}
private void RotateThumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
cpt.X += e.HorizontalChange;
cpt.Y += e.VerticalChange;
double a = Math.Atan2(cpt.Y - Height * RenderTransformOrigin.Y, cpt.X - Width * RenderTransformOrigin.X) -
Math.Atan2(start.Y - Height * RenderTransformOrigin.Y, start.X - Width * RenderTransformOrigin.X);
Matrix rotate = new Matrix(Math.Cos(a), Math.Sin(a), -Math.Sin(a), Math.Cos(a), 0, 0);
Matrix newM = rotate.Multiple(mx);
current.RenderTransform = new MatrixTransform { Matrix = newM };
}
这儿为了实现Matrix对象的矩阵乘法操作,对Matrix添加了扩展方法:【添加一个MatrixComputerHelper的静态类,实现Multiple扩展方法】,代码如下 :
public static class MatrixComputerHelper
{
public static Matrix Multiple(this Matrix originMx, Matrix sourceMx)//矩阵乘法
{
Matrix m = new Matrix();
m.M11 = originMx.M11 * sourceMx.M11 + originMx.M12 * sourceMx.M21;
m.M12 = originMx.M11 * sourceMx.M12 + originMx.M12 * sourceMx.M22;
m.M21 = originMx.M21 * sourceMx.M11 + originMx.M22 * sourceMx.M12;
m.M22 = originMx.M21 * sourceMx.M12 + originMx.M22 * sourceMx.M22;
m.OffsetX = originMx.OffsetX + sourceMx.OffsetX;
m.OffsetY = originMx.OffsetY + sourceMx.OffsetY;
return m;
}
}
现在我们编译类库,现在就可以使用了我们的Adorner装饰器控件了。
实验:
新建一个Silverlight应用程序,并添加上面的RotateAndResizingLib.dll引用,在MainPage用户控件上添加一些Rectangle或Circule对象,然后将Adorner控件添加到MainPage上(为了应用的广泛,请将它添加到可视树顶层的Grid中)
接着重写MainPage的OnMouseLeftButtonDown方法,并将上面的Rectangle或Circle对象添加到Adorner对象的AdornedElement属性上。代码如下:
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
foreach (var child in VisualTreeHelper.FindElementsInHostCoordinates(e.GetPosition(null), this))
{
if (child is UIElement)
{
Target = child;
Canvas.SetZIndex(child, 1000);
adorner1.AdornedElement = child as FrameworkElement;
adorner1.current_MouseLeftButtonDown(child, e);
break;
}
}
base.OnMouseLeftButtonDown(e);
}
至此我们实现了一个拥有缩放+拖动+旋转的Silverlight应用程序。效果效果如图:
点击下载源码