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)是可行的。这一点需要慢慢理解。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!