WPF 获取鼠标相对于控件的坐标信息,控制控件锚点放缩
使用MouseMove事件+e.GetPosition方法可以获取鼠标相对于控件的坐标信息,或者使用TranslatePoint间接计算。
Canvas是Panel的容器。


eLastMousePosition和relativeToPanel 是相同的
锚点放缩靠Mtatrix实现:


终点在于理解红色框框部分,我还没吃透,可以参考一下:
在RenderTransform上叠加一个ScaleAt-腾讯云开发者社区-腾讯云
理解了希望可以在评论区给出解释。谢谢。
下面是完整代码:
xaml:
<Windowx:Class="PanelZoomDemo.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="Panel缩放演示 - 实时坐标"Width="900"Height="700"><TabControl><TabItem Header="aa"><Grid><Grid.ColumnDefinitions><ColumnDefinition Width="300" /><ColumnDefinition Width="10" /><ColumnDefinition Width="*" /></Grid.ColumnDefinitions><Grid.RowDefinitions><RowDefinition Height="Auto" /><RowDefinition Height="*" /><RowDefinition Height="Auto" /></Grid.RowDefinitions><!-- 标题栏 --><StackPanelGrid.Row="0"Grid.ColumnSpan="3"Margin="10"><TextBlockFontSize="16"FontWeight="Bold"Text="Panel缩放演示 - 实时坐标跟踪" /><TextBlock Margin="0,5" Text="左键点击:放大 | 右键点击:缩小 | 移动鼠标实时显示坐标" /><TextBlockx:Name="ScaleInfoText"FontWeight="Bold"Foreground="Blue"Text="当前缩放: 1.00x" /></StackPanel><!-- 左侧:坐标信息面板 --><BorderGrid.Row="1"Grid.Column="0"Margin="5"Background="#F5F5F5"BorderBrush="Gray"BorderThickness="1"><ScrollViewer VerticalScrollBarVisibility="Auto"><StackPanel Margin="15"><TextBlockMargin="0,0,0,10"FontSize="16"FontWeight="Bold"Foreground="DarkBlue"Text="实时坐标信息" /><GroupBox Margin="0,0,0,10" Header="鼠标状态"><StackPanel Margin="5"><TextBlockx:Name="MouseOverText"Margin="0,2"FontWeight="Bold"Foreground="Red"Text="位置: 在Panel外" /><TextBlockx:Name="MouseCoordText"Margin="0,5,0,2"Text="屏幕坐标: (0, 0)" /><TextBlockx:Name="RelativeCoordText"Margin="0,2"Text="相对Panel: (0, 0)" /><TextBlockx:Name="eRelativeCoordText"Margin="0,2"Text="e.Position Panel: (0, 0)" /></StackPanel></GroupBox><GroupBox Margin="0,0,0,10" Header="点击信息"><StackPanel Margin="5"><TextBlockx:Name="LastClickText"Margin="0,2"Text="最后点击: (0, 0)" /><TextBlockx:Name="ClickCountText"Margin="0,2"Text="点击次数: 0" /></StackPanel></GroupBox><GroupBox Margin="0,0,0,10" Header="变换信息"><StackPanel Margin="5"><TextBlockx:Name="ScaleText"Margin="0,2"Text="缩放比例: 1.00x" /><TextBlockx:Name="TransformText"Margin="0,2"Text="变换矩阵: Identity" /><TextBlockx:Name="PanelPositionText"Margin="0,2"Text="Panel位置: (100, 100)" /></StackPanel></GroupBox><GroupBox Header="操作提示"><StackPanel Margin="5"><TextBlock Margin="0,2" Text="• 左键点击:放大1.2倍" /><TextBlock Margin="0,2" Text="• 右键点击:缩小0.8倍" /><TextBlock Margin="0,2" Text="• 移动鼠标:实时跟踪坐标" /><TextBlock Margin="0,2" Text="• 标记点不会阻挡点击" /></StackPanel></GroupBox></StackPanel></ScrollViewer></Border><!-- 右侧:Canvas绘图区域 --><Canvasx:Name="MainCanvas"Grid.Row="1"Grid.Column="2"Margin="5"Background="LightGray"MouseMove="OnCanvasMouseMove"><!-- 测试Panel --><Borderx:Name="TestPanel"Canvas.Left="100"Canvas.Top="100"Width="200"Height="150"Background="OrangeRed"BorderBrush="DarkRed"BorderThickness="3"CornerRadius="10"MouseDown="OnPanelMouseDown"MouseEnter="OnPanelMouseEnter"MouseLeave="OnPanelMouseLeave"MouseMove="OnPanelMouseMove"><Border.RenderTransform><MatrixTransform x:Name="PanelTransform" /></Border.RenderTransform><!-- Panel内部内容 --><StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"><TextBlockHorizontalAlignment="Center"FontSize="16"FontWeight="Bold"Foreground="White"Text="点击我缩放" /><TextBlockMargin="0,5"HorizontalAlignment="Center"FontSize="12"Foreground="White"Text="左键放大 · 右键缩小" /><TextBlockx:Name="PanelCoordText"Margin="0,5"HorizontalAlignment="Center"FontSize="11"Foreground="White"Text="坐标: (0, 0)" /></StackPanel></Border><!-- 鼠标位置指示器 --><Canvas x:Name="MouseIndicatorCanvas" /><!-- 点击标记 --><Canvas x:Name="MarkerCanvas" /></Canvas><!-- 底部日志 --><TextBoxx:Name="LogTextBox"Grid.Row="2"Grid.ColumnSpan="3"Height="120"Margin="10"Background="Black"FontFamily="Consolas"FontSize="10"Foreground="White"IsReadOnly="True"VerticalScrollBarVisibility="Auto" /></Grid></TabItem><TabItem Header="bb"><RectangleCanvas.Left="100"Canvas.Top="100"Width="50"Height="50"Fill="#CCCCCCFF"Stroke="Blue"StrokeThickness="2"><Rectangle.RenderTransform><TransformGroup><!-- 第一次缩放:以中心点缩放2倍 --><ScaleTransform CenterX="50" CenterY="50" ScaleX="2" ScaleY="2" /><!-- 第二次缩放:以右下角缩放1.5倍 --><ScaleTransform CenterX="25" CenterY="25" ScaleX="1.5" ScaleY="1.5" /><!-- 可以继续添加更多变换 --></TransformGroup></Rectangle.RenderTransform></Rectangle></TabItem></TabControl></Window>
cs:
using System;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Threading;namespace PanelZoomDemo
{public partial class MainWindow : Window{private StringBuilder logBuilder = new StringBuilder();private double currentScale = 1.0;private Point lastClickPosition = new Point(0, 0);private DispatcherTimer mouseMoveTimer;private Point lastMousePosition = new Point(0, 0);private Point eLastMousePosition = new Point(0, 0);private bool isMouseOverPanel = false;private int clickCount = 0;public MainWindow(){InitializeComponent();InitializeMouseTracking();UpdateScaleInfo();AddLog("Demo启动 - 移动鼠标实时显示坐标,左键放大,右键缩小");AddLog("==================================================");}private void InitializeMouseTracking(){// 使用定时器限制鼠标移动事件的频率mouseMoveTimer = new DispatcherTimer();mouseMoveTimer.Interval = TimeSpan.FromMilliseconds(50); // 20fpsmouseMoveTimer.Tick += OnMouseMoveTimerTick;mouseMoveTimer.Start();}private void OnCanvasMouseMove(object sender, MouseEventArgs e){// 记录鼠标位置,由定时器处理实际更新lastMousePosition = e.GetPosition(MainCanvas);}private void OnPanelMouseMove(object sender, MouseEventArgs e){// Panel内的鼠标移动也记录位置lastMousePosition = e.GetPosition(MainCanvas);eLastMousePosition = e.GetPosition(TestPanel);}private void OnPanelMouseEnter(object sender, MouseEventArgs e){isMouseOverPanel = true;UpdateMouseStatusDisplay();}private void OnPanelMouseLeave(object sender, MouseEventArgs e){isMouseOverPanel = false;UpdateMouseStatusDisplay();}private void OnMouseMoveTimerTick(object sender, EventArgs e){UpdateMousePosition();}private void UpdateMousePosition(){try{eRelativeCoordText.Text = $"e坐标: ({eLastMousePosition.X:F1}, {eLastMousePosition.Y:F1})"; // 获取鼠标相对于Panel的位置Point mousePosition = lastMousePosition;Point relativeToPanel = MainCanvas.TranslatePoint(mousePosition, TestPanel);// 检查鼠标是否在Panel的变换后边界内bool isInsideTransformedPanel = IsPointInTransformedPanel(mousePosition);// 更新坐标显示MouseCoordText.Text = $"屏幕坐标: ({mousePosition.X:F1}, {mousePosition.Y:F1})";RelativeCoordText.Text = $"相对Panel: ({relativeToPanel.X:F1}, {relativeToPanel.Y:F1})";LastClickText.Text = $"最后点击: ({lastClickPosition.X:F1}, {lastClickPosition.Y:F1})";ClickCountText.Text = $"点击次数: {clickCount}";ScaleText.Text = $"缩放比例: {currentScale:F2}x";Matrix matrix = PanelTransform.Matrix;TransformText.Text = $"变换矩阵: [{matrix.M11:F2} {matrix.M12:F2}]";PanelPositionText.Text = $"Panel位置: ({Canvas.GetLeft(TestPanel):F0}, {Canvas.GetTop(TestPanel):F0})";// 更新Panel内部的坐标显示PanelCoordText.Text = $"坐标: ({relativeToPanel.X:F0}, {relativeToPanel.Y:F0})";// 更新鼠标指示器UpdateMouseIndicator(mousePosition, isInsideTransformedPanel);}catch (Exception ex){AddLog($"坐标更新错误: {ex.Message}");}}private bool IsPointInTransformedPanel(Point mousePosition){// 获取Panel的变换后边界Rect panelBounds = new Rect(Canvas.GetLeft(TestPanel),Canvas.GetTop(TestPanel),TestPanel.Width,TestPanel.Height);// 应用变换到边界Matrix matrix = PanelTransform.Matrix;Point[] corners = new Point[]{new Point(panelBounds.Left, panelBounds.Top),new Point(panelBounds.Right, panelBounds.Top),new Point(panelBounds.Right, panelBounds.Bottom),new Point(panelBounds.Left, panelBounds.Bottom)};for (int i = 0; i < corners.Length; i++){corners[i] = matrix.Transform(corners[i]);}// 简单的边界框检测(对于旋转等复杂变换可能需要更精确的检测)double minX = Math.Min(Math.Min(corners[0].X, corners[1].X), Math.Min(corners[2].X, corners[3].X));double maxX = Math.Max(Math.Max(corners[0].X, corners[1].X), Math.Max(corners[2].X, corners[3].X));double minY = Math.Min(Math.Min(corners[0].Y, corners[1].Y), Math.Min(corners[2].Y, corners[3].Y));double maxY = Math.Max(Math.Max(corners[0].Y, corners[1].Y), Math.Max(corners[2].Y, corners[3].Y));Rect transformedBounds = new Rect(minX, minY, maxX - minX, maxY - minY);return transformedBounds.Contains(mousePosition);}private void UpdateMouseStatusDisplay(){if (isMouseOverPanel){MouseOverText.Text = "位置: 在Panel内";MouseOverText.Foreground = Brushes.Green;}else{MouseOverText.Text = "位置: 在Panel外";MouseOverText.Foreground = Brushes.Red;}}private void UpdateMouseIndicator(Point mousePosition, bool isOverPanel){// 清除之前的指示器MouseIndicatorCanvas.Children.Clear();if (isOverPanel){// 在Panel内显示绿色指示器Ellipse indicator = new Ellipse{Width = 8,Height = 8,Fill = Brushes.LimeGreen,Stroke = Brushes.DarkGreen,StrokeThickness = 1,IsHitTestVisible = false};Canvas.SetLeft(indicator, mousePosition.X - 4);Canvas.SetTop(indicator, mousePosition.Y - 4);MouseIndicatorCanvas.Children.Add(indicator);// 添加十字线Line horizontalLine = new Line{X1 = mousePosition.X - 15,Y1 = mousePosition.Y,X2 = mousePosition.X + 15,Y2 = mousePosition.Y,Stroke = Brushes.LimeGreen,StrokeThickness = 1,IsHitTestVisible = false};Line verticalLine = new Line{X1 = mousePosition.X,Y1 = mousePosition.Y - 15,X2 = mousePosition.X,Y2 = mousePosition.Y + 15,Stroke = Brushes.LimeGreen,StrokeThickness = 1,IsHitTestVisible = false};MouseIndicatorCanvas.Children.Add(horizontalLine);MouseIndicatorCanvas.Children.Add(verticalLine);}else{// 在Panel外显示红色指示器Ellipse indicator = new Ellipse{Width = 6,Height = 6,Fill = Brushes.Red,Stroke = Brushes.DarkRed,StrokeThickness = 1,IsHitTestVisible = false};Canvas.SetLeft(indicator, mousePosition.X - 3);Canvas.SetTop(indicator, mousePosition.Y - 3);MouseIndicatorCanvas.Children.Add(indicator);}}private void OnPanelMouseDown(object sender, MouseButtonEventArgs e){// 获取点击位置相对于Panel的坐标Point clickPosition = e.GetPosition(TestPanel);lastClickPosition = clickPosition;clickCount++;// 确定缩放因子:左键放大,右键缩小double scaleFactor = e.ChangedButton == MouseButton.Left ? 1.2 : 0.8;// 执行缩放ZoomAtPoint(scaleFactor, clickPosition);//ZoomAtPoint(scaleFactor, positionNew);// 添加点击标记AddClickMarker(clickPosition);// 记录日志string action = e.ChangedButton == MouseButton.Left ? "放大" : "缩小";AddLog($"[{action}] 位置:({clickPosition.X:F1},{clickPosition.Y:F1}) 缩放:{scaleFactor:F1}x 总缩放:{currentScale:F2}x");}private void ZoomAtPoint(double scaleFactor, Point centerPoint){// 获取当前变换矩阵Matrix matrix = PanelTransform.Matrix;try{////法1,不符合锚点放缩://Matrix inverseMatrix = matrix;// 计算在正确坐标系中的缩放中心//inverseMatrix.Invert();//Point correctCenter = inverseMatrix.Transform(centerPoint);////应用缩放变换//matrix.ScaleAt(scaleFactor, scaleFactor, correctCenter.X, correctCenter.Y);//法2:var positionNew = matrix.Transform(centerPoint);AddLog($"positionNew 位置:({positionNew.X:F1},{positionNew.Y:F1}) ");matrix.ScaleAt(scaleFactor, scaleFactor, positionNew.X, positionNew.Y);}catch (InvalidOperationException){// 如果矩阵不可逆,使用原始坐标matrix.ScaleAt(scaleFactor, scaleFactor, centerPoint.X, centerPoint.Y);AddLog("警告: 使用原始坐标进行缩放");}// 更新变换PanelTransform.Matrix = matrix;// 更新当前缩放比例currentScale *= scaleFactor;UpdateScaleInfo();}private void AddClickMarker(Point clickPosition){// 清除之前的标记MarkerCanvas.Children.Clear();// 计算标记在Canvas中的实际位置double panelLeft = Canvas.GetLeft(TestPanel);double panelTop = Canvas.GetTop(TestPanel);// 应用当前变换到点击位置Matrix matrix = PanelTransform.Matrix;Point transformedPoint = matrix.Transform(clickPosition);Point canvasPosition = new Point(panelLeft + transformedPoint.X, panelTop + transformedPoint.Y);// 创建标记 - 不拦截鼠标事件Ellipse marker = new Ellipse{Width = 12,Height = 12,Fill = Brushes.Yellow,Stroke = Brushes.Red,StrokeThickness = 2,IsHitTestVisible = false,ToolTip = $"点击位置\n原始:({clickPosition.X:F1},{clickPosition.Y:F1})\n变换后:({transformedPoint.X:F1},{transformedPoint.Y:F1})"};Canvas.SetLeft(marker, canvasPosition.X - 6);Canvas.SetTop(marker, canvasPosition.Y - 6);MarkerCanvas.Children.Add(marker);}private void UpdateScaleInfo(){ScaleInfoText.Text = $"当前缩放: {currentScale:F2}x";}private void AddLog(string message){if (logBuilder.Length > 1500){logBuilder.Clear();logBuilder.AppendLine("日志已清空...");}logBuilder.AppendLine(message);LogTextBox.Text = logBuilder.ToString();LogTextBox.ScrollToEnd();}protected override void OnClosed(EventArgs e){mouseMoveTimer?.Stop();base.OnClosed(e);}}
}
