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
相关产品推荐
相关产品推荐

