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.
the mouse on the line : moving and zooming not works.
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;
}
}
}
CodePudding user response:
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.
If you do not interact with them, you could also consider making the paths IsHitTestVisible false.
CodePudding user response:
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);