.
GDI+ FAQ
Skip Navigation LinksWelcome : Windows Forms Tips and Tricks : Backtrack the mouse to the virtual page

How to back-track the mouse to the virtual page

WYSIWYG (What You See Is What You Get) drawing systems rely on representing data on-screen in an accurate manner and usually enable the user to interact with the graphical information using the mouse. Such systems normally use a representation of data stored in a structured model and draw that data on-screen accordingly.

Being able to match the position of the mouse to the exact position of the data representation is therefore very important.

The mouse position is always reported as relative to the origin of the client-area of a window and so the relationship of mouse to model will only ever be accurate in the following circumstances:

  • When the origin of the virtual page coincides with the origin of the window. In other words, the window has not been scrolled.

  • When the scale of the virtual data model is a 1:1 relation to the window on which it's represented. For example no zooming or rotation of the virtual page.

Most WYSIWYG drawing systems have some sort of zoom or pan facility and the simplest way of implementing this is by creating a matrix that describes the scale and offset of the axes and then draw all the items with that transform in-place. The great thing is that the same transform that enables you to draw the panned or zoomed output also enables you to track the mouse's position on your screen to the corresponding position in the virtual page using the inverse of the drawing matrix.

The following listing shows how pan and zoom are catered for in the paint event.

//Creates the drawing matrix with the right zoom;

Matrix mx=new Matrix(_zoom,0,0,_zoom,0,0);

//pans it according to the scroll bars

mx.Translate(this.AutoScrollPosition.X * (1.0f/_zoom), this.AutoScrollPosition.Y * (1.0f/_zoom));

//use it for drawing

e.Graphics.Transform=mx;

Now, any drawing performed on the control's surface will be scaled and positioned according to the zoom factor and the scroll-bar positions. When the mouse is moved around over the window however, the coordinates are still in relation to the control, not the data drawn in it. The following listing shows how to use the drawing matrix to backtrack the mouse.

    protected Point BacktrackMouse(MouseEventArgs e)

    {

      //Creates the drawing matrix with the right zoom;

      Matrix mx=new Matrix(_zoom,0,0,_zoom,0,0);

      //pans it according to the scroll bars

      mx.Translate(this.AutoScrollPosition.X * (1.0f/_zoom), this.AutoScrollPosition.Y * (1.0f/_zoom));

      //inverts it

      mx.Invert();

      //uses it to transform the current mouse position

      Point[] pa=new Point[]{new Point(e.X,e.Y)};

      mx.TransformPoints(pa);

      return pa[0];

    }

 

Now, whenever the mouse button is clicked or the mouse moved, it is possible to condition the coordinates provided by the MouseEventArgs to provide a coordinate that corresponds to the virtual page no-matter what the zoom or pan values may be. The following demonstration application enables the user do draw spots of colour in response to mouse clicks and drawing strokes. The drawing is performed by storing a long list of points in an ArrayList and redrawing them at each draw cycle. It is possible to input points at any zoom level or pan position.

Figure 1. The demo application at work.

using System;

using System.Drawing;

using System.Drawing.Drawing2D;

using System.Collections;

using System.ComponentModel;

using System.Windows.Forms;

using System.Data;

 

namespace backtrack

{

  /// <summary>

  /// Summary description for Form1.

  /// </summary>

  public class Form1 : System.Windows.Forms.Form

  {

    private System.Windows.Forms.MainMenu mainMenu1;

    private System.Windows.Forms.MenuItem menuItem1;

    private System.Windows.Forms.MenuItem menuItem2;

    private System.Windows.Forms.MenuItem menuItem3;

    private System.Windows.Forms.MenuItem menuItem4;

    private System.Windows.Forms.MenuItem menuItem5;

    private System.Windows.Forms.MenuItem menuItem6;

    /// <summary>

    /// Required designer variable.

    /// </summary>

    private System.ComponentModel.Container components = null;

 

    float _zoom=1.0f;

    public float Zoom

    {

      get{return _zoom;}

      set{

        _zoom=value;

        RecalcAutosize();

      }

    }

 

    ArrayList _blobs=new ArrayList();

 

    bool _mouseDown=false;

 

 

    protected void RecalcAutosize()

    {

      this.AutoScrollMinSize=new Size((int)(_zoom*1024), (int)(_zoom*768));

      this.AutoScrollPosition=new Point(0,0);

      Invalidate();

    }

 

 

    public Form1()

    {

      //

      // Required for Windows Form Designer support

      //

      InitializeComponent();

 

      SetStyle(ControlStyles.AllPaintingInWmPaint |

        ControlStyles.DoubleBuffer |

        ControlStyles.UserPaint |

        ControlStyles.ResizeRedraw,

        true);

    }

 

    /// <summary>

    /// Clean up any resources being used.

    /// </summary>

    protected override void Dispose( bool disposing )

    {

      if( disposing )

      {

        if (components != null)

        {

          components.Dispose();

        }

      }

      base.Dispose( disposing );

    }

 

    #region Windows Form Designer generated code

    /// <summary>

    /// Required method for Designer support - do not modify

    /// the contents of this method with the code editor.

    /// </summary>

    private void InitializeComponent()

    {

      this.mainMenu1 = new System.Windows.Forms.MainMenu();

      this.menuItem1 = new System.Windows.Forms.MenuItem();

      this.menuItem2 = new System.Windows.Forms.MenuItem();

      this.menuItem3 = new System.Windows.Forms.MenuItem();

      this.menuItem4 = new System.Windows.Forms.MenuItem();

      this.menuItem5 = new System.Windows.Forms.MenuItem();

      this.menuItem6 = new System.Windows.Forms.MenuItem();

      //

      // mainMenu1

      //

      this.mainMenu1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {

                                            this.menuItem1,

                                            this.menuItem2});

      //

      // menuItem1

      //

      this.menuItem1.Index = 0;

      this.menuItem1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {

                                            this.menuItem6});

      this.menuItem1.Text = "&File";

      //

      // menuItem2

      //

      this.menuItem2.Index = 1;

      this.menuItem2.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {

                                            this.menuItem3,

                                            this.menuItem4,

                                            this.menuItem5});

      this.menuItem2.Text = "Zoom";

      //

      // menuItem3

      //

      this.menuItem3.Index = 0;

      this.menuItem3.Text = "50%";

      this.menuItem3.Click += new System.EventHandler(this.menuItem3_Click);

      //

      // menuItem4

      //

      this.menuItem4.Index = 1;

      this.menuItem4.Text = "100%";

      this.menuItem4.Click += new System.EventHandler(this.menuItem4_Click);

      //

      // menuItem5

      //

      this.menuItem5.Index = 2;

      this.menuItem5.Text = "200%";

      this.menuItem5.Click += new System.EventHandler(this.menuItem5_Click);

      //

      // menuItem6

      //

      this.menuItem6.Index = 0;

      this.menuItem6.Text = "E&xit";

      this.menuItem6.Click += new System.EventHandler(this.menuItem6_Click);

      //

      // Form1

      //

      this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);

      this.AutoScroll = true;

      this.AutoScrollMinSize = new System.Drawing.Size(1024, 768);

      this.BackColor = System.Drawing.Color.White;

      this.ClientSize = new System.Drawing.Size(275, 249);

      this.Menu = this.mainMenu1;

      this.Name = "Form1";

      this.Text = "Form1";

 

    }

    #endregion

 

    /// <summary>

    /// The main entry point for the application.

    /// </summary>

    [STAThread]

    static void Main()

    {

      Application.Run(new Form1());

    }

 

    private void menuItem6_Click(object sender, System.EventArgs e)

    {

      Application.Exit();

    }

 

    private void menuItem3_Click(object sender, System.EventArgs e)

    {

      Zoom=0.5f;

    }

 

    private void menuItem4_Click(object sender, System.EventArgs e)

    {

      Zoom=1.0f;

    }

 

    private void menuItem5_Click(object sender, System.EventArgs e)

    {

      Zoom=2.0f;

    }

 

    protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e)

    {

      base.OnMouseDown(e);

      _mouseDown=true;

      Point pt=BacktrackMouse(e);

      this._blobs.Add(pt);

      Invalidate();

    }

 

    protected override void OnMouseMove(System.Windows.Forms.MouseEventArgs e)

    {

      base.OnMouseMove(e);

      if(_mouseDown)

      {

        Point pt=BacktrackMouse(e);

        this._blobs.Add(pt);

        Invalidate();

      }

    }

 

    protected override void OnMouseUp(System.Windows.Forms.MouseEventArgs e)

    {

      base.OnMouseUp(e);

      _mouseDown=false;

    }

 

    protected Point BacktrackMouse(MouseEventArgs e)

    {

      //Creates the drawing matrix with the right zoom;

      Matrix mx=new Matrix(_zoom,0,0,_zoom,0,0);

      //pans it according to the scroll bars

      mx.Translate(this.AutoScrollPosition.X * (1.0f/_zoom), this.AutoScrollPosition.Y * (1.0f/_zoom));

      //inverts it

      mx.Invert();

      //uses it to transform the current mouse position

      Point[] pa=new Point[]{new Point(e.X,e.Y)};

      mx.TransformPoints(pa);

      return pa[0];

    }

 

    protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)

    {

 

      //Creates the drawing matrix with the right zoom;

      Matrix mx=new Matrix(_zoom,0,0,_zoom,0,0);

      //pans it according to the scroll bars

      mx.Translate(this.AutoScrollPosition.X * (1.0f/_zoom), this.AutoScrollPosition.Y * (1.0f/_zoom));

      //use it for drawing

      e.Graphics.Transform=mx;

 

      SolidBrush sb=new SolidBrush(Color.Red);

      //draw all the points

      foreach(Point pt in _blobs)

        e.Graphics.FillEllipse(sb,pt.X-5, pt.Y-5, 10,10);

      sb.Dispose();

    }

 

 

 

  }

}

 

Return to Windows Forms Tips and Tricks

Visit the GDI+ FAQ

Copyright Bob Powell 2004. All rights reserved.

 

Sponsored By
DaraizeTechnologies.com
Bob Powell

Create your badge

Copyright © Bob Powell 2000-2012.  All rights reserved.