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:
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; 'Creates the drawing matrix with the right zoom; Dim mx As New Matrix(_zoom, 0, 0, _zoom, 0, 0) 'pans it according to the scroll bars mx.Translate(Me.AutoScrollPosition.X * (1.0F / _zoom), Me.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]; }
Protected Function BacktrackMouse(ByVal e As MouseEventArgs) As Point 'Creates the drawing matrix with the right zoom; Dim mx As New Matrix(_zoom, 0, 0, _zoom, 0, 0) 'pans it according to the scroll bars mx.Translate(Me.AutoScrollPosition.X * (1.0F / _zoom), Me.AutoScrollPosition.Y * (1.0F / _zoom)) 'inverts it mx.Invert() 'uses it to transform the current mouse position Dim pa() As Point = {New Point(e.X, e.Y)} mx.TransformPoints(pa) Return pa(0) End Function 'BacktrackMouse
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(); }
} }
Imports System Imports System.Drawing Imports System.Drawing.Drawing2D Imports System.Collections Imports System.ComponentModel Imports System.Windows.Forms Imports System.Data
Namespace backtrack '/ <summary> '/ Summary description for Form1. '/ </summary>
Public Class Form1 Inherits System.Windows.Forms.Form Private mainMenu1 As System.Windows.Forms.MainMenu Private menuItem1 As System.Windows.Forms.MenuItem Private menuItem2 As System.Windows.Forms.MenuItem Private WithEvents menuItem3 As System.Windows.Forms.MenuItem Private WithEvents menuItem4 As System.Windows.Forms.MenuItem Private WithEvents menuItem5 As System.Windows.Forms.MenuItem Private WithEvents menuItem6 As System.Windows.Forms.MenuItem '/ <summary> '/ Required designer variable. '/ </summary> Private components As System.ComponentModel.Container = Nothing
Private _zoom As Single = 1F
Public Property Zoom() As Single Get Return _zoom End Get Set _zoom = value RecalcAutosize() End Set End Property
Private _blobs As New ArrayList()
Private _mouseDown As Boolean = False
Protected Sub RecalcAutosize() Me.AutoScrollMinSize = New Size(CInt(_zoom * 1024), CInt(_zoom * 768)) Me.AutoScrollPosition = New Point(0, 0) Invalidate() End Sub 'RecalcAutosize
Public Sub New() ' ' Required for Windows Form Designer support ' InitializeComponent()
SetStyle(ControlStyles.AllPaintingInWmPaint Or ControlStyles.DoubleBuffer Or ControlStyles.UserPaint Or ControlStyles.ResizeRedraw, True) End Sub 'New
'/ <summary> '/ Clean up any resources being used. '/ </summary> Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean) If disposing Then If Not (components Is Nothing) Then components.Dispose() End If End If MyBase.Dispose(disposing) End Sub 'Dispose
#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 Sub InitializeComponent() Me.mainMenu1 = New System.Windows.Forms.MainMenu Me.menuItem1 = New System.Windows.Forms.MenuItem Me.menuItem2 = New System.Windows.Forms.MenuItem Me.menuItem3 = New System.Windows.Forms.MenuItem Me.menuItem4 = New System.Windows.Forms.MenuItem Me.menuItem5 = New System.Windows.Forms.MenuItem Me.menuItem6 = New System.Windows.Forms.MenuItem ' ' mainMenu1 ' Me.mainMenu1.MenuItems.AddRange(New System.Windows.Forms.MenuItem() {Me.menuItem1, Me.menuItem2}) ' ' menuItem1 ' Me.menuItem1.Index = 0 Me.menuItem1.MenuItems.AddRange(New System.Windows.Forms.MenuItem() {Me.menuItem6}) Me.menuItem1.Text = "&File" ' ' menuItem2 ' Me.menuItem2.Index = 1 Me.menuItem2.MenuItems.AddRange(New System.Windows.Forms.MenuItem() {Me.menuItem3, Me.menuItem4, Me.menuItem5}) Me.menuItem2.Text = "Zoom" ' ' menuItem3 ' Me.menuItem3.Index = 0 Me.menuItem3.Text = "50%" ' ' menuItem4 ' Me.menuItem4.Index = 1 Me.menuItem4.Text = "100%" ' ' menuItem5 ' Me.menuItem5.Index = 2 Me.menuItem5.Text = "200%" ' ' menuItem6 ' Me.menuItem6.Index = 0 Me.menuItem6.Text = "E&xit" ' ' Form1 ' Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13) Me.AutoScroll = True Me.AutoScrollMinSize = New System.Drawing.Size(1024, 768) Me.BackColor = System.Drawing.Color.White Me.ClientSize = New System.Drawing.Size(275, 249) Me.Menu = Me.mainMenu1 Me.Name = "Form1" Me.Text = "Form1" End Sub 'InitializeComponent #End Region
'/ <summary> '/ The main entry point for the application. '/ </summary> <STAThread()> _ Shared Sub Main() Application.Run(New Form1) End Sub 'Main
Private Sub menuItem6_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles menuItem6.Click Application.Exit() End Sub 'menuItem6_Click
Private Sub menuItem3_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles menuItem3.Click Zoom = 0.5F End Sub 'menuItem3_Click
Private Sub menuItem4_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles menuItem4.Click Zoom = 1.0F End Sub 'menuItem4_Click
Private Sub menuItem5_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles menuItem5.Click Zoom = 2.0F End Sub 'menuItem5_Click
Protected Overrides Sub OnMouseDown(ByVal e As System.Windows.Forms.MouseEventArgs) MyBase.OnMouseDown(e) _mouseDown = True Dim pt As Point = BacktrackMouse(e) Me._blobs.Add(pt) Invalidate() End Sub 'OnMouseDown
Protected Overrides Sub OnMouseMove(ByVal e As System.Windows.Forms.MouseEventArgs) MyBase.OnMouseMove(e) If _mouseDown Then Dim pt As Point = BacktrackMouse(e) Me._blobs.Add(pt) Invalidate() End If End Sub 'OnMouseMove
Protected Overrides Sub OnMouseUp(ByVal e As System.Windows.Forms.MouseEventArgs) MyBase.OnMouseUp(e) _mouseDown = False End Sub 'OnMouseUp
Protected Function BacktrackMouse(ByVal e As MouseEventArgs) As Point 'Creates the drawing matrix with the right zoom; Dim mx As New Matrix(_zoom, 0, 0, _zoom, 0, 0) 'pans it according to the scroll bars mx.Translate(Me.AutoScrollPosition.X * (1.0F / _zoom), Me.AutoScrollPosition.Y * (1.0F / _zoom)) 'inverts it mx.Invert() 'uses it to transform the current mouse position Dim pa() As Point = {New Point(e.X, e.Y)} mx.TransformPoints(pa) Return pa(0) End Function 'BacktrackMouse
Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
'Creates the drawing matrix with the right zoom; Dim mx As New Matrix(_zoom, 0, 0, _zoom, 0, 0) 'pans it according to the scroll bars mx.Translate(Me.AutoScrollPosition.X * (1.0F / _zoom), Me.AutoScrollPosition.Y * (1.0F / _zoom)) 'use it for drawing e.Graphics.Transform = mx
Dim sb As New SolidBrush(Color.Red) 'draw all the points Dim pt As Point For Each pt In _blobs e.Graphics.FillEllipse(sb, pt.X - 5, pt.Y - 5, 10, 10) Next pt sb.Dispose() End Sub 'OnPaint End Class 'Form1 End Namespace 'backtrack Return to Windows Forms Tips and Tricks Copyright Ramuseco Limited 2004. All rights reserved.
|