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


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


终点在于理解红色框框部分,我还没吃透,可以参考一下:
在RenderTransform上叠加一个ScaleAt-腾讯云开发者社区-腾讯云
理解了希望可以在评论区给出解释。谢谢。
下面是完整代码:
xaml:
XML
<Window
x: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>
<!-- 标题栏 -->
<StackPanel
Grid.Row="0"
Grid.ColumnSpan="3"
Margin="10">
<TextBlock
FontSize="16"
FontWeight="Bold"
Text="Panel缩放演示 - 实时坐标跟踪" />
<TextBlock Margin="0,5" Text="左键点击:放大 | 右键点击:缩小 | 移动鼠标实时显示坐标" />
<TextBlock
x:Name="ScaleInfoText"
FontWeight="Bold"
Foreground="Blue"
Text="当前缩放: 1.00x" />
</StackPanel>
<!-- 左侧:坐标信息面板 -->
<Border
Grid.Row="1"
Grid.Column="0"
Margin="5"
Background="#F5F5F5"
BorderBrush="Gray"
BorderThickness="1">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Margin="15">
<TextBlock
Margin="0,0,0,10"
FontSize="16"
FontWeight="Bold"
Foreground="DarkBlue"
Text="实时坐标信息" />
<GroupBox Margin="0,0,0,10" Header="鼠标状态">
<StackPanel Margin="5">
<TextBlock
x:Name="MouseOverText"
Margin="0,2"
FontWeight="Bold"
Foreground="Red"
Text="位置: 在Panel外" />
<TextBlock
x:Name="MouseCoordText"
Margin="0,5,0,2"
Text="屏幕坐标: (0, 0)" />
<TextBlock
x:Name="RelativeCoordText"
Margin="0,2"
Text="相对Panel: (0, 0)" />
<TextBlock
x:Name="eRelativeCoordText"
Margin="0,2"
Text="e.Position Panel: (0, 0)" />
</StackPanel>
</GroupBox>
<GroupBox Margin="0,0,0,10" Header="点击信息">
<StackPanel Margin="5">
<TextBlock
x:Name="LastClickText"
Margin="0,2"
Text="最后点击: (0, 0)" />
<TextBlock
x:Name="ClickCountText"
Margin="0,2"
Text="点击次数: 0" />
</StackPanel>
</GroupBox>
<GroupBox Margin="0,0,0,10" Header="变换信息">
<StackPanel Margin="5">
<TextBlock
x:Name="ScaleText"
Margin="0,2"
Text="缩放比例: 1.00x" />
<TextBlock
x:Name="TransformText"
Margin="0,2"
Text="变换矩阵: Identity" />
<TextBlock
x: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绘图区域 -->
<Canvas
x:Name="MainCanvas"
Grid.Row="1"
Grid.Column="2"
Margin="5"
Background="LightGray"
MouseMove="OnCanvasMouseMove">
<!-- 测试Panel -->
<Border
x: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">
<TextBlock
HorizontalAlignment="Center"
FontSize="16"
FontWeight="Bold"
Foreground="White"
Text="点击我缩放" />
<TextBlock
Margin="0,5"
HorizontalAlignment="Center"
FontSize="12"
Foreground="White"
Text="左键放大 · 右键缩小" />
<TextBlock
x: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>
<!-- 底部日志 -->
<TextBox
x: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">
<Rectangle
Canvas.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:
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); // 20fps
mouseMoveTimer.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);
}
}
}