Trigger MouseMove/MouseUp After Leaving ListView - c#

I have an application with a ListView control that contains a number of objects the user can "drag and drop" within a Panel. Originally, I used DoDragDrop but decided not to so that I could implement a semi-transparent, custom control I call ShadowBox. The ShadowBox inherits from Form and is centred on the mouse cursor as you place the item. The item is placed in the Panel as a custom control that inherits from Label we can call Item.
When using the the MouseMove event on the Item I am able to freely drag the ShadowBox anywhere on screen and it disappears once I let go of the mouse regardless of where on the screen I am. This means MouseMove is continuing to fire even after the cursor technically left the bounds of the Item.
My problem has two elements:
1) When I do the same thing on a ListView the MouseMove is not fired unless the mouse is directly over the ListView. This means the ShadowBox blocks the MouseMove from triggering and I can't continue the MouseMove as I enter the panel. Is there a way to fix this (see code below). I think that this:
How do I capture the mouse move event
may be my answer, but I can't get it to work.
2) Just for the sake of my understanding, and assuming I'm not just doing something wrong on my MouseDown or MouseMove events, why is the MouseMove behaviour different for Item than for ListView? (i.e. why does MouseMove seem to continue to fire even when the mouse isn't directly over the Item, but the same is not true for the ListView?)
ShadowBox sb;
Point startMousePosition;
private void Toolbox_MouseDown(object sender, MouseEventArgs e)
{
ListViewItem lvi;
Point p;
lvi = lvToolbox.GetItemAt(e.X, e.Y);
if (lvi != null)
{
if ((e.Button & MouseButtons.Left) == MouseButtons.Left)
{
sb = new ShadowBox();
p = lvToolbox.PointToScreen(e.Location);
sb.Show();
sb.Size = new Size(144, 108); //Note: I simplified this code, the size will depend on the ListViewItem selected.
sb.Opacity = 0.8;
sb.Location = new Point(p.X - sb.Width / 2, p.Y - sb.Height / 2);
sb.Show();
startMouseDown = new Point(sb.Width / 2, sb.Height / 2); //e.Location;
lvToolbox.MouseMove += new MouseEventHandler(Toolbox_MouseMove);
}
}
}
private void Toolbox_MouseMove(object sender, MouseEventArgs e)
{
if ((e.Button & MouseButtons.Left) == MouseButtons.Left)
{
sb.Location = PointToScreen(new Point(e.X - startMouseDown.X, e.Y - startMouseDown.Y));
}
}
private void Toolbox_MouseUp(object sender, MouseEventArgs e)
{
if (sb != null)
{
sb.Dispose();
}
lvToolbox.MouseMove -= new MouseEventHandler(Toolbox_MouseMove);
}

Related

Winforms custom cursor stops setting after a time

I have a form that contains a panel. The idea is that when the mouse is hovered over the panel it displays the OpenHand cursor. You can then drag the panel which causes the cursor to change to the Grab cursor. Upon mouse release the Grab cursor should change back to the OpenHand cursor.
Unfortunately, I'm having an issue that if I drag and release my panel (with the mouse within the bounds of the panel), eventually (usually on the second or third go) the Grab cursor will not change back into the OpenHand cursor.
This remains the case even when you move the mouse out of the panel and back in and try dragging and releasing.
Here is my Panel class:
public class MapPanel : Panel
{
private Point MouseDownLocation;
private Cursor OpenHand = new Cursor(Properties.Resources.hand.Handle);
private Cursor Grab = new Cursor(Properties.Resources.punch.Handle);
public MapPanel()
{
MouseDown += new MouseEventHandler(mapPanel_MouseDown);
MouseUp += new MouseEventHandler(mapPanel_MouseUp);
MouseMove += new MouseEventHandler(mapPanel_MouseMove);
}
private void mapPanel_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
Cursor = Grab;
MouseDownLocation = e.Location;
}
}
private void mapPanel_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
Cursor = OpenHand;
}
}
private void mapPanel_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
Left = e.X + Left - MouseDownLocation.X;
Top = e.Y + Top - MouseDownLocation.Y;
}
}
}
Interestingly there are no issues if I don't include the MouseMove handler, however the Cursor = OpenHand; statement inside the MouseUp method does still get called with the MouseMove handler included. It just doesn't take effect.
Hopefully someone can shed some light on this. Any help would be very much appreciated!

How to enable a disabled control on click

I have a DevExpress grid, which is disabled on screen. When I click the control, I want it to become enabled. Right now I have a click event set up for the grid:
private void gridPSR_Click(object sender, EventArgs e)
{
gridPSR.Enabled = true;
}
This isn't working. How should I be going about this?
Disabled controls do not receive windows messages, so you will never get the click message on that control. Assuming this is Winforms, you can listen for the click on the form (or whatever control is hosting this grid) and check if the click location is in the rectangle of the disabled control and then enable the control accordingly:
void Form1_MouseClick(object sender, MouseEventArgs e)
{
if (gridPSR.ClientRectangle.Contains(e.Location))
{
gridPSR.Enabled = true;
}
}
I know it's an old post but for me bounds worked instead of ClientRectangle
private void OnPanelMouseClick(object sender, MouseEventArgs e)
{
if ((e.Button == MouseButtons.Left) &&
myControl.Bounds.Contains(e.Location) &&
!myControl.Enabled)
{
myControl.Enabled = true;
}
}
Where myControl is member variable of your control instance. OnPanelMouseClick handler should be linked with MouseClick event of form or container that holds control.
In this code I am setting an event on a disabled TextBox control called 'txtNumLabels'. I tested this code with the text box both on a Form and also with having it within a GroupBox container.
Set an event in the constructor after the 'InitializeComponent();'
this.txtNumLabels.Parent.MouseClick += new System.Windows.Forms.MouseEventHandler(this.txtNumLabels_Parent_MouseClick);
Here is the event handler -
private void txtNumLabels_Parent_MouseClick(object sender, MouseEventArgs mouseEvent)
{
// The Bounds property of a control returns a rectangle of its Location and Size within its parent control
Rectangle rect = txtNumLabels.Bounds;
// Other method that gets the same rectangle -
// Point t = txtNumLabels.Location;
// Size ts = txtNumLabels.Size;
// Rectangle rect = new Rectangle(t, ts);
if (rect.Contains(mouseEvent.Location))
{
txtNumLabels.Enabled = true;
}
}

"Click and Drag-Mouse-Over" behavior for Objects in a Panel

I'll try and keep this generic for future refference.
Let's suppose our Form has a grid of Squares (RectangleShapes which we'll reffer to individually as "points".) Since the size of this grid can vary before the Form loads, we've create all the points in a panel when we first load our Form and can use...
foreach (Squares point in mySquares)
...when we want to change their behavior or appearance. Now, we've already developed code to change the color of each individual point when the user clicks left or right. It would look something like:
private void panelGrid_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left || e.Button == MouseButtons.Right)
{
foreach (Squares point in mySquares)
{
if (point.Rectangle.Contains(e.Location)) // determine which point to use
{
if (e.Button == MouseButtons.Left)
{
Pencil(point); // left-click to fill color
}
else
{
Erase(point); // right-click to erase color
}
}
}
panelGrid.Invalidate(); // refreshes grid
}
}
And this works like a charm. But suppose we need to change this code: Now we also want the color to change when a mouse button is held down, and the cursor is moved onto a new point. (Sort of like MS-Paint: Dragging the pencil tool over multiple pixels fills each one in turn.)
What confuses me is the proper way to implement this behavior properly. To my understanding, I would want the Pencil/Eraser methods to call when:
A.) The mouse enters a "point" AND a button is already held down.
OR
B.) A mouse button becomes pressed. (See above code)
Where this gets tricky for me is determining how best to implement the new checks, and how to perform them on the individual points in the grid - or even if that's necessary. Any tips?
Sounds like you want the MouseDown and MouseMove events to do the same thing:
private void MouseStuff(MouseEventArgs e) {
if (e.Button == MouseButtons.Left || e.Button == MouseButtons.Right)
{
foreach (Squares point in mySquares) {
if (point.Square.Contains(e.Location)) {
if (e.Button == MouseButtons.Left) {
Pencil(point);
} else {
Erase(point);
}
}
}
panelGrid.Invalidate();
}
}
then try calling it from both events:
private void panelGrid_MouseDown(object sender, MouseEventArgs e) {
MouseStuff(e);
}
private void panelGrid_MouseMove(object sender, MouseEventArgs e) {
MouseStuff(e);
}
Look into using a double-buffered panel to make the control less flicky since invalidating the panel on a MouseMove event can be quite intensive:
public class BufferedPanel : Panel {
public BufferedPanel() {
this.DoubleBuffered = true;
this.ResizeRedraw = true;
}
}

Prevent drag and drop outside of the current control (TreeNodes in a TreeView)

I'm maintaining a Windows app that has multiple forms in the one window (form1, form2, form3). I'm treating the other form2 and form3 as black boxes at the moment. In form1 I have a TreeView, and I'm implementing drag and drop functionality within that TreeView.
How can I prevent a drop outside of the form1 control?
I'm implementing 3 events handlers:
private void treeView_ItemDrag (...)
{
DoDragDrop(e.Item, DragDropEffects.Move);
}
private void treeView_DragEvent (...)
{
e.Effect = DragDropEffects.Move;
}
private void treeView_DragDrop (...)
{
//the node move logic here
}
form2 and form3 have a drag and drop relationship between them, so when I drag a node from form1 into form3 by default it allows the move (bad). I want to be able to prevent this from the form1 control code.
How can I prevent a drop outside of the form1 control? I've looked at the _DragLeave event, but I'm unsure how to control the operation without the DragEventArgs.
There is this little know property in the Cursor object that can restrict the mouse movement only to a certain rectangle.
this as a global variable for Form1
Rectangle _originalClip;
this goes in your Form1_Load event
_originalClip = Cursor.Clip;
this could be in your treeView.ItemDrag, forcing the cursor inside the form1 client area
Cursor.Clip = form1.RectangleToScreen(form1.ClientRectangle);
Now you need to restore the original clip area. A good place will be in the treeView.DragDrop. But to be on the safe side put also in your Form1_Closing event
Cursor.Clip = _originalClip;
You can check if mouse drag action is going outside the allowed area, and if so, cancel the drag action.
There's a nice sample in MSDN that uses the QueryContinueDrag event for that purpose. I think you can use that on the base of your solution.
Link: DragAction Enumeration
I know this is an old topic, but since I've never found a good answer to how to prevent dragging a control outside of a panel, I thought that I'd throw in the solution that I put together. I used some the tips from above and some work of my own.
private void Form1_Load(object sender, EventArgs e)
{
_originalClip = Cursor.Clip;
}
private void pb_MouseMove(object sender, MouseEventArgs e)
{
PictureBox pb = (PictureBox)sender;
if (e.Button == MouseButtons.Left)
{
Size sz = new Size(panel1.RectangleToScreen(panel1.ClientRectangle).Width - (pb.Width), panel1.RectangleToScreen(panel1.ClientRectangle).Height - (pb.Height));
Point loc = new Point(panel1.RectangleToScreen(panel1.ClientRectangle).X + (pb.Width / 2), panel1.RectangleToScreen(panel1.ClientRectangle).Y + (pb.Height / 2));
Rectangle rct = new Rectangle(loc, sz);
Cursor.Clip = rct;
pb.Left += (e.X - x);
pb.Top += (e.Y - y);
}
}
private void pb_MouseUp(object sender, MouseEventArgs e)
{
Cursor.Clip = _originalClip;
}
What this does is uses the Cursor.Clip method along with a Rectangle object with it's size set to be the size of the panel ("panel1" in the code) containing a bunch of Pictureboxes ("pb" in the code). The new rectangle's size is set to the parent panel minus the width and height of the Picturebox and it's location set to the location of panel1 minus half of the Picturebox's width and height. This gives you a rectangle which will constrain the Picturebox from being drug outside the panel.

Event handler works partially

I create in a class of a WPF application two identical circles and I want to assign the same event to both of them. However only on one of them the event hander works while on the other not. I tried to implement the same event handler with two different names but still the same happens. What could a potential problem in respect of the following code?
SmallCircle leftCircle1 = new InputCircle(Brushes.Yellow, 7, 7);
Ellipse s1Ellipse = leftCircle1.thisEllipse;
rectCanvas.Children.Add(s1Ellipse);
SmallCircle leftCircle2 = new InputCircle(Brushes.Yellow, 7, 7);
Ellipse s2Ellipse = leftCircle2.thisEllipse;
rectCanvas.Children.Add(s2Ellipse);
s1Ellipse.MouseLeftButtonDown += new MouseButtonEventHandler(input_MouseLeftButtonDown1);
s2Ellipse.MouseLeftButtonDown += new MouseButtonEventHandler(input_MouseLeftButtonDown1);
s3Ellipse.MouseLeftButtonDown += new MouseButtonEventHandler(output_MouseLeftButtonDown1);
private void output_MouseLeftButtonDown1(object sender, MouseButtonEventArgs e)
{
rectCanvas.MouseLeftButtonDown -= Canvas_MouseLeftButtonDown;
rectCanvas.MouseLeftButtonUp -= Canvas_MouseLeftButtonUp;
rectCanvas.MouseMove -= Canvas_MouseMove;
nodeComb.lineCreated = true;
Point StartPosition = e.GetPosition(sender as UIElement);
nodeComb.initialPoint = StartPosition;
}
private void input_MouseLeftButtonDown1(object sender, MouseButtonEventArgs e)
{
if (nodeComb.lineCreated == true)
{
Point EndPosition = e.GetPosition(sender as UIElement);
nodeComb.endingPoint = EndPosition;
nodeComb.createLine();
nodeComb.lineCreated = false;
}
}
In particular, s2Ellipse event handler works, while s1Ellipse does not.
Only the bottom input circle (s2) listens to handler, the above does not.
Really very strange!
Ok, I lost finally the problem. I add a textbox on the bottom half of the rectangle which contains the name of each rectangle. This had an adequate width to cover the small input circle, so as the mouse event handler not to apply to the upper circle. I just trimmed its width and the handler applies to both circles. Really deceptive...

Categories