WPF对象都具有RenderTransform的属性,可以通过设置RenderTransform来对WPF的元素进行变换,无论是控件还是形状都可以变换。典型的变换包括缩放和平移:
(一)缩放
如果采用Canvas作为画板来绘制一些形状,想要通过鼠标或触摸操作来进行放大或缩小,那么不能简单地对canvas进行变换,否则Cancas放大的时候就会覆盖周边的其它控件,也就是Canvas占据的屏幕变大了或缩小了,而不仅仅是Canvas内部画出来的形状变大了或缩小了。
那如果需要实现放大或缩小怎么办呢?我采取的方式是在Canvas外面包装一个WPF元素,比如Border元素。这样,Canvas就成为Border元素的子元素了,然后在Border元素上实现鼠标控制操作来变换Canvas元素。
<Border Name="outside" Grid.Row="1" Background="LightBlue" PreviewMouseDown="outsidewrapper_PreviewMouseDown" ClipToBounds="True">
<Canvas Name="inside" Width="{Binding Path=ActualWidth,RelativeSource={RelativeSource AncestorType=Border}}"
Height="{Binding Path=ActualHeight,RelativeSource={RelativeSource AncestorType=Border}}">
<Rectangle Canvas.Left="150" Canvas.Top="150" Width="380" Height="296" Fill="Red"/>
</Canvas>
</Border>
private void outsidewrapper_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
// Point p = e.GetPosition(inside); //不能用这个,应该删除这一行
Point po = e.GetPosition(outside);
TransformGroup tg= inside.RenderTransform as TransformGroup;
if (tg == null)
tg = new TransformGroup();
tg.Children.Add(new ScaleTransform(0.6, 0.6, po.X, po.Y)); //centerX和centerY用外部包装元素的坐标,不能用内部被变换的Canvas元素的坐标
inside.RenderTransform = tg;
}
需要注意事项包括:
(1)包装的元素需要添加ClipToBounds="True"属性,这样当放大时,内部Canvas超出包装元素的时候,超出部分就会被裁剪掉;
(2)把Canvas元素的初始大小设置为与包装元素一样大小,可以通过RelativeSource来设置:Width="{Binding Path=ActualWidth,RelativeSource={RelativeSource AncestorType=Border}}" ,Height="{Binding Path=ActualHeight,RelativeSource={RelativeSource AncestorType=Border}}"。
(3)将外包包装元素的背景和Canvas的背景设置为一样的背景,这样当Canvas缩小的时候,不会给然感觉Canvas画布真的小了。或者Canvas不设置背景也是可行的(但可能Canvas会收不到鼠标事件......)。
(4)用鼠标事件控制放大缩小的话,可以使用隧道事件,且事件应该关联在外部包装元素Border上。放大或缩小的时候,放大缩小中心点(CenterX,CenterY)用的不是Canvas的坐标点,而是外部包装元素的坐标点。当然具体根据需要采用合适的事件,比如滚轮事件之类的,触控事件之类的。
(5)理解:按理说所有元素的变换都是针对自身的坐标体系的,而不是针对外部父元素的坐标体系的,但是WPF有个特点,虽然时针对自身坐标系的变化,但是变换前后自身的ActualWidth和ActualHeight都没有发生任何变化,下次继续变换的时候,还是用的ActualWidth和ActualHeigth作为自身坐标系的参考用途,而不是按照变换后的实际尺寸在定位自身坐标系尺寸的。所以,使用外部包装元素的坐标系来给定每次变换的(CenterX和CenterY)是可行的。这一点需要慢慢理解。
(二)平移