Xamarin MotionEventActions.Move never gets hit in custom views onTouch event? - c#

I have an app where I am drawing lines through points to form polygons, I want to be able to have the four corners of the polygon be moveable, I have this code inside of a custom view I've created for drawing polygons:
public override bool OnTouchEvent(MotionEvent e)
{
MotionEventActions action = e.Action;
float x = 0.0f;
float y = 0.0f;
float radius = 20.0f;
// Get the current pointer index
int tempPointerIndex = e.ActionIndex;
x = e.GetX(tempPointerIndex);
y = e.GetY(tempPointerIndex);
// For each point, check to see if the touch fell within the bounds of the circle
for (int i = 0; i < _points.Length; i++)
{
if((x >= _points[i].X - radius && x <= _points[i].X + 20) ||
(y >= _points[i].Y - radius && y >= _points[i].Y + 20))
{
switch (action)
{
case MotionEventActions.Down:
{
// Get the current x/y when not moving so we can calculate the delta x/y in
// move events
lastX = x;
lastY = y;
// Set the global active pointer index
activePointerId = e.GetPointerId(0);
break;
}
case MotionEventActions.Move:
{
int pointerIndex = e.FindPointerIndex(activePointerId);
x = e.GetX(pointerIndex);
y = e.GetY(pointerIndex);
// Find the delta x/y
float deltaX = x - lastX;
float deltaY = y - lastY;
// Move the selected skew handle by the delta x/y
_points[i].X += deltaX;
_points[i].Y += deltaY;
// Force a redraw
this.Invalidate();
break;
}
case MotionEventActions.Up:
{
// Invalidate the active pointer index
activePointerId = global::Android.Views.MotionEvent.InvalidPointerId;
break;
}
case MotionEventActions.Cancel:
{
// Invalidate the active pointer index
activePointerId = global::Android.Views.MotionEvent.InvalidPointerId;
break;
}
case MotionEventActions.PointerUp:
{
int pointerIndex = e.ActionIndex;
int pointerId = e.GetPointerId(pointerIndex);
if(pointerId == activePointerId)
{
// Active pointer going up, choose new active pointer and adjust accordingly
int newPointerIndex = pointerIndex == 0 ? 1 : 0;
lastX = e.GetX(newPointerIndex);
lastY = e.GetY(newPointerIndex);
activePointerId = e.GetPointerId(newPointerIndex);
}
break;
}
}
}
}
return base.OnTouchEvent(e);
}
The case for MotionEventActions.Move never seems to get hit, in fact, the only event that seems to get hit is the down event when I initially hit a point. Also, the condition that checks the bounds of the point works for sure, I've tested it numerous times, so that isn't the issue either, I'm not sure if maybe I'm checking for the event incorrectly, but as far as I can tell this is how both the Xamarin and Android documentation say to handle this event, so I'm not really sure what the problem is, or how to find it. My question is, what exactly am I doing wrong that is causing the move event to not be picked up?

Once you determine if one of your points is in range of the user's touch, start returning true from OnTouchEvent until you get an Up, that way all future touch events become Move events on your custom View and are not redirected to the View's parent.
Down/Move/Up touch event logic:
public override bool OnTouchEvent(MotionEvent e)
{
x = e.GetX(e.ActionIndex);
y = e.GetY(e.ActionIndex);
Log.Debug(TAG, $"{e.Action} : {x} : {y}" );
if (e.Action == MotionEventActions.Down)
{
if (true) // some check if the x/y touch is on or near a moveable point, isDraggingPoint becomes true
isDraggingPoint = true;
else
isDraggingPoint = false;
}
if (e.Action == MotionEventActions.Move)
{
// move your Point, x/y touch for draggingPoint, update display, etc...
}
if (e.Action == MotionEventActions.Up)
{
// save the final x/y touch for draggingPoint, update display, etc...
isDraggingPoint = false;
}
return isDraggingPoint;
}
Output:
[CustomView] Down : 358.5332 : 234.6167
[CustomView] Move : 358.5332 : 234.6167
[CustomView] Move : 352.5305 : 238.6241
[CustomView] Move : 334.8269 : 237.3517
[CustomView] Move : 360.5305 : 246.6073
[CustomView] Move : 360.5305 : 246.6073
[CustomView] Up : 360.5305 : 246.6073

Related

How to detect if user is shaking form c# (detect shake),

I've made a paint clone and want to clear the bord when you shake the form, (so by holding mouse1 down on the top bar and moving it right and left fast).
I tried to compare two points on the x-axis, the points are set based on the mouse position and tested against a treshold. And I added a combo so you need to overcome the treshold multiple times to avoid accidental activation, to help with this there is also a timer that resets the combo.
The problem is that it sees fast movement to one side as a shake I still want the user to be able to move the form without clearing the canvas.
int firstpos = 0;
int secondpos = 0;
bool isfirst = true;
int combo = 0;
int threshold = 50;
Point lastPoint;
private void TopBar_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)//to move the form
{
this.Left += e.X - lastPoint.X;
this.Top += e.Y - lastPoint.Y;
}
if (isfirst)
{
firstpos = e.X;
isfirst = false;
}
else
{
secondpos = e.X;
int diff = secondpos - firstpos;
if (diff < 0) diff *= -1;//make positive
if (diff >= threshold)
{
combo++;
Thread t = new Thread(startdecay);
t.Start();
}
if (combo == 4)
{
canvas.Invalidate();//clear the canvas
combo = 0;
}
isfirst = true;
}
}
void startdecay()
{
Thread.Sleep(1000);
combo = 0;
}
private void TopBar_MouseDown(object sender, MouseEventArgs e)
{
lastPoint = new Point(e.X, e.Y);
}
the form
isfirst == true: Save firstpos.
Set isfirst = false.
isfirst == false: Get secondpos.
Get diff.
if Math.Sign(diff) != saveddiff {
increment shakecounter.
Set saveddiff = Math.Sign(diff).
}
Set firstpos = secondpos.
Accept when shakecounter over some value desired.
Don't forget to reset everything when Mouse Button goes up.

How could I change my cursor shape if there are nearby coordinates?

I have a bunch of coordinates and I draw them as a coordinate system on a bitmap image. While hovering my mouse over my image, I want to highlight the coordinates that are within certain Distance from my MousePosition using the below routine:
public void HighLightNearbyDots(Point _mousePosition)
{
// ..
foreach (var point in myDisplayedCoords)
{
Distance = (int)(MousePosition - point); // this gets the distance between two points and converts it to an int
if (Distance < 10)
point.Color = Colors.Red;
else
point.Color = InitialCoordColor;
}
DrawImage();
}
Now, if I find a Nearby coordinates, I want to Change my CursorShape to be a HAND. I can not do that inside if (Distance < NearbyCursorDistance); Why? Because I will either need to change back to Arrow (which takes a micro second) on the else and user won't see it, or it will stay as HAND for the rest of execution. So I implement this:
private bool IsThereANearbyDot(CoordPoint _mousePosition)
{
foreach(var point in MyDisplayedCoords)
{
if (10 > (int) (_mousePosition - point))
return true;
}
return false;
}
And use it this way:
public void HighLightNearbyDots(Point _mousePosition)
{
//..
if (IsThereANearbyDot(MousePosition))
CursorShape = Cursors.Hand;
else
CursorShape = Cursors.Arrow;
foreach (var point in myDisplayedCoords)
{
Distance = (int)(MousePosition - point);
if (Distance < 10)
point.Color = Colors.Red;
else
point.Color = InitialCoordColor;
}
DrawImage();
}
It works but if MyDisplayedCoords is huge, which I have to iterate twice now, it takes a lot of time and users will notice lagging on the screen. How could I solve this problem?
All you need to do is set the cursor to the default (arrow) at the start of your HighLightNearbyDots() method and then set to hand in the distance < 10 clause (and not the else). E.g.:
public void HighLightNearbyDots(Point _mousePosition)
{
// ..
CursorShape = Cursors.Arrow;
foreach (var point in myDisplayedCoords)
{
Distance = (int)(MousePosition - point); // this gets the distance between two points and converts it to an int
if (Distance < 10)
{
CursorShape = Cursors.Hand;
point.Color = Colors.Red;
}
else
point.Color = InitialCoordColor;
}
DrawImage();
}
You shouldn't have any problems with the cursor flickering, but if you do then you could set a flag and just set the cursor once in the method, like this:
public void HighLightNearbyDots(Point _mousePosition)
{
// ..
bool handCursor = false;
foreach (var point in myDisplayedCoords)
{
Distance = (int)(MousePosition - point); // this gets the distance between two points and converts it to an int
if (Distance < 10)
{
handCursor = true;
point.Color = Colors.Red;
}
else
point.Color = InitialCoordColor;
}
CursorShape = handCursor ? Cursors.Hand : Cursors.Arrow;
DrawImage();
}
This will ensure that the cursor is resolved each time you execute the HighLightNearbyDots() method (which presumably you are doing periodically, e.g. on mouse-move events), and that it will be the default cursor unless you've highlighted any dots, in which case it will be the hand.
If that does not address your question, please provide a good Minimal, Complete, and Verifiable example that shows clearly what you're doing, along with a precise explanation of what the code does and what you want it to do instead, as well as what specifically you are having trouble figuring out.

Making breakout game

My friends I made some parts of this game but I have two problems in this game.
1)Problem about distinguishing ball collision with obstacles
1-1)A first problem is related to complex guidelines that I used them for simulation of ball collision with board or ball collision with blocks. These guidelines are not accurate especially when the ball meets with block corner or bottom.
If it is not better for collision distinguish I use a better code. I wrote this code. Are you think that are there better way?
2)How I can do a work that if the ball collision with any obstacle, the obstacle report the collision.
My aim is about using of events.
Are you thinking that I make obstacles as runtime?
If I make as a runtime, how I can make collision event for them?
With best regards
private Point MouseDownLocation;
int step = 2;
int stepleft = 2;
bool flagBottom;
bool flagTop;
private void Form1_Load(object sender, EventArgs e)
{
timer1.Interval = 10;
timer1.Enabled = true;
}
private void timer1_Tick(object sender, EventArgs e)
{
ball.Top += step;
ball.Left += stepleft;
//board simulate collision
bool collisonX = ball.Location.X + ball.Width > board.Location.X && ball.Location.X < board.Location.X + board.Width;
bool collisonY = ball.Top + ball.Height == board.Location.Y || ball.Top + ball.Height - 1 == board.Location.Y;
//board2(button1) simulate collision
bool collisonX2 = ball.Location.X + ball.Width > board2.Location.X && ball.Location.X < board2.Location.X + board2.Width;
bool collisonY2 = ball.Top + ball.Height == board2.Location.Y || ball.Top + ball.Height - 1 == board2.Location.Y;
//Collision the ball with under buttons
bool collsionButtonY = ball.Top - ball.Height == board2.Location.Y || ball.Top - ball.Height == board2.Location.Y - 1;
//collision leftwall
bool leftWall = ball.Left == 0 || ball.Left == -1 || ball.Left == 1;
//collision rightwall
bool topWall = ball.Top == 0 || ball.Top == -1 || ball.Top == 1;
bool bottomWall = collisonX && collisonY;
bool toppWall = collisonX2 && collisonY2;
//collision
bool barrier = collisonX2 && collsionButtonY;
//rightwall
bool rightWall = ball.Left + ball.Width == this.ClientSize.Width || ball.Left + ball.Width == this.ClientSize.Width - 1;
// sidewall = collision rightwall or leftwall
bool sideWall = leftWall || rightWall;
//Check the ball hit the ground
bool check = ball.Top + ball.Height < this.ClientSize.Height;
//if topWall true,This means that the ball is hit to the topwall
if (topWall)
{
flagBottom = false;
flagTop = true;
if (stepleft > 0)
{
step = 2;
}
else if (stepleft < 0)
{
step = 2;
}
}
//if bottomWall true,This means that the ball is hit to the board
else if (bottomWall)
{
flagBottom = true;
flagTop = false;
if (stepleft > 0)
{
step = step * -1;
}
else if (stepleft < 0)
{
step = step * -1;
}
}
//if barrier true and flagbottom true,This means that the ball is hit to the board2(button1)
else if (barrier && flagBottom)
{
if (stepleft > 0)
{
step = step * -1;
}
else if (stepleft < 0)
{
step = step * -1;
}
}
//if toppWall true and flagTop true,This means that the ball is hit to The top button is hit
else if (toppWall && flagTop)
{
if (stepleft > 0)
{
step = step * -1;
}
else if (stepleft < 0)
{
step = step * -1;
}
}
else if (sideWall)
{
//if leftwall true,This means that the ball is hit to the left side wall
if (leftWall)
{
if (flagTop)
{
stepleft = 2;
}
else if (flagBottom)
{
stepleft = 2;
}
}
//if rightWall true,This means that the ball is hit to the left side wall
else if (rightWall)
{
if (flagTop)
{
stepleft = -2;
}
else if (flagBottom)
{
stepleft = -2;
}
}
}
//check if ckeck==ture,this mean the ball is hit the ground
else if (!check)
{
timer1.Enabled = false;
}
}
private void board_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
MouseDownLocation = e.Location;
}
}
private void board_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
board.Left = e.X + board.Left - MouseDownLocation.X;
}
}
Define each "Wall" as an Object, this will increase your codes readablility. it will also allow you to define an Event for each wall, Listen to t. that way you know which wall has been hit
N.b. this is pseudo code. It wont compile :P
public class Wall
{
int X,Y,W,H;
//Define an event, add a listener to this, becuase we will fire this when there is a collision
public event EventHandler OnCollision;
public void CollisionCheck(Ball playerBall)
{
//1 Check for a collision with the "Ball" object
if(Ball.Rectangle().Intersects(this.Rectangle))
this.OnCollision(this, EventArgs.Empty); //Fire the event, null check might be requried
}
}
I do not know your game but (suspect 2D brick-like game)
Objects/Obstacles
all objects/obstacles can be simplified to rectangular objects
so make list of them with defined parameters like size and position
if they are movable/dropable then also speed and state...
type of object (affects color,scoring,capabilities...)
create member function like update(dt),draw()
create events you like OnHIt,OnCollision,...
for example (C++ pseudo code):
// game object types ...
enum _game_object_type_enum
{
_game_object_type_none=0,
_game_object_type_wall,
_game_object_type_brick1,
_game_object_type_brick2,
_game_object_type_brick3,
_game_object_type_bomb,
_game_object_type_joker1,
_game_object_type_joker2,
...
};
// game object class
class game_object
{
public;
int type; // type of object
float x,y,vx,vy,hx,hy; // position,speed,half size of rectangle
void update(float dt); // this call periodicaly in some timer updates positions,... and call events, dt is time passed
void draw(); // can add some rendering device context ...
};
// whole game class holds the game world/board/map and player(s) ...
class game_board
{
public;
game_object *go; int gos; // list of all objects in game
void update(float dt) { for (int i=0;i<gos;i++) go[i].update(dt); }
void draw() { for (int i=0;i<gos;i++) go[i].draw(); }
}
events and flow control
first define some events you need
either you use global function/pointers for that or as members
depends on your platform and to what you are used to
for example like this:
void OnCollisionFX(game_object o1,game_object o2)
{
// do something ...
}
void (*OnCollision)(game_object o1,game_object o2)=OnCollisionFX;
for player interactions only (like player controlled object collisions test only)
is better to handle this in game_object::update(dt) but you need to add player context
something like game_object::update(dt,plr)
where plr is pointer to player controlled object or player class
if you have massive interactivity between game objects (like brick/brick collisions)
then is better to handle this in game_board::update(dt)
because otherwise you will need to provide references for everything into update call
and that will quickly lead to heap/stack trashing and very slow performance
also more advanced multiple object collision/interaction approaches are better in main game class
here is an example for first case:
void game_object::update(float dt,game_object &plr)
{
// update position
x+=vx*dt;
y+=vy*dt;
// here you should make your own collision test of coarse
if (fabs(x-plr.x)<=hx+plr.hx)
if (fabs(y-plr.y)<=hy+plr.hy)
{
... do what you need like mirror or stop speed or clamp position ...
if (OnCollision) OnCollision(this,plr);
}
}
in second case you should check each object against each other
void game_board::update(float dt)
{
int i,j;
// update position
for (i=0;i<gos;i++)
{
go[i].x+=go[i].vx*dt;
go[i].y+=go[i].vy*dt;
}
// here you should make your own collision test of coarse
for (i=0;i<gos;i++)
for (j=i+1;j<gos;j++) // the lower indexes are already tested
if (fabs(go[i].x-go[j].x)<=go[i].hx+go[j].hx)
if (fabs(go[i].y-go[j].y)<=go[i].hy+go[j].hy)
{
... do what you need like mirror or stop speed or clamp position ...
if (OnCollision) OnCollision(go[i],go[j]);
}
}
performance
you can improve performance by use of type property
for example if some game objets are static then the do not need to update position
if some are indestructible then there can be also some processing avoided and so on ...
[notes]
dt/fps/speed should be combined
so in each frame your player controlled object or any moving object does not move further
then the half size of smallest object
if this is not true then you need better collision test
use line intersection algorithm
last position and actual position gives you one line
and object edge gives you the other one
if booth objects are moving then that is even more complicated :)

How can I detect the direction of a dragging event?

So I have a PictureBox and the user is supposed to drag the cursor inside it towards the direction shown by the arrow (in the PictureBox), but I'm not sure how I will set the coordinates to make sure the user dragged in the correct direction (up, down, right, or left).
private void picArrow_MouseDown(object sender, MouseEventArgs e)
{
mPointDown = new Point(e.X, e.Y);
//lblTest.Text = "X: " + mPointDown.X.ToString() + " Y: " + mPointDown.Y.ToString();
}
private void picArrow_MouseUp(object sender, MouseEventArgs e)
{
lblTest.Text += " " + " X: " + e.X.ToString() + " Y: " + e.Y.ToString();
//MessageBox.Show("Entered mouseup");
//rnd_Arr refers to the number of the arrow being shown, 0 = towards the right
if (rnd_Arr == 0 && mPointDown.X >= 0 && mPointDown.Y >= 0 && e.X >= 40 && e.Y >= 0)
{
//some code
}
else
{
MessageBox.Show("DONE!");
}
}
And I know this code doesn't work because even if the user drags down (when he's supposed to drag up), it still accepts it and increments the score.
I'm not putting too much restrictions. It doesn't have to be in a perfectly straight line, or start and end in exact locations. As long as the user is dragging inside the PictureBox and dragging to the correct direction, or at least reaches the minimum length to make sure it is considerable that the user is dragging to the correct direction, like for example:
The arrow shown is pointing to the right. The user doesn't have to drag all the way through the arrow, if he drags horizontally past 40px (and the whole length of the arrow is, say, 80px), then that'll add a point to his score. Nevertheless, I'm deliberating this part, if I should just be more demanding and require the user to drag all the way through.
Should I remove the mouse events for the PictureBox and add mouse events for the form instead?
Thank you!
You're going to want to check the MouseDown.X and compare it to the MouseUp.X (or Y if you want to check vertical direction as well). It is important to note that (0, 0) is the upper left of your screen.
Start by subtracting MouseUp.X from MouseDown.X to get a delta X. This is your change in X pixels over the move operation.
If your X comparison yields a negative delta (MouseUp.X is smaller than MouseDown.X), your mouse has moved left.
If your X comparison yields a positive delta (MouseUp.X is larger than MouseDown.X), your mouse has moved right.
The same concept applies to the Y coord, positive change means you moved the mouse down and a negative changemeans you moved the mouse up. See below code:
Point mouseDownPoint;
Point mouseUpPoint;
float deltaX = mouseUpPoint.X - mouseDownPoint.X;
float deltaY = mouseUpPoint.Y - mouseDownPoint.Y;
if (deltaX > 0)
{
// Moved right
}
else if (deltaX < 0)
{
// Moved left
}
if (deltaY > 0)
{
// Moved down
}
else if (deltaY < 0)
{
// Moved up
}
private void picArrow_MouseUp(object sender, MouseEventArgs e)
{
bool movedUp, movedDown, movedLeft, movedRight;
if (e.X == mPointDown.X) { movedRight = movedLeft = false; }
else { movedRight = e.X < mPointDown.X; movedLeft = !movedRight; }
if (e.Y == mPointDown.Y) { movedUp = movedDown = false; }
else { movedUp = e.Y < mPointDown.Y; movedDown = !movedUp; }
// Code can now use the Booleans above as needed
// . . .
}

How to scroll Flow layout panel content as per mouse position

i have taken one Flow layout panel and placed multiple picture box inside in it. now i want when i will place my mouse at the right or left most edge of the Flow layout panel then rest of picture will scroll out. just think about windows 8 start screen where many tiles appear in screen and when we place mouse at right most edge on the screen then rest of the tiles scroll out. i want to simulate same thing in windows form with Flow layout panel.
i want my Flow layout panel will not show scroll bar but images will scroll out when i will place mouse right or left most part on the panel. here is my screen shot
some one told me to do it this way...here is bit code
Set AutoScrollPosition property in MouseMove event of Panel.
private void panel1_MouseMove(object sender, MouseEventArgs e)
{
panel1.AutoScrollPosition = new Point(e.X, e.Y);
}
but this trick was not good. AutoScrollPosition works when scroll bar is visible but in my case i do not want to show scroll bar with Flow layout panel. i want smooth scrolling images from left to right or right to left. anyone can help me to achieve what i am trying to do....if possible guide me with respect of coding. thanks
EDIT
Here i am giving my full code after modification following #Taw suggestion but it is not working fine....rather flickering found when picture move. anyway here is the full code.
namespace ScrollTest
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
flowLayoutPanel1.MouseMove += MouseScroll;
foreach (Control x in this.Controls)
{
if (x is PictureBox)
{
((PictureBox)x).MouseMove += MouseScroll;
}
}
}
int near = 33;
private void MouseScroll(object sender, MouseEventArgs e)
{
Point mouse = flowLayoutPanel1.PointToClient(MousePosition);
Rectangle C = flowLayoutPanel1.ClientRectangle;
int dLeft = mouse.X - C.Left;
int dTop = mouse.Y - C.Top;
int dRight = C.Right - mouse.X;
int dBottom = C.Bottom - mouse.Y;
int dX = dLeft < near ? dLeft : dRight < near ? -dRight : 0;
int dY = dTop < near ? dTop : dBottom < near ? -dBottom : 0;
if (dX != 0 | dY != 0) scrollFLP(dX, dY);
}
void scrollFLP(int deltaX, int deltaY)
{
flowLayoutPanel1.Left += getSpeedFromDistance(deltaX);
flowLayoutPanel1.Top += getSpeedFromDistance(deltaY);
System.Threading.Thread.Sleep(11);
}
int getSpeedFromDistance(int delta)
{
int sig = Math.Sign(delta);
int d = Math.Abs(delta);
if (d > near / 2) return sig;
else if (d > near / 3) return near / 10 * sig;
else if (d > near / 4) return near / 8 * sig;
else if (d > near / 5) return near / 5 * sig;
else return near * sig;
}
}
}
basically i am trying achieve something like suppose i have flow layout panel and which has many picture box inside it with many images as the screen shot but scroll bar should not show rather scroll will happen automatically when i will place my mouse at the top or bottom of the flow layout panel like carousel.
see this picture of your application
when place my mouse at the right end then it scroll and form background shown which i do not want. i want picture box will scroll & scroll upto last one not more than that.
any idea how to do it. thanks
2nd Edit
this code i added as per your suggestion
public Form1()
{
InitializeComponent();
for (int i = 0; i < 666; i++)
{
PictureBox pan = new PictureBox();
//pan.MouseMove += MouseScroll;
//pan.MouseLeave += outSideCheck;
pan.Size = new Size(75, 75);
pan.BackColor = Color.FromArgb(255, (i * 2) & 255, (i * 7) & 255, (i * 4) & 255);
flowLayoutPanel1.Controls.Add(pan);
}
//flowLayoutPanel1.MouseMove += MouseScroll;
//this.flowLayoutPanel1.MouseLeave += outSideCheck;
mouseScroller MSC = new mouseScroller();
MSC.registerControl(flowLayoutPanel1); // FLP = your FlowLayouPanel
MSC.timerSpeed = 5; // optional
MSC.nearness = 100; // optional
flowLayoutPanel1.AutoScroll = false;
}
now the apps doing wired behavior after adding new code. if i am making any mistake then guide me please. thanks
This is a two-part problem:
How to grab the event
How to scroll a FlowLayoutPanel with its scrollbars invisible.
Second first. It is not an easy task from what I found, unless you use a simple and rather common trick: Don't actually scroll it! Instead place it into a Panel and then control its position inside that Panel.
To do this you add a Panel panel1 to your Form, Dock or Anchor it as you need to and set its Autoscroll = false (!) (Which is not the way it is usually done, when you want to make, say a PictureBox scrollable. But we don't want the Panel to show it Scrollbars either.)
Set the FLP to its desired size and place it into the Panel, it obviously also has Autoscroll = false, and we're ready to tackle the other problem of setting up the event..:
First you add the MouseScroll event below to your code and then you hook every control up to it you want to work with the mouse move, namely the FLP:
flowLayoutPanel1.MouseMove += MouseScroll;
..and also each of your PictureBoxes, maybe like this
// your creation loop..
PictureBox pbox = new PictureBox();
pbox.MouseMove += MouseScroll; // <<--- hook into to the mousemove
pan.MouseLeave += outSideCheck; // <<--- hook into to the mouseleave
// .. do your stuff.. here I put some paint on to test..
pbox.BackColor = Color.FromArgb(255, 111, (i * 3) & 255, (i * 4) & 255);
flowLayoutPanel1.Controls.Add(pbox);
or however you create them..
Edit 2 I have changed my original code once more. It now includes an outside check, a check for moving towards the closest edge and a workaround for tha mousemove bug. It uses a Timer set to maybe 30ms. The speed mapping is in a function of its own.
flowLayoutPanel1.MouseMove += MouseScroll;
this.flowLayoutPanel1.MouseLeave += outSideCheck;
flowLayoutPanel1.AutoScroll = false;
int near = 33;
Point lastLocation = Point.Empty;
int dX = 0;
int dY = 0;
private void MouseScroll(object sender, MouseEventArgs e)
{
Point mouse = panel1.PointToClient(MousePosition);
Rectangle C = panel1.ClientRectangle;
// mouseMove has a bug, we need to workaround
if (mouse == lastLocation) return;
if (lastLocation == Point.Empty) { lastLocation = mouse; return; }
// distance from each edge
int dLeft = mouse.X - C.Left;
int dTop = mouse.Y - C.Top;
int dRight = C.Right - mouse.X;
int dBottom = C.Bottom - mouse.Y;
// relevant distances with sign
dX = dLeft < near ? dLeft : dRight < near ? -dRight : 0;
dY = dTop < near ? dTop : dBottom < near ? -dBottom : 0;
// we need the closest edge to check if we are moving in or out
List<int> edges = new List<int>() { dLeft, dTop, dRight, dBottom };
var closest = edges.IndexOf(edges.Min());
// if we are moving
if (dX != 0 | dY != 0)
// if moving out: go else stop going
if (!movingIn(mouse, closest)) timer1.Start(); else timer1.Stop();
// remember position
lastLocation = mouse;
}
bool movingIn(Point current, int Edge)
{
switch (Edge)
{
case 0: return current.X > lastLocation.X;
case 1: return current.Y > lastLocation.Y;
case 2: return current.X < lastLocation.X;
case 3: return current.Y < lastLocation.Y;
}
return false;
}
void scrollFLP(int deltaX, int deltaY)
{
flowLayoutPanel1.Left += getSpeedFromDistance(deltaX);
flowLayoutPanel1.Top += getSpeedFromDistance(deltaY);
Size C = panel1.ClientSize;
if (flowLayoutPanel1.Left > 1) { flowLayoutPanel1.Left = 0; timer1.Stop(); }
if (flowLayoutPanel1.Right < C.Width)
{ flowLayoutPanel1.Left = C.Width - flowLayoutPanel1.Width; timer1.Stop(); }
if (flowLayoutPanel1.Top > 1) { flowLayoutPanel1.Top = 0; timer1.Stop(); }
if (flowLayoutPanel1.Bottom < C.Height)
{ flowLayoutPanel1.Top = C.Height - flowLayoutPanel1.Height; timer1.Stop(); }
}
int getSpeedFromDistance(int delta)
{
int sig = Math.Sign(delta);
int d = Math.Abs(delta);
if (d > near / 2) return sig;
else if (d > near / 3) return 2 * sig;
else if (d > near / 4) return 4 * sig;
else if (d > near / 5) return 6 * sig;
else return 10 * sig;
}
private void timer1_Tick(object sender, EventArgs e)
{
if (insidePanel()) scrollFLP(dX, dY); else timer1.Stop();
}
bool insidePanel()
{
return panel1.ClientRectangle.Contains(panel1.PointToClient(MousePosition));
}
private void outSideCheck(object sender, EventArgs e)
{
if (!insidePanel()) {timer1.Stop(); lastLocation = Point.Empty;}
}
Of course you'll want to play with the various 'magic' numbers :-)
Stop code and direction check are now included.
As usual, key is to know precisely what you want.. I hope this gets you started on ways to achieve it!
It's been a while since this question was asked. I just encountered the problem. My scenario was a little different, but I still think it's a solution to the same problem (at worst a timer control can be used because autoscroll is not turned on).
Here is my scenario: I have one panel control (normal panel). I have an PictureBox in it that I made with zoom. I'm making rectangular selections on top of this image, and when the selections spilled out of the panel, my panel was supposed to slide in the direction I was selecting. (in my scenario, mouse is pressed)(also in my scenario, autoscroll is on). This is how I solved it without writing so much code:
I added two private variable for scroll position (Valid for the whole class scope).
private int xPos;
private int yPos;
private int speed = 5;
and I assigned them the current scroll positions when the form is loaded.
private void Form1_Load(object sender, EventArgs e)
{
//when I change the scrollbar manually or change with zoom I still
//need to add these lines to the related event
xPos = panel1.HorizontalScroll.Value;
yPos = panel1.VerticalScroll.Value;
}
and inside my picturebox's mousemove event
private void picturebox1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left) {
Point mouse = panel1.PointToClient(MousePosition);
if (!panel1.ClientRectangle.Contains( mouse ))
{
Rectangle CRect = panel1.ClientRectangle;
int dLeft = mouse.X - CRect.Left;
int dRight = CRect.Right - mouse.X;
int dTop = mouse.Y - CRect.Top;
int dBottom = CRect.Bottom - mouse.Y;
if(dLeft < 0 && panel1.HorizontalScroll.Value > 0)
{
xPos = -panel1.AutoScrollPosition.X - speed;
}
if (dRight < 0 && panel1.HorizontalScroll.Value < panel1.HorizontalScroll.Maximum)
{
xPos = -panel1.AutoScrollPosition.X + speed;
}
if (dTop < 0 && panel1.VerticalScroll.Value > 0)
{
yPos = -panel1.AutoScrollPosition.Y - speed;
}
if (dBottom < 0 && panel1.VerticalScroll.Value < panel1.VerticalScroll.Maximum)
{
yPos = -panel1.AutoScrollPosition.Y + speed;
}
panel1.AutoScrollPosition = new Point(xPos, yPos);
}
}
}

Categories

Resources