Detect whether multiple ListView items were selected by dragging - c#

I have a ListView with the View set to LargeIcon.
I specifically need to detect when multiple items were selected by dragging a selection box around them with the mouse.
(For example I don't want to know when items were selected by CTRL + Click)
I thought I could simply do it by keeping track of whether the mouse was down while it was moving which would indicate dragging, then on mouse up if it was a drag then I can set another variable to indicate this.
In my example below mouseDown is set to true, but when I keep the mouse down and move it isDrag is never set to true and I can't see what I'm doing wrong.
(Edit: isDrag becomes true if I remove the if clause which is weird because as I said mouseDown is definitely true).
I realise the code is a little longer than it needs to be but it's for clarity.
bool mouseDown;
bool isDrag;
bool wasDrag;
private void listView1_MouseDown(object sender, MouseEventArgs args)
{
wasDrag = false;
mouseDown = true;
}
private void listView1_MouseMove(object sender, MouseEventArgs args)
{
if (mouseDown)
isDrag = true; // <-- Never becomes true, even though mouseDown is true
}
private void listView1_MouseUp(object sender, MouseEventArgs args)
{
if (isDrag)
wasDrag = true;
mouseDown = false;
isDrag = false;
}
I know it'll be something stupid. Please put me out of my misery.
Alternatively if someone knows of a better was to detect a dragging selection (what's the proper term?) then I'm all ears.

Can you try this:
private void listView1_MouseMove(object sender, MouseEventArgs args)
{
isDrag = mouseDown;
}
I think for some reason, your event listView1_MouseUp still fires, which makes your isDrag variable set to other than the intended value. Try to put breakpoints on both MouseMove and MouseUp events to see the sequence with which they are firing.

After further investigation I've discovered that for a ListView control the MouseMove event will not fire while MouseDown is still occurring and fires immediately after releasing the mouse.
I can only assume that the logic built into this control that allows you to select multiple files by dragging a selection is messing with these events and essentially making them synchronous.
I've put together a basic workaround for this. It's not ideal but it does the job so I thought I'd share.
Basically when the mouse goes down I record the position. When the mouse goes up I check to see if it has moved more than a certain distance in any direction. If it has not I consider it a click, if it has I consider it a drag.
// Records the mouse position on mousedown
int beforeMoveX;
int beforeMoveY;
// How far in pixels the mouse must move in any direction
// before we consider this a drag rather than a click
int moveBounds = 20;
private void listView1_MouseDown(object sender, MouseEventArgs e)
{
// Save the mouse position
beforeMoveX = e.X;
beforeMoveY= e.Y;
}
private void listView1_MouseUp(object sender, MouseEventArgs e)
{
// Did we move more than the bounds in any direction?
if (e.X < (beforeMoveX - moveBounds) ||
e.X > (beforeMoveX + moveBounds) ||
e.Y < (beforeMoveY - moveBounds) ||
e.Y > (beforeMoveY + moveBounds))
{
// DRAGGED!
}
else
{
// NOT DRAGGED!
}
}

Related

WinForm not dragging smoothly when moving by client area

Whenever I move a Windows Form by some component (i.e. a Label) in the client area, I end up with a strange mouse offset in which the form does not stay visually underneath the cursor. It will still move according to my mouse location on the screen, but it dramatically shifts southeast of the cursor's position.
I've had to specify a negative offset of my own to counteract this offset; my code is as follows:
private void component_MouseDown(object sender, MouseEventArgs e)
{
if (sender is Label)
{
if (e.Button == MouseButtons.Left)
{
mouseLoc = new Point(-(e.X + OFFSET_X), -(e.Y + OFFSET_Y));
isMouseDown = true;
}
}
}
private void component_MouseMove(object sender, MouseEventArgs e)
{
if (isTitleLabelMouseDown)
{
Point p = Control.MousePosition;
p.Offset(mouseLoc);
Location = p;
}
}
private void component_MouseUp(object sender, MouseEventArgs e)
{
isMouseDown = false;
}
This offset does fix the problem, but what throws me for a loop is why the form's location offsets when I move it by its client area in the first place?
Thanks!
You seem to be translating client coordinates to screen coordinates. There is a better way...
https://msdn.microsoft.com/en-us/library/system.windows.forms.control.pointtoscreen%28v=vs.110%29.aspx
Edit: And of course there's a better way to do this whole thing. Basically, you want to intercept the click higher up the chain and tell Windows that the click is actually in the window title, which will cause Windows to do the dragging for you...
Winforms - WM_NCHITEST message for click on control

Mouse up event doesn't get triggered

I have the following problem: I have a panel which has a specific color, say red.
When the user presses his mouse, the color of this panel gets stored in a variable. Then the user moves, his mouse still pressed, over to another panel. When he releases the mouse there, this panel should get the background color of the first that had been stored in the variable. My code looks something like this:
public Color currentColor;
private void ColorPickMouseDown(object sender, MouseEventArgs e)
{
Panel pnlSender = (Panel)sender;
currentColor = pnlSender.BackColor;
}
private void AttempsColorChanger(object sender, MouseEventArgs e)
{
Panel pnl = (Panel)sender;
pnl.BackColor = currentColor;
}
I need to identify the sender first because there are many possible panels that can trigger this event. The first MouseDown method works totally fine, the color is stored nicely in the variable. The secon one however doesn't even get triggered when the user does what I described above. When the ser clicks on the second panel, it works (there is an MouseUp part in a click aswell I guess).
What's wrong here? Why is the event not triggered when the user holds the mouse key down before?
(This answer assumes you are using Windows Forms.)
It could be that you need to capture the mouse by setting this.Capture = true in the MouseDown of the source control. (See Control.Capture)
If you did that, the source window would get the MouseUp event, and it would be the source window that had to determine the destination window under the mouse coords. You can do that using Control.GetChildAtPoint() (see this answer on Stack Overflow).
Use Windows Forms Drag and Drop Support Instead! <- Click for more info
I'm going to suggest you bite the bullet and use the .Net Drag and Drop methods to do this. It requires some reading up, but it will be much better to use it.
You start a drag in response to a MouseDown event by calling Control.DoDragDrop().
Then you need to handle the Control.DragDrop event in the drop target control.
There's a few more things you might need to do to set it up; see the Control.DoDragDrop() documentation for an example.
(For WPF drag and drop support, see here.)
when your mouse enter the target control , mouse down triggerd ang get target BackColor! you need add an boolean flag to your code :
public Color currentColor;
bool flag=false;
private void ColorPickMouseDown(object sender, MouseEventArgs e)
{
if(flag==false)
{
flag=true
Panel pnlSender = (Panel)sender;
currentColor = pnlSender.BackColor;
}
}
//assume mouse up for panles
private void AttempsColorChanger(object sender, MouseEventArgs e)
{
if(flag==true)
{
Panel pnl = (Panel)sender;
pnl.BackColor = currentColor;
flag=flase;
}
}
and also you need change your flag in mouseMove( if )
As I mentioned in my comment Mouse Events are captured by the originating control, You would probably be better off using the DragDrop functionality built into Windows Forms. Something like this should work for you. I assigned common event handlers, so they can be assigned to all of your panels and just work.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void panel_MouseDown(object sender, MouseEventArgs e)
{
((Control)sender).DoDragDrop(((Control)sender).BackColor,DragDropEffects.All);
}
private void panel_DragDrop(object sender, DragEventArgs e)
{
((Control)sender).BackColor = (Color)e.Data.GetData(BackColor.GetType());
}
private void panel_DragEnter(object sender, DragEventArgs e)
{
e.Effect = DragDropEffects.Copy;
}
}
I know it's an old question but I had the same issue and none of the above answers worked for me. In my case I had to handle the MouseMove event in the target control and check for the mouse to be released. I did set 'BringToFront' on my target panel just in case that helped at all.
public Color currentColor;
private void ColorPickMouseDown(object sender, MouseEventArgs e)
{
Panel pnlSender = (Panel)sender;
currentColor = pnlSender.BackColor;
}
private void panelTarget_MouseMove(object sender, MouseEventArgs e)
{
//the mouse button is released
if (SortMouseLocation == Point.Empty)
{
Panel pnl = (Panel)sender;
pnl.BackColor = currentColor;
}
}

WPF equivalent of Control.CursorChanged event

I am trying to change my mouse cursor at certain point when I'm dragging my mouse around in a wpf listview. However, when I set my mouse, it quickly gets overridden by something else, and get changed back to the drag cursor.
I am not sure where the cursor change comes from, it is certainly not from my code, so it has to be system. If it is system, then I have to intercept the event for cursor change, and handle the event in order for the cursor to show what I want right?
So is there a WPF equivalent of this Control.CursorChanged event? Or perhaps there's some other way to approach this problem?
Edit:
here's part of my code
private void SetDragCursor()
{
if (_badDragLoc)
{
Mouse.OverrideCursor = Cursors.No;
}
else
{
Mouse.OverrideCursor = Cursors.Arrow;
}
}
private void listView_DragOver(object sender, DragEventArgs e)
{
if (at a bad drag location)
{
_badDragLoc = true;
SetDragCursor();
}
}
I also have a drag leave event handler, in which I also have the SetDragCursor() method as well. When I step by step go through each line of code in debugger, the mouse turned into the drag cursor from the no cursor right after it enters the drag leave handler. Which is why I think it has to be the system.
If it indeed is the system, then if I can capture the event firing, I can then handle those event myself and not let it bubble through.
Thank you!
Just does not work like that, the way to set the cursor during a DragOver event is the following:
void listView__DragOver(object sender, DragEventArgs e)
{
if (!e.Data.GetDataPresent("Images"))
{
e.Effects = DragDropEffects.None;
e.Handled = true;
}
}
depending on the value of DragDropEffects enum you assign to e.Effects the mouse will change cursor.
do not call Mouse.OverrideCursor because is not the right way.

Trying to return value upon Mouseup C#

I'm trying to accomplish what I thought would be somewhat straight forward, but apparently isn't.
What I'd like to accomplish is as follows. The user presses a button in a toolbar, this allows the state of the UI to change. Now that the state has changed when the user clicks mouse-down they can create a box on the screen by dragging the mouse. Upon the mouse-up I want to return the x,y coord.
So basically something like this
protected void MyUI_MouseDown(object inSender , MouseEventArgs inArgs)
{
switch(myState)
{
case CreateBox:
Rectangle rect = DrawBox();
}
}
public Rectangle DrawBox()
{
myDrawFlag = true;
}
private MyUI_MouseMove(object inSender , MouseEventArgs inArgs)
{
if(myDrawFlag)
{
DrawBox(inArgs.X , inArgs.Y);
}
}
Basically, I'm not sure how to get from point A to point C. The mouse down changes the state of the UI and lets me draw on the screen via the Mouse Move - but I want to return the value upon the Mouse UP.
I know I'm doing something wrong - could someone tell me what?
Edit 1: Yes, there is nothing in DrawBox(). Basically my question is how do I have that method not return UNTIL, I get the mouse-up event?
Edit 2: I am tracking the mouse movement events. As the mouse moves I am updating the start X,Y and the new endpoint X,Y. I still think I'm not asking this question correctly.
I don't want return DrawBox() until the mouse-up event. Mouse-Down should just notify the UI it can draw a box on the screen. Mouse-Move (which I'm using) updates the coordinates. And then Mouse-Up should tell the UI it can't draw anymore and ONLY THEN should DrawBox() return with the points.
The idea to wait inside the MouseDown method until die MouseUp event is fired is not the way to go. You have to think event driven. Better create/save your final object in the MouseUp method.
protected void MyUI_MouseDown(object inSender, MouseEventArgs inArgs)
{
switch(myState)
{
case CreateBox:
Rectangle rect = new Rectangle(inArgs.X, inArgs.Y, 0, 0);
break;
}
}
protected void MyUI_MouseUp(object inSender, MouseEventArgs inArgs)
{
rect.Width = inArgs.X - rect.X;
rect.Height = inArgs.Y - rect.Y;
// now save/draw your object
}
You can store your points in a struct and then fill up the struct in the MouseUp event, and reuse it where you wish.
struct coords
{
int x;
int y;
}
coords my_coords;
protected void MyGui_MouseUp(object sender, MouseEventArgs args)
{
my_coords.x = args.x;
my_coords.y = args.y;
}
//here you can use my_coords to what you need...
You need to handle the MouseUp event. The MouseMove event you are handling will fire every pixel change in distance that the mouse moves so you are in effect calling the DrawBox(int, int) method hundreds of times. If you handle the MouseUp event instead you will get the coordinates when the mouse button is released.
As far as i see it, you have 3 states:
Normal, which doesn require any explanation.
DesignMode, When the button in he toolbar is clicked.
DrawingBox, When you are actually drawing the box
Now you want the following transition:
Normal -> DesigMode -> OnDrawingBox -> AfterDrawingBox.
This can implemented using a state machine or an enum. With the enum, your code will be something like this:
enum DrawBoxState { Normal, DesignMode, DrawingBox }
DrawBoxState _currentState = DrawBoxState.Normal;
Point _startPoint;
Point _endPoint;
void OnToolbarButtonClicked(object sender, EventArgs e)
{
switch (_currentState)
{
case DrawBoxState.Normal:
_currentState = DrawBoxState.DesignMode;
break;
default:
_currentState = DrawBoxState.Normal;
break;
}
}
void OnMouseDown(object sender, MouseEventArgs e)
{
switch (_currentState)
{
case DrawBoxState.DesignMode:
{
_currentState = DrawBoxState.OnDrawingBox;
_startPoint = e.Location; // not sure if this is right
}
break;
}
}
void OnMouseUp(object sender, MouseEventArgs e)
{
switch (_currentState)
{
case DrawBoxState.DrawingBox:
{
_currentState = DrawBoxState.Normal;
_endPoint = e.Location;
}
break;
}
}
Now you can simply, raise an event every time the state changes, and adept changes on that event. e.g. if state changes to normal, check if startPoint and endPoint have been set and therefore do something with the value's.

How to make a control "transparent" for the mouse or Route MouseMove event to parent?

I want to create a card playing game. I the use mousemove event to drag cards through the window. The problem is if I move the mouse over another card, it is stuck because the card underneath the mouse cursor gets the mouse events, so that the MouseMove event of the window isn't fired.
This is what I do:
private void RommeeGUI_MouseMove(object sender, MouseEventArgs e)
{
if (handkarte != null)
{
handkarte.Location = this.PointToClient(Cursor.Position);
}
}
I tried the following, but there was no difference:
SetStyle(ControlStyles.UserMouse,true);
SetStyle(ControlStyles.EnableNotifyMessage, true);
Iam looking for a way to implement an application-global event-handler or a way to implement so called event-bubbling. At least I want to make the mouse ignore certain controls.
In order to do this you will need to keep track of a few things in your code:
On which card the mouse is pointing
when the mouse button is pressed;
this is the card that you want to
move (use the MouseDown event)
Move the the card when the mouse is moved
Stop moving the card when the mouse button is released (use the
MouseUp event)
In order to just move around controls, there is no need to actually capture the mouse.
A quick example (using Panel controls as "cards"):
Panel _currentlyMovingCard = null;
Point _moveOrigin = Point.Empty;
private void Card_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
_currentlyMovingCard = (Panel)sender;
_moveOrigin = e.Location;
}
}
private void Card_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left && _currentlyMovingCard != null)
{
// move the _currentlyMovingCard control
_currentlyMovingCard.Location = new Point(
_currentlyMovingCard.Left - _moveOrigin.X + e.X,
_currentlyMovingCard.Top - _moveOrigin.Y + e.Y);
}
}
private void Card_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left && _currentlyMovingCard != null)
{
_currentlyMovingCard = null;
}
}
What you could do, is send the MouseDown event to the event.function you want to call.
Say you got a "Label" ontop of a "Card", but you wan't to "go-through" it:
private void Label_MouseDown( object sender, MouseEventArgs)
{
// Send this event down the line!
Card_MouseDown(sender, e); // Call the card's MouseDown event function
}
Now the appropriate event-function is called, even though the bothersome label was clicked.
Normally you do this by, before capturing the mouse, seeing if anybody else has it...

Categories

Resources