英文:
Canvas MouseMove event not detected on drawn elements
问题
我需要你的帮助。我正在开发一个带有鼠标管理功能的绘图程序,用于移动和缩放绘图。
一切都运行得相当顺利。
然而,我遇到了一个问题。当光标位于一条线上时,我的MouseWheel和MouseMove事件不再起作用。
这可能是因为程序将其视为我的画布的子元素而不是我的画布。
我不知道如何解决这个问题?
在画布上的鼠标:移动和缩放有效。
在线上的鼠标:移动和缩放不起作用。
XAML
<Window x:Class="CanvasCad.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:CanvasCad"
mc:Ignorable="d"
Title="MainWindow"
Height="450" Width="800"
Loaded="Window_Loaded"
>
<Grid Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Margin="5">
<Label Content="Model origin in canvas"/>
<Label Name="lblOriginInCanvasX" Content="X="/>
<Label Name="lblOriginInCanvasY" Content="Y="/>
<Separator Height="1" />
<Label Content="Model scale in canvas"/>
<Label Name="lblScaleModelInCanvas" Content="z="/>
<Separator Height="1" />
<Label Content="Mouse in canvas"/>
<Label Name="lblMouseInCanvasX" Content="X="/>
<Label Name="lblMouseInCanvasY" Content="Y="/>
<Separator Height="1" />
<Label Content="Mouse in model"/>
<Label Name="lblMouseInModelX" Content="X="/>
<Label Name="lblMouseInModelY" Content="Y="/>
<Separator Height="1" />
</StackPanel>
<Canvas
Name="CanvasCad"
Grid.Column="1"
>
</Canvas>
<StackPanel
Grid.Column="2">
<Button Name="btTest">Test</Button>
</StackPanel>
</Grid>
</Window>
C#
// 这里是你的C#代码,不需要翻译
// 这里是你的C#代码,不需要翻译
// 这里是你的C#代码,不需要翻译
// 这里是你的C#代码,不需要翻译
<details>
<summary>英文:</summary>
I need your help. I'm working on a drawing program with mouse management of moving and zooming the drawing.
It all works quite well.
However, I am having a problem. My MouseWheel and MouseMove events no longer work when the cursor is on a line.
It's probably because the program considers it to be the child element of my canvas and not my canvas.
I don't see how to work around the problem?
the mouse on the canvas : moving and zooming works.
mouse on the canvas
[![enter image description here][1]][1]
the mouse on the line : moving and zooming not works.
the mouse on the line
[![enter image description here][2]][2]
**xaml**
<Window x:Class="CanvasCad.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:CanvasCad"
mc:Ignorable="d"
Title="MainWindow"
Height="450" Width="800"
Loaded="Window_Loaded"
>
<Grid Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Margin="5">
<Label Content="Model origin in canvas"/>
<Label Name="lblOriginInCanvasX" Content="X="/>
<Label Name="lblOriginInCanvasY" Content="Y="/>
<Separator Height="1" />
<Label Content="Model scale in canvas"/>
<Label Name="lblScaleModelInCanvas" Content="z="/>
<Separator Height="1" />
<Label Content="Mouse in canvas"/>
<Label Name="lblMouseInCanvasX" Content="X="/>
<Label Name="lblMouseInCanvasY" Content="Y="/>
<Separator Height="1" />
<Label Content="Mouse in model"/>
<Label Name="lblMouseInModelX" Content="X="/>
<Label Name="lblMouseInModelY" Content="Y="/>
<Separator Height="1" />
</StackPanel>
<Canvas
Name="CanvasCad"
Grid.Column="1"
>
</Canvas>
<StackPanel
Grid.Column="2">
<Button Name="btTest">Test</Button>
</StackPanel>
</Grid>
</Window>
c#
**c#**
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Xml.Linq;
namespace CanvasCad
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private Point _originModelInCanvas;
private double _scaleModelInCanvas;
private double _zoomFactor;
private bool _IsModelMove;
private Point _vectorMouseToOriginInCanvas;
private List<Entity> _entities;
public MainWindow()
{
InitializeComponent();
//Rendu du canvas
CanvasCad.Background = new SolidColorBrush(Colors.Black);
CanvasCad.ClipToBounds = true;
//Gestion des évènement
CanvasCad.MouseMove += CanvasCad_MouseMove;
CanvasCad.MouseDown += CanvasCad_MouseDown;
CanvasCad.MouseUp += CanvasCad_MouseUp;
CanvasCad.MouseWheel += CanvasCad_MouseWheel;
//Balise
_IsModelMove = false;
//Definition de l'origine
_originModelInCanvas = new Point(0, 0);
_scaleModelInCanvas = 1;
//Facteur de zoom
_zoomFactor = 0.1;
//Liste des entitées de dessin
_entities = new List<Entity>();
Entity e;
//axe x
e = new Entity(new Point(-500, 0), new Point(500, 0), new SolidColorBrush(Colors.Blue), 2);
_entities.Add(e);
//axe y
e = new Entity(new Point(0, -500), new Point(0, 500), new SolidColorBrush(Colors.Blue), 2);
_entities.Add(e);
//ligne1
e = new Entity(new Point(50, 70), new Point(100, 30), new SolidColorBrush(Colors.Red), 4);
_entities.Add(e);
//ligne2
e = new Entity(new Point(200, -83), new Point(-96, 300), new SolidColorBrush(Colors.Orange), 4);
_entities.Add(e);
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
//Definition de l'origine
_originModelInCanvas = new Point(75, 130);
_scaleModelInCanvas = 1;
//Affichage
lblOriginInCanvasX.Content = $"X={_originModelInCanvas.X.ToString()}";
lblOriginInCanvasY.Content = $"Y={_originModelInCanvas.Y.ToString()}";
lblScaleModelInCanvas.Content = $"Z={_scaleModelInCanvas.ToString()}";
//Redessiner
Redraw();
}
private void CanvasCad_MouseMove(object sender, MouseEventArgs e)
{
//Position de la souris dans le canvas (coordonnées wpf)
Point ptMouseInCanvas = e.GetPosition(CanvasCad);
//Position de la souris dans le World
Point ptMouseInModel = ConvertPoint.MouseCanvasToModel (ptMouseInCanvas, _originModelInCanvas, _scaleModelInCanvas);
//Gestion du déplacement du Model par la souris
if (_IsModelMove)
//Bouton de la roulette appuyé
{
_originModelInCanvas = new Point(ptMouseInCanvas.X-_vectorMouseToOriginInCanvas.X, ptMouseInCanvas.Y - _vectorMouseToOriginInCanvas.Y);
}
//Affichage
lblMouseInCanvasX.Content = $"X={ptMouseInCanvas.X.ToString()}";
lblMouseInCanvasY.Content = $"Y={ptMouseInCanvas.Y.ToString()}";
lblMouseInModelX.Content = $"X={ptMouseInModel.X.ToString()}";
lblMouseInModelY.Content = $"Y={ptMouseInModel.Y.ToString()}";
//Redessiner
Redraw();
}
private void CanvasCad_MouseWheel(object sender, MouseWheelEventArgs e)
{
//Position de la souris dans le canvas (coordonnées wpf)
Point ptMouseInCanvas = e.GetPosition(CanvasCad);
if (!_IsModelMove)
{
//Détection de la roulette
double zoom = 0;
if (e.Delta > 0) { zoom = 1 + _zoomFactor; }
if (e.Delta < 0) { zoom = 1 - _zoomFactor; }
//Vecteur entre la souris et l'origine
_vectorMouseToOriginInCanvas = new Point(ptMouseInCanvas.X - _originModelInCanvas.X, ptMouseInCanvas.Y - _originModelInCanvas.Y);
_originModelInCanvas = new Point(ptMouseInCanvas.X - _vectorMouseToOriginInCanvas.X * zoom, ptMouseInCanvas.Y - _vectorMouseToOriginInCanvas.Y * zoom);
_scaleModelInCanvas = _scaleModelInCanvas * zoom;
//Redessiner
Redraw();
}
}
private void CanvasCad_MouseDown(object sender, MouseButtonEventArgs e)
{
//Position de la souris dans le canvas
Point ptMouseInCanvas = new Point(e.GetPosition(CanvasCad).X, e.GetPosition(CanvasCad).Y);
//Bouton de la souris
switch (e.ChangedButton)
{
//
case MouseButton.Left:
break;
//
case MouseButton.Right:
break;
//Gestion du déplacement du Model
case MouseButton.Middle:
//Bouton de la roulette appuyé
_IsModelMove = true;
//Vecteur entre la souris et l'origine
_vectorMouseToOriginInCanvas = new Point(ptMouseInCanvas.X-_originModelInCanvas.X, ptMouseInCanvas.Y - _originModelInCanvas.Y);
break;
}
}
private void CanvasCad_MouseUp(object sender, MouseButtonEventArgs e)
{
//Position de la souris dans le canvas
Point ptMouseCanvas = new Point(e.GetPosition(this).X, e.GetPosition(this).Y);
//Bouton de la souris
switch (e.ChangedButton)
{
case MouseButton.Left:
break;
case MouseButton.Right:
break;
case MouseButton.Middle:
//Bouton de la roulette relachée
_IsModelMove = false;
break;
}
}
private void Redraw()
{
//Canvas nettoyé
CanvasCad.Children.Clear();
//Ajout des entitées au canvas
foreach (Entity e in _entities)
{
//CanvasCad.Children.Add(e.ToModel());
CanvasCad.Children.Add(e.ToModel(_originModelInCanvas, _scaleModelInCanvas));
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;
namespace CanvasCad
{
internal class Entity
{
private Point _pointStart;
public Point pointStart
{
get { return _pointStart; }
set { _pointStart = value; }
}
private Point _pointEnd;
public Point pointEnd
{
get { return _pointEnd; }
set { _pointEnd = value; }
}
private Brush _brush;
public Brush brush
{
get { return _brush; }
set { _brush = value; }
}
private int _thickness;
public int thickness
{
get { return _thickness; }
set { _thickness = value; }
}
public Entity()
{
_pointStart = new Point(0, 0);
_pointEnd = new Point(0, 0);
//_SolidColorBrush = new SolidColorBrush(Color.FromRgb(0, 0, 255));
_brush = Brushes.Black;
_thickness = 1;
}
public Entity(Point pointStart, Point pointEnd, Brush brush, int thickness)
{
_pointStart = pointStart;
_pointEnd = pointEnd;
_brush = brush;
_thickness = thickness;
}
public Path ToCanvas()
{
Path p = new Path();
p.Stroke = _brush;
p.StrokeThickness = _thickness;
LineGeometry lg = new LineGeometry(_pointStart, _pointEnd);
GeometryGroup gg = new GeometryGroup();
gg.Children.Add(lg);
p.Data = gg;
return p;
}
public Path ToModel(Point origin, double scale)
{
Path p = new Path();
p.Stroke = _brush;
p.StrokeThickness = _thickness;
LineGeometry lg = new LineGeometry(ConvertPoint.CanvasToModel(_pointStart, origin, scale), ConvertPoint.CanvasToModel(_pointEnd, origin, scale));
//Matrix translateMatrix = new Matrix();
//translateMatrix.Translate(100, 0);
//p.Transform(translateMatrix);
GeometryGroup gg = new GeometryGroup();
gg.Children.Add(lg);
p.Data = gg;
return p;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
namespace CanvasCad
{
internal class ConvertPoint
{
public static Point CanvasToModel(Point point, Point origine, double scale)
{
//point de référence
Point p = new Point(point.X, point.Y);
//symétrie par rapport à y
p = new Point(p.X, -p.Y);
//mise à l'echelle par rapport à l'origine
p = new Point(p.X*scale, p.Y*scale);
//déplacement de l'origine
p = new Point(p.X + origine.X, p.Y + origine.Y);
return p;
}
public static Point MouseCanvasToModel(Point mouse, Point origine, double scale)
{
//point de référence
Point p = new Point(mouse.X, mouse.Y);
//déplacement de l'origine
p = new Point(p.X - origine.X, p.Y - origine.Y);
//mise à l'echelle par rapport à l'origine
p = new Point(p.X / scale, p.Y / scale);
//symétrie par rapport à y
p = new Point(p.X, -p.Y);
return p;
}
}
}
[1]: https://i.stack.imgur.com/iQTWd.png
[2]: https://i.stack.imgur.com/DOgWH.png
</details>
# 答案1
**得分**: 1
有几种方法可以解决这个问题。
问题的原因是因为您在画布上处理事件,而路径位于其中。事件在路径上触发而不是画布上,因为它首先被“命中”。
解决这个问题的最简单方法是使用预览事件。
例如,
PreviewMouseMove。
https://learn.microsoft.com/en-us/dotnet/api/system.windows.uielement.previewmousemove?view=windowsdesktop-6.0
和
PreviewMouseWheel
https://learn.microsoft.com/en-us/dotnet/api/system.windows.uielement.previewmousewheel?view=windowsdesktop-6.0
之所以这些事件会起作用,而mousemove和mousewheel不会,是因为在WPF中路由事件的工作方式不同。
https://learn.microsoft.com/en-us/dotnet/desktop/wpf/events/routed-events-overview?view=netdesktop-6.0
如果您不与它们交互,还可以考虑将路径的IsHitTestVisible属性设置为false。
<details>
<summary>英文:</summary>
There are several ways to work round this.
The problem is caused because you handle events on the canvas and your paths are in the way. The event fires on the path rather than the canvas because it's "hit" first.
The simplest approach to working round that is to use preview events.
eg
PreviewMouseMove.
https://learn.microsoft.com/en-us/dotnet/api/system.windows.uielement.previewmousemove?view=windowsdesktop-6.0
and
PreviewMouseWheel
https://learn.microsoft.com/en-us/dotnet/api/system.windows.uielement.previewmousewheel?view=windowsdesktop-6.0
The reason these will work whilst mousemove and mousewheel will not is because of the way routed events work in wpf.
https://learn.microsoft.com/en-us/dotnet/desktop/wpf/events/routed-events-overview?view=netdesktop-6.0
If you do not interact with them, you could also consider making the paths IsHitTestVisible false.
</details>
# 答案2
**得分**: 1
在@Andy的回答中添加。首先,一些常规信息。RoutedEvent 主要有以下特点:
- 冒泡 - 从最终元素上升到窗口的事件;
- 隧道 - 从窗口浸入最终元素的事件。
在任何级别上,事件都可以标记为已处理,然后带有此标志继续沿着路由移动。
CLR 包装事件(例如,UIElement.MouseMove)是访问静态 RoutedEvent 方法的代理方法(参见[源代码][1]):
```cs
public event MouseEventHandler MouseMove
{
add { AddHandler(Mouse.MouseMoveEvent, value, false); }
remove { RemoveHandler(Mouse.MouseMoveEvent, value); }
}
AddHandler 方法中的 false 参数表示不应调用带有执行标志的事件处理程序。
因此,如果子元素已将冒泡事件标记为已处理,则不会调用使用事件的 CLR 包装的父元素处理程序。
这正是在你的情况中发生的事情。
实际上是一种可能的解决方案。
您可以直接使用 RoutedEvent 的静态方法连接处理程序。在这种情况下,您可以指定它即使对于已处理的事件也要调用。
AddHandler(Mouse.MouseMoveEvent, new MouseEventHandler(CanvasCad_MouseMove), true);
英文:
Adding to @Andy's answer.
First, some general information. RoutedEvent are mostly:
- bubbling - those that rise from the final element up to the Window;
- tunneling - those that are immersed from the Window to the final element.
At any level, an event can be marked as processed and it will move further along the route with this flag.
The CLR wrapper-event (for example, UIElement.MouseMove) is a proxy methods that accesses the static RoutedEvent methods (see Source Code):
public event MouseEventHandler MouseMove
{
add { AddHandler(Mouse.MouseMoveEvent, value, false); }
remove { RemoveHandler(Mouse.MouseMoveEvent, value); }
}
The false parameter in the AddHandler method indicates that the handler should not be called for events marked with the execution flag.
For this reason, if a child element has marked the bubble event as handled, then the parent element's handler wired with the event's CLR wrapper will not be invoked.
This is exactly what happened in your case.
Actually a possible solution.
You can connect the handler directly using the static methods of RoutedEvent. In this case, you can specify that it be called even for processed events.
AddHandler(Mouse.MouseMoveEvent, new MouseEventHandler(CanvasCad_MouseMove), true);
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论