Search code examples
c#wpfcanvasmouseevent

Canvas MouseMove event not detected on drawn elements


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

the mouse on the line : moving and zooming not works.

the mouse on the line
enter image description here

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;
        }

    }
}

Solution

  • 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.