Skip to content

Latest commit

 

History

History
229 lines (197 loc) · 7.76 KB

mouse.md

File metadata and controls

229 lines (197 loc) · 7.76 KB

Interacting with the mouse

Detect if the mouse is held down without requiring a sender

poll System.Windows.Forms.Control.MouseButtons

// example ignore a scrollbar until it is let go
private void vScrollBar1_Scroll(object sender, ScrollEventArgs e)
{
    if (System.Windows.Forms.Control.MouseButtons == MouseButtons.Left) return;
    ScollBarMoved();
}

Prevent focus of buttons after they are clicked

When buttons are clicked, they gain a little blue border. They remain selected until something else is clicked. This looks very bad for some applications where it doesn't make sense (i.e., zoom in/out buttons). This has to do with FocusVisualStyle and it's not easy to remove this behavior.

My workaround is to bind the MouseUp event with a function which focuses on an invisible button. It's inelegant, but fast and functional.

// when MouseUp event is called, this moves focus to an invisible button
private void Focus_Reset(object sender, MouseEventArgs e)
{
    button1.Focus();
}

Detecting (X, Y) Location in a Panel

Bind a function to the MouseMove event

private void panel1_MouseMove(object sender, MouseEventArgs e)
{
    lbl_title.Text = string.Format("({0}, {1})", e.X, e.Y);
}
Point pt = new Point(e.Location); // also works

Change the cursor

panel1.Cursor = Cursors.SizeWE;

Mouse tracking through controls.

What if you want to track MouseMove for Panel1 but the things inside the panel cover up the panel backgruond? The solution is to create a hanlder function and connect the MouseMove event of the panel and every control inside the panel to the same handler. Some controls contain other controls (i.e., nested panels) so a recursive solution is ideal.

public Form1()
{
    InitializeComponent();
    mouse_track_this_control(panel_dataView);
}

private void MouseTracker_move(object sender, MouseEventArgs e)
{
    richTextBox1.Text = string.Format("MOVE: ({0}, {1})", e.X, e.Y);
}
private void MouseTracker_down(object sender, MouseEventArgs e)
{
    richTextBox1.Text = string.Format("DOWN: ({0}, {1})", e.X, e.Y);
}
private void MouseTracker_up(object sender, MouseEventArgs e)
{
    richTextBox1.Text = string.Format("UP: ({0}, {1})", e.X, e.Y);
}

private void mouse_track_this_control(Control control)
{
    control.MouseMove += MouseTracker_move;
    control.MouseDown += MouseTracker_down;
    control.MouseUp += MouseTracker_up;
    System.Console.WriteLine($"Connecting mouse tracker to {control.Name}");
    foreach (Control control_child in control.Controls)
    {
        mouse_track_this_control(control_child);
    }

}

Cursor Location on the Screen

mouse_position_screen = new Point(Cursor.Position.X, Cursor.Position.Y);

Cusor Location relative to a Panel

// find absolute cusor position on the screen
mouse_position = new Point(Cursor.Position.X, Cursor.Position.Y);

// subtract-out the location of the panel we consider (0, 0)
mouse_position.X -= this.PointToScreen(panel_dataView.Location).X;
mouse_position.Y -= this.PointToScreen(panel_dataView.Location).Y;

Click-and-Drag Panning and Zooming

The zooming code keeps messing me up! It works well this way though.

Left-Click-Drag Panning

shift the axis by the pixel distance dragged times unitsPerPixel

double dX = (mouse_left_down_position.X - mouse_position.X) * mouse_left_down_axis.xAxis.unitsPerPx;
double dY = (mouse_position.Y - mouse_left_down_position.Y) * mouse_left_down_axis.yAxis.unitsPerPx;
axis1 = new FigureAxis(mouse_left_down_axis.xAxis.min + dX, mouse_left_down_axis.xAxis.max + dX,
		       mouse_left_down_axis.yAxis.min + dY, mouse_left_down_axis.yAxis.max + dY,
		       mouse_left_down_axis.xAxis.pxSize, mouse_left_down_axis.yAxis.pxSize);

Right-Click-Drag Zooming (v1)

expand the edges by the same distance of the drag when zooming out, or sqrt(distance) when zooming in

double dX = (mouse_right_down_position.X - mouse_position.X) * mouse_right_down_axis.xAxis.unitsPerPx;
double dY = (mouse_position.Y - mouse_right_down_position.Y) * mouse_right_down_axis.yAxis.unitsPerPx;
if (dX < 0) dX = -Math.Sqrt(Math.Abs(dX));
if (dY < 0) dY = -Math.Sqrt(Math.Abs(dY));
axis1 = new FigureAxis(mouse_right_down_axis.xAxis.min - dX, mouse_right_down_axis.xAxis.max + dX,
		       mouse_right_down_axis.yAxis.min - dY, mouse_right_down_axis.yAxis.max + dY,
		       mouse_right_down_axis.xAxis.pxSize, mouse_right_down_axis.yAxis.pxSize);
With better class design, this simplified to
public void Zoom(double zoomFrac)
{
    double newSpan = span / zoomFrac;
    x1 = center - newSpan / 2;
    x2 = center + newSpan / 2;
    RecalculateScale();
}

public void ZoomPx(int px)
{
    double dX = px * unitsPerPx;
    double dXFrac = dX / (Math.Abs(dX) + span);
    Zoom(Math.Pow(10, dXFrac));
}

Right-Click-Drag Zooming (v1)

This one feels pretty natural. It gets the dX as a fraction of the original window size (dXunits / axisWidthUnits) then applies the zoom function with 10^dX. That's a magical number that feels really nice, and it automatically slows down at extremes.

double dX = (mouse_right_down_position.X - mouse_position.X) * mouse_right_down_axis.xAxis.unitsPerPx;
double dY = (mouse_position.Y - mouse_right_down_position.Y) * mouse_right_down_axis.yAxis.unitsPerPx;

double dXFrac = dX / (Math.Abs(dX) + (mouse_right_down_axis.xAxis.max - mouse_right_down_axis.xAxis.min));
double dYFrac = dY / (Math.Abs(dY) + (mouse_right_down_axis.yAxis.max - mouse_right_down_axis.yAxis.min));

axis1 = new FigureAxis(mouse_right_down_axis.xAxis.min, mouse_right_down_axis.xAxis.max,
		       mouse_right_down_axis.yAxis.min, mouse_right_down_axis.yAxis.max,
		       mouse_right_down_axis.xAxis.pxSize, mouse_right_down_axis.yAxis.pxSize);

axis1.Zoom(Math.Pow(10, dXFrac), Math.Pow(10, dYFrac)); // THE MAGIC HAPPENS HERE

Clear();
RedrawFrame();

Pan-and-Zoom Mouse Tracker

This class is helpful to track the mouse for left-click-pan / right-click-zoom applications. I instantiate it at a form level, then call its down/up/move methods from within down/up/move events.

public class MouseTracker
{
    Point posDown;
    Point posUp;
    Point posMove;
    Point dragDelta;
    Point dropDelta;
    bool panning = false;
    bool zooming = false;

    public void Down()
    {
	posDown = new Point(Cursor.Position.X, Cursor.Position.Y);
	if (Control.MouseButtons == MouseButtons.Left)
	{
	    panning = true;
	    Console.WriteLine($"pan started at: ({posDown.X}, {posDown.Y})");
	}
	else if (Control.MouseButtons == MouseButtons.Right)
	{
	    zooming = true;
	    Console.WriteLine($"zoom started at: ({posDown.X}, {posDown.Y})");
	}
    }

    public void Up()
    {
	posUp = new Point(Cursor.Position.X, Cursor.Position.Y);
	dropDelta = dragDelta;
	if (panning)
	{
	    Console.WriteLine($"pan ended at: ({dropDelta.X}, {dropDelta.Y})");
	    panning = false;
	}
	if (zooming)
	{
	    Console.WriteLine($"zoom ended at: ({dropDelta.X}, {dropDelta.Y})");
	    zooming = false;
	}
    }

    public void Move()
    {
	posMove = new Point(Cursor.Position.X, Cursor.Position.Y);
	if (panning || zooming)
	    Drag();
    }

    private void Drag()
    {
	dragDelta = new Point(posMove.X - posDown.X, posMove.Y - posDown.Y);
	if (panning)
	{
	    Console.WriteLine($"  pan drag: ({dragDelta.X}, {dragDelta.Y})");
	}
	else if (zooming)
	{
	    Console.WriteLine($"zoom drag: ({dragDelta.X}, {dragDelta.Y})");
	}
    }
}

private void pb_MouseDown(object sender, MouseEventArgs e) { mouseTracker.Down(); }
private void pb_MouseUp(object sender, MouseEventArgs e) { mouseTracker.Up(); }
private void pb_MouseMove(object sender, MouseEventArgs e) { mouseTracker.Move(); }