WPF 获取鼠标相对于控件的坐标信息,控制控件锚点放缩

使用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);
        }
    }
}
相关推荐
张人玉6 小时前
Prism 框架笔记及实例
c#·wpf·prism
Macbethad18 小时前
EtherCAT从站程序技术方案:基于WPF的高性能实现
网络协议·wpf
Macbethad18 小时前
基于WPF的485主站系统技术方案
网络协议·wpf·信息与通信
赵财猫._.2 天前
HarmonyOS内存优化实战:泄漏检测、大对象管理与垃圾回收策略
华为·wpf·harmonyos
赵财猫._.2 天前
鸿蒙超级终端体验:无缝流转的底层实现与用户体验优化
wpf·harmonyos·ux
故事不长丨2 天前
C#委托的使用
c#·wpf·winfrom·委托·网站开发
行走正道2 天前
【探索实战】跨云应用分发自动化实战:基于Kurator的统一交付体系深度解析
运维·自动化·wpf·kurator·跨云分发
Macbethad2 天前
基于WPF的Ethernet/IP主站程序技术方案
网络协议·tcp/ip·wpf
张人玉3 天前
Prism Template Pack 完整使用示例(VS2022 + .NET 8 + DryIoc)
.net·wpf·prism
棉晗榜3 天前
wpf 在XAML中配置视图模型,通过 d:DataContext设置设计时类型,方便按F12跳转查看类型
wpf