WPF 基于Transform实现画布超出边界触发计算
有些场景需要对画布边界做界限控制,此时需要计算画布的四个方向的界限和极值
先看效果图:
画布在通过RenderTransform 做变换,由于在变换的过程中,实际的宽高没有改变,需要通过Transform实时记录变换的状态
1、图中用到了是对Canvas做 RenderTransform的变换,支持缩放和移动
2、最外层需要包一层Border,主要是限定画布的实际的宽高,因为Canvas在做变换的时候,实际的宽高并不改变,画布canvas的移动的偏移量是通过计算Canvas与Border的宽高相对的位置
3、
<Border x:Name="canvasGrid"> <Canvas x:Name="writeBorad" Width="1920" Height="1080" MouseDown="WriteBorad_OnMouseDown" MouseWheel="UIElement_OnMouseWheel" Background="{StaticResource test}" MouseUp="WriteBorad_OnMouseUp" MouseMove="UIElement_OnMouseMove" > <Canvas.RenderTransform> <TransformGroup> <ScaleTransform x:Name="scale"></ScaleTransform> <TranslateTransform x:Name="trans"></TranslateTransform> </TransformGroup> </Canvas.RenderTransform> <Button Width="60" Height="60" Canvas.Left="100" Canvas.Top="100"></Button> </Canvas> </Border>
后台计算逻辑:
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); Loaded += MainWindow_Loaded; this.writeBorad.Width = 3840; this.writeBorad.Height = 2160; } private void MainWindow_Loaded(object sender, RoutedEventArgs e) { //取整设置成刚好的网格大小 var drawBrush = this.writeBorad.Background as DrawingBrush; if (drawBrush != null) { var size = drawBrush.Viewport.Size; //横向判断取整 var div = writeBorad.Width / size.Width; var rem = writeBorad.Width % size.Width; if (rem != 0) { writeBorad.Width = size.Width *(Math.Floor(div) + 1); } //纵向判断取整 div = writeBorad.Height / size.Height; rem = writeBorad.Height % size.Height; if (rem != 0) { writeBorad.Height =size.Height* (Math.Floor(div) + 1); } } trans.X = -writeBorad.Width / 2; trans.Y = -writeBorad.Height / 2; } private void UIElement_OnMouseWheel(object sender, MouseWheelEventArgs e) { Point point = e.GetPosition(writeBorad); var delta = e.Delta * 0.001; DowheelZoom(null, point, delta); } double scaleSize = 1; private void DowheelZoom(System.Windows.Media.TransformGroup group, Point point, double delta) { //按住ctrl缩放 // if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) { if ((scale.ScaleX + delta) <= 1) { //return; scale.ScaleX = 1; scale.ScaleY = 1; } else if (scale.ScaleX + delta > 5) { scale.ScaleX = 5; scale.ScaleY = 5; } else { scale.ScaleX += delta; scale.ScaleY += delta; } var targetX = point.X * (scale.ScaleX - 1); var targetY = point.Y * (scale.ScaleX - 1); trans.X = -targetX; trans.Y = -targetY; Console.WriteLine($"{trans.X}==={trans.Y}"); scaleSize = scale.ScaleX; } } double tranX; double tranY; Point startPoint; //判断是否鼠标点击 private bool isMouseDown; private void UIElement_OnMouseMove(object sender, MouseEventArgs e) { if(e.LeftButton!= MouseButtonState.Pressed || !isMouseDown) return; Point mousePoint = e.GetPosition(canvasGrid); var targetX = tranX + mousePoint.X - startPoint.X; var targetY = tranY + mousePoint.Y - startPoint.Y; trans.X = targetX; trans.Y = targetY; Console.WriteLine($"{trans.X}==={trans.Y}"); //边界检测 //缩放后的尺寸 var scaleWidth = writeBorad.ActualWidth * scaleSize; var scaleHeight = writeBorad.ActualHeight * scaleSize; var gridWidth = canvasGrid.ActualWidth; var gridHeight = canvasGrid.ActualHeight; //x轴最小x坐标 var minX = -(scaleWidth - gridWidth); //y轴最小坐标 var minY = -(scaleHeight - gridHeight); double resetX = 0; double resetY = 0;
//计算边界值 //x轴左侧坐标越界,缓存到point点 if (targetX > 0) { ////拖拽前在可拖动区域 //if(trans.X<0) targetX = 0; resetX = targetX; isNeedResetX = true; Console.WriteLine("左侧越界了"); } if (targetX < minX) { targetX = minX; resetX = targetX; isNeedResetX = true; Console.WriteLine("右侧越界了"); } if (targetY > 0) { targetY = 0; resetY = targetY; isNeedResetY = true; Console.WriteLine("上侧越界了"); } if (targetY < minY) { targetY = minY; resetY = targetY; Console.WriteLine("下侧越界了"); isNeedResetY = true; } ResetPoint=new Point(resetX,resetY); } private Point ResetPoint; private bool isNeedResetX; private bool isNeedResetY; private void WriteBorad_OnMouseDown(object sender, MouseButtonEventArgs e) { isMouseDown = true; writeBorad.CaptureMouse(); startPoint = e.GetPosition(canvasGrid); tranX = trans.X; tranY = trans.Y; isNeedResetX = false; isNeedResetY = false; } private void AddElement(Point position) { Button btn=new Button(); btn.Height = 60; btn.Width = 60; Canvas.SetLeft(btn,position.X); Canvas.SetTop(btn, position.Y); writeBorad.Children.Add(btn); } private void WriteBorad_OnMouseUp(object sender, MouseButtonEventArgs e) { AddElement(e.GetPosition(writeBorad)); isMouseDown = false; writeBorad.ReleaseMouseCapture(); if (isNeedResetX) { trans.X = ResetPoint.X; } if (isNeedResetY) { trans.Y = ResetPoint.Y; } isNeedResetY = isNeedResetX = false; ResetPoint =new Point(); if (isNeedResetY || isNeedResetX) { Console.WriteLine($"{trans.X}==={trans.Y}"); } } }
总结:1、通过Border限定画布的实际宽高,画布做Transform做平移缩放,通过实时计算宽高来计算偏移量,从而计算是否触发边界
2、本次演示用到Canvas的 ScaleTransform 和 TranslateTransform 做的平移缩放的变换,后续修改为MatrixTransform,灵活性更高,支持多指触发 Manipulation 操作
3、后续增加超出边界触发回弹的效果,以及回弹动画的处理