You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WPF MVVM:如何获取Canvas上的鼠标坐标(修复点击矩形坐标异常)

问题描述

我有一个包含Canvas的窗口,Canvas尺寸小于窗口本身。点击Canvas时能在点击位置绘制矩形,但点击已有矩形时,鼠标坐标是相对于该矩形左上角而非Canvas。

XAML代码

<ItemsControl ItemsSource="{Binding CanvasItems}" Grid.Column="1" >
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas Background="White">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="MouseLeftButtonDown">
                        <i:InvokeCommandAction Command="{Binding MouseButtonDownCommand}" PassEventArgsToCommand="True"/>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </Canvas>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemContainerStyle>
        <Style TargetType="Shape">
            <Setter Property="Canvas.Left" Value="{Binding ModelLeft}"/>
            <Setter Property="Canvas.Top" Value="{Binding ModelTop}"/>
        </Style>
    </ItemsControl.ItemContainerStyle>
</ItemsControl>

ViewModel代码

private void OnMouseButtonDown(object obj)
{
    var mouseArgs = obj as MouseButtonEventArgs;
    var point = mouseArgs.GetPosition((IInputElement)mouseArgs.Source);
    
    ModelLeft = (int)point.X;
    ModelTop = (int)point.Y;

    Rectangle rect = new Rectangle();
    
    rect.Stroke = new SolidColorBrush(Colors.Black);
    rect.Fill = new SolidColorBrush(Colors.Red);
    rect.Width = 200;
    rect.Height = 200;

    CanvasItems.Add(rect);
}

说明:ModelLeft和ModelTop是int类型属性,CanvasItems是ObservableCollection<object>,矩形使用System.Windows.Shapes命名空间。需求是始终获取相对于Canvas的鼠标坐标,而非已有图形的坐标。


解决方案

问题核心在于GetPosition的参数用了mouseArgs.Source:点击矩形时,Source指向矩形而非Canvas,导致坐标相对矩形计算。要始终获取Canvas的坐标,需将GetPosition的参数固定为Canvas实例,以下是两种实现方式:

方式一:XAML传递Canvas引用(直接高效)

修改XAML,给Canvas命名并通过命令参数传递引用:

<Canvas Background="White" x:Name="drawingCanvas">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="MouseLeftButtonDown">
            <i:InvokeCommandAction Command="{Binding MouseButtonDownCommand}" 
                                   PassEventArgsToCommand="True"
                                   CommandParameter="{Binding ElementName=drawingCanvas}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</Canvas>

更新ViewModel方法,接收Canvas参数并计算坐标:

private void OnMouseButtonDown(object obj)
{
    var paramArray = obj as object[];
    if (paramArray == null || paramArray.Length < 2) return;
    
    var mouseArgs = paramArray[0] as MouseButtonEventArgs;
    var canvas = paramArray[1] as Canvas;
    if (mouseArgs == null || canvas == null) return;

    var point = mouseArgs.GetPosition(canvas);
    
    ModelLeft = (int)point.X;
    ModelTop = (int)point.Y;

    Rectangle rect = new Rectangle();
    
    rect.Stroke = new SolidColorBrush(Colors.Black);
    rect.Fill = new SolidColorBrush(Colors.Red);
    rect.Width = 200;
    rect.Height = 200;

    CanvasItems.Add(rect);
}

方式二:通过视觉树查找Canvas(无需修改XAML)

如果不想改动XAML,可在ViewModel中通过视觉树向上查找Canvas:

private void OnMouseButtonDown(object obj)
{
    var mouseArgs = obj as MouseButtonEventArgs;
    if (mouseArgs == null) return;

    // 从事件源向上遍历视觉树,找到Canvas
    var sourceElement = mouseArgs.Source as DependencyObject;
    var canvas = FindVisualParent<Canvas>(sourceElement);
    if (canvas == null) return;

    var point = mouseArgs.GetPosition(canvas);
    
    ModelLeft = (int)point.X;
    ModelTop = (int)point.Y;

    Rectangle rect = new Rectangle();
    
    rect.Stroke = new SolidColorBrush(Colors.Black);
    rect.Fill = new SolidColorBrush(Colors.Red);
    rect.Width = 200;
    rect.Height = 200;

    CanvasItems.Add(rect);
}

// 辅助方法:递归查找指定类型的视觉父元素
private T FindVisualParent<T>(DependencyObject child) where T : DependencyObject
{
    var parent = VisualTreeHelper.GetParent(child);
    if (parent == null) return null;
    
    if (parent is T parentT)
        return parentT;
    
    return FindVisualParent<T>(parent);
}

补充说明

  • 方式一性能更优,适合能直接获取Canvas引用的场景;
  • 方式二更灵活,无需修改XAML,适用于无法直接绑定Canvas的情况;
  • 两种方式都能确保GetPosition以Canvas为参照,得到正确的相对坐标。

内容的提问来源于stack exchange,提问作者Turwaith

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 23:36:17