Home > Software design >  Canvas MouseMove event not detected on drawn elements
Canvas MouseMove event not detected on drawn elements

Time:01-10

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

    }
}

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.

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.

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);
  • Related