WPF 的Canvas画图区整体缩放与平移

wpf Canvas 对鼠标事件不响应的解决办法

据说是把背景色设上就可以了

WPF对象都具有RenderTransform的属性,可以通过设置RenderTransform来对WPF的元素进行变换,无论是控件还是形状都可以变换。典型的变换包括缩小放大与平移。

为了实现平移,这里以按下鼠标中间键并移动鼠标作为事件触发方式,来实现平移。即先下辖鼠标中键(滚轮键),移动鼠标,这样WPF元素就会跟随鼠标平移。

WPF元素和形状的变换是针对于自身的坐标系的,因此不能直接获取其自身坐标系的点来进行变换,否则是不对了,有时也会不停闪烁跳动,本人一开始不小心用错了,就用待平移的WPF元素自身坐标点来作为平移参数,后来发现屏幕不停闪烁跳动,且平移距离也不正确。

如果采用Canvas作为画板来绘制一些形状,想要通过鼠标或触摸操作来进行平移,那么不能简单地对canvas进行变换,否则Cancas平移的时候就会覆盖周边的其它控件,也就是Canvas画布自身被移动了,而不仅仅是Canvas内部画出来的形状被移动了。

那如果需要实现移动怎么办呢?与前一篇文章一样,我采取的方式是在Canvas外面包装一个WPF元素,比如Border元素。这样,Canvas就成为Border元素的子元素了,然后在Border元素上实现鼠标控制操作来变换Canvas元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
<Border Name="outside" Grid.Column="1" Background="LightBlue"
PreviewMouseDown="outsidewrapper_PreviewMouseDown"
PreviewMouseMove="outsidewrapper_PreviewMouseMove"
PreviewMouseUp="outside_PreviewMouseUp"
PreviewMouseWheel="outside_PreviewMouseWheel"
ClipToBounds="True">
 
<Canvas Name="inside" Width="{Binding Path=ActualWidth,RelativeSource={RelativeSource AncestorType=Border}}"
Height="{Binding Path=ActualHeight,RelativeSource={RelativeSource AncestorType=Border}}">
<Canvas.RenderTransform>
<TransformGroup/>
</Canvas.RenderTransform>
<Line Canvas.Left="50" Canvas.Top="50" X1="100" Y1="200" X2="100" Y2="200" Stroke="Black" StrokeThickness="5"/>
<Rectangle Canvas.Left="150" Canvas.Top="150" Width="380" Height="296" Fill="Red" />
 
</Canvas>
</Border>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
 
Point previousPoint;
bool isTranslateStart = false;
 
private void outsidewrapper_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
//说明:虽然WPF元素存在MouseLeftButtonDown和MouseRightButtonDown事件,
//但却没找到MouseMiddleButtonDown事件。
//然而,幸运的是,MouseDown事件是不论哪个鼠标键按下的时候都会触发的,因此可以利用该事件,
//并通过检测哪个键被按下的状态参数来判断是否是中间键被按下了。
//也就是说,当出现MouseDown事件的时候,判断三个键的状态,如果中间键被按下,
//而其它两个键没有被按下,则认为是中间键按下的事件。在该事件中,记录下当时鼠标的位置
 
if(e.MiddleButton==MouseButtonState.Pressed&&e.LeftButton==MouseButtonState.Released&&e.RightButton==MouseButtonState.Released)
{
previousPoint = e.GetPosition(outside);
isTranslateStart = true;
}
e.Handled = true;
}
 
private void outsidewrapper_PreviewMouseMove(object sender, MouseEventArgs e)
{
if (e.MiddleButton == MouseButtonState.Pressed && e.LeftButton == MouseButtonState.Released && e.RightButton == MouseButtonState.Released)
{
if (isTranslateStart)
{
Point currentPoint = e.GetPosition(outside); //不能用 inside,必须用outside
Vector v = currentPoint - previousPoint;
TransformGroup tg = inside.RenderTransform as TransformGroup;
tg.Children.Add(new TranslateTransform(v.X,v.Y)); //centerX和centerY用外部包装元素的坐标,不能用内部被变换的Canvas元素的坐标
// inside.RenderTransform = tg;
previousPoint = currentPoint;
}
 
}
e.Handled = true;
}
 
private void outside_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
if (e.MiddleButton == MouseButtonState.Pressed && e.LeftButton == MouseButtonState.Released && e.RightButton == MouseButtonState.Released)
{
if (isTranslateStart)
{
isTranslateStart = false;
}
}
e.Handled = true;
}
 
private void outside_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
 
Point currentPoint = e.GetPosition(outside); //不能用 inside,必须用outside
 
TransformGroup tg = inside.RenderTransform as TransformGroup;
 
double s = ((double)e.Delta) / 1000.0+1.0;
 
//centerX和centerY用外部包装元素的坐标,不能用内部被变换的Canvas元素的坐标
tg.Children.Add(new ScaleTransform(s,s,currentPoint.X,currentPoint.Y));
e.Handled = true;
}
}

需要注意事项包括:

(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上。平移的大小用外包装元素坐标系统中的坐标点进行计算。当然具体根据需要采用合适的事件,比如滚轮事件之类的,触控事件之类的。

(5)虽然WPF元素存在MouseLeftButtonDown和MouseRightButtonDown事件,但却没找到MouseMiddleButtonDown事件。然而,幸运的是,MouseDown事件是不论哪个鼠标键按下的时候都会触发的,因此可以利用该事件,并通过检测哪个键被按下的状态参数来判断是否是中间键被按下了。也就是说,当出现MouseDown事件的时候,判断三个键的状态,如果中间键被按下,而其它两个键没有被按下,则认为是中间键按下的事件。在该事件中,记录下当时鼠标的位置。鼠标移动的时候,获取新的鼠标位置,并与记录为对象成员变量的前一个鼠标位置相减,即刻得知平移量,通过向Canvas的TransformGroup中添加平移变换,即可实现平移变换。

(6)理解:按理说所有元素的变换都是针对自身的坐标体系的,而不是针对外部父元素的坐标体系的,但是WPF有个特点,虽然时针对自身坐标系的变化,但是变换前后自身的ActualWidth和ActualHeight都没有发生任何变化,下次继续变换的时候,还是用的ActualWidth和ActualHeigth作为自身坐标系的参考用途,而不是按照变换后的实际尺寸在定位自身坐标系尺寸的。所以,使用外部包装元素的坐标系来给定每次变换的(CenterX和CenterY)是可行的。这一点需要慢慢理解。

posted @   多见多闻  阅读(1796)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示