Winforms custom cursor stops setting after a time - c#

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!

Related

Apply parent mouse events to child elements

I am making little Windows Forms Application.
I have PictureBox (parent) and Label (child) in it.
The Parent's Mouse Events are working perfectly, but Mouse events generated by child elements are not reflected on the Parent. The Cursor also changes back to its default (arrow).
Is it possible to pass events generated by child Controls, e.g., the MouseEnter event, to the Parent Control?
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Card.MouseEnter += new EventHandler(Card_MouseEnter);
Card.MouseLeave += new EventHandler(Card_MouseLeave);
Card.MouseDown += new MouseEventHandler(this.Card_MouseDown);
Card.MouseUp += new MouseEventHandler(this.Card_MouseUp);
}
void Card_MouseLeave(object sender, EventArgs e)
{
this.Card.BackgroundImage = ((System.Drawing.Image)(Properties.Resources.card_bg));
this.Rename("Running!");
}
void Card_MouseEnter(object sender, EventArgs e)
{
this.Card.BackgroundImage = ((System.Drawing.Image)(Properties.Resources.card_hover_bg));
}
private void Card_MouseDown(object sender, EventArgs e)
{
this.Card.BackgroundImage = ((System.Drawing.Image)(Properties.Resources.card_click_bg));
}
private void Card_MouseUp(object sender, EventArgs e)
{
this.Card.BackgroundImage = ((System.Drawing.Image)(Properties.Resources.card_hover_bg));
this.Rename("Please Wait...");
}
private void CardName_MouseDown(object sender, MouseEventArgs e)
{
}
void Rename(string args)
{
this.CardName.Text = args;
}
private void CardName_Click(object sender, EventArgs e)
{
}
}
         
This is what I have This is what I want to achieve
The first animation represents what I have now, the second is what I need to achieve :)
When I'm making pictureBox1.Controls.Add(label1) label1 is
disappearing and I tried bring to front and change color but couldn't
do it. Please if you will have any idea show me in provided code by me
to be understandable for me. Thank you all again and again :)
You'd use code like this, maybe in the Load() event of the Form:
private void Form1_Load(object sender, EventArgs e)
{
Point pt = CardName.Parent.PointToScreen(CardName.Location);
Card.Controls.Add(CardName);
CardName.Location = Card.PointToClient(pt);
}
This keeps the label in the same position as it was, but makes the picturebox the parent.
Not sure where you're going wrong. Here's an example showing it in action. Both the PictureBox (Card) and Label (CardName) are inside a Panel (panel1). Clicking on button2 toggles the visibility of the Card. Clicking on button1 makes Card the Parent of CardName. You can see that at first, only the Card toggles visibility, but after clicking on button1 and setting the Parent, both toggle visibility together since CardName is a Child of Card (it also changes its BackColor to match that of its new Parent):
Code:
public partial class Form1 : Form
{
private void button1_Click(object sender, EventArgs e)
{
Point pt = CardName.Parent.PointToScreen(CardName.Location);
Card.Controls.Add(CardName);
CardName.Location = Card.PointToClient(pt);
}
private void button2_Click(object sender, EventArgs e)
{
Card.Visible = !Card.Visible;
}
}
When I move mouse over label, panel thinks mouse left it and rises
MouseLeave event
Here is how you can tell if the cursor has actually left the BOUNDS of the Panel, as opposed to simply enter a child control within the Panel:
private void panel1_MouseEnter(object sender, EventArgs e)
{
panel1.BackColor = Color.Red;
}
private void panel1_MouseLeave(object sender, EventArgs e)
{
Point pt = panel1.PointToClient(Cursor.Position);
if (!panel1.ClientRectangle.Contains(pt))
{
// we only get in here when the cursor leaves the BOUNDS of panel1
panel1.BackColor = Control.DefaultBackColor;
}
}
First of all, you should build a UserControl as a container for all your objects: it'd make everything simpler (the one I'm using here is actually a UserControl, modified to comply with your current setup).
When a Control other than the PictureBox is interacted with, you can decide whether you want to trigger a similar action on the main Control or perform a different action based on what event has been generated.
▶ When the Mouse Pointer enters you assembled Control, you want to change the default Cursor: then, when one of the Labels raises the Enter event, call the method of the main Control that handles this. An event handler is a method, you can call it.
▶ When a Label is clicked, you don't want to trigger the related action of the main Control: in this case, there's nothing to do, just handle this event and perform the required action.
▶ The Label should be child of the main Control. You're using a PictureBox, which is not a ContainerControl. You can add child controls to it anyway. You need to do this in code, since - as mentioned - the PictureBox is not designed to host Controls, thus you cannot drop one inside it: the Control you drop will be parented with the Container that hosts the PictureBox (your Form, here).
When you set the parent in code, you need to remember that the Location of the child control is relative to the old Parent, so you have to re-define it's position.
E.g: PictureBox.Bounds = (100, 100, 100, 200) / Label.Bounds = (100, 250, 100, 50)
When the PictureBox becomes Parent of your Label, the Label.Location is still (100, 250): so, now, it will be hidden, since it's outside the visible bounds of its new Parent. You have to reposition it in relation to the new host: its new Location should be (0, 150), to keep the previous relative position.
PictureBox.Control.Add(Label);
//[...]
Label.Location = new Point(Label.Left - PictureBox.Left, Label.Top - PictureBox.Top);
=> Label.Location = (100 - 100, 250 - 100) => (0, 150)
Or, centered horizontally:
Label.Location = new Point((PictureBox.Width - Label.Width) / 2, Label.Top - PictureBox.Top);
=> Label.Location = ((100 - 100) / 2, 250 - 100) => (0, 150) // <- Since both have the same Width
Or, using positions relative to the Screen:
var p = Label.PointToScreen(Point.Empty); // Relative to the ClientRectangle (Top/Left = (0, 0))
PictureBox.Controls.Add(Label);
Label.Location = PictureBox.PointToClient(p);
In any case, call BringToFront() after, to ensure that the new child Control is brought on top and anchor the Control, so it will keep its position and its Width will be bound to the Parent Width:
Label.BringToFront();
Label.Anchor = AnchorStyles.Left | AnchorStyles.Bottom | AnchorStyles.Right;
Now, assuming you want to change the Cursor to Cursors.Hand when the Mouse enters your combined Control and reset to default when it leaves it:
▶ You want the Cursor to change shape in any case.
▶ You want to generate different actions when the PictureBox is clicked and when one of the Labels is clicked.
▶ Both Labels can have distinct actions when clicked.
→ In the visual sample, the Label above the PictureBox is named lblTitle, the Label inside the PictureBox, at the bottom, is named lblFooter.
→ The PictureBox is named ImageView.
Setup the handlers:
NOTE: With a UserControl, the events handling, e.g., in relation to MouseEnter, changes in:
// The Parent's MouseEnter calls OnMouseEnter
protected override void OnMouseEnter(EventArgs e)
{
base.OnMouseEnter(e);
this.Cursor = Cursors.Hand;
}
// Child Controls just call the same method
private void Labels_MouseEnter(object sender, EventArgs e) => OnMouseEnter(e);
public Form1()
{
InitializeComponent();
Point p = lblFooter.PointToScreen(Point.Empty);
ImageView.Controls.Add(lblFooter);
lblFooter.Location = ImageView.PointToClient(p);
ImageView_MouseEnter += ImageView_MouseEnter;
ImageView_MouseLeave += ImageView_MouseLeave;
// Not added in the code here, do whatever is needed with this handler
ImageView_Click += ImageView_Click;
lblFooter.MouseEnter += Labels_MouseEnter;
lblFooter.MouseLeave += Labels_MouseLeave;
lblFooter.MouseClick += lblFooter_MouseClick;
lblTitle.MouseEnter += Labels_MouseEnter;
lblTitle.MouseLeave += Labels_MouseLeave;
lblTitle.MouseDown += lblTitle_MouseDown;
lblTitle.MouseUp += lblTitle_MouseUp;
}
private void ImageView_MouseEnter(object sender, EventArgs e) => this.Cursor = Cursors.Hand;
private void ImageView_MouseLeave(object sender, EventArgs e) => this.Cursor = Cursors.Default;
private void Labels_MouseEnter(object sender, EventArgs e)
{
ImageView_MouseEnter(ImageView, e);
// [...]
// Do stuff related to the Labels Enter event
}
private void Labels_MouseLeave(object sender, EventArgs e) {
ImageView_MouseLeave(ImageView, e);
}
private void lblTitle_MouseDown(object sender, MouseEventArgs e) {
// Perform actions when the Mouse button is held down lblTitle
}
private void lblTitle_MouseUp(object sender, MouseEventArgs e) {
// Perform actions when the Mouse button is released
}
private void lblFooter_MouseClick(object sender, MouseEventArgs e) {
// Perform actions on a Mouse Click event on lblFooter
}

Trigger MouseMove/MouseUp After Leaving ListView

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);
}

Rectangle area over panel to catch mouse inputs

c# winforms here. I need to draw an invisible rectangle area over a panel and catch his mouse enter/leave events.
My situation (as for some other suggestions you may have):
I have a media player (the panel), on mouse enter event I make visible a little navigation menu (it's located over the panel). I want to hide the nav menu on mouse leave from the panel. This works but unfortunately also entering the nav menu make it invisible. Many thanks.
On mouse leave, simply see if the current Cursor.Position is contained by your rectangle. For example, using a panel and a label:
public Form1()
{
InitializeComponent();
panel1.MouseEnter += panel1_MouseEnter;
panel1.MouseLeave += common_MouseLeave;
label1.MouseLeave += common_MouseLeave;
}
private void panel1_MouseEnter(object sender, EventArgs e)
{
label1.Visible = true;
}
private void common_MouseLeave(object sender, EventArgs e)
{
Rectangle rc = panel1.RectangleToScreen(panel1.ClientRectangle);
if (!rc.Contains(Cursor.Position))
{
label1.Visible = false;
}
}

Redrawing issues when moving a control

I am trying to move some controls around on a WinForm with the mouse. I am using the code below. To see my issue start a new project in VS add the code below. Set the form BackGroundImage to any image then add any control. Set the control events for MouseUp, MouseDown, and MouseMove. Start debugging and click and move the control. The image in the form starts getting erased. I have tried several different suspend drawing classes and methods I have found on the net but nothing I have found lets me move the controls around without serious flickering or not being able to see the move. Any help would be appreciated.
P.S. If you set the same events to the up, move, and down events of the form, it moves fine with out any flickering.
private bool _mouseDown;
private Point _startPoint;
private void Event_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
_mouseDown = true;
_startPoint = new Point(e.X, e.Y);
}
}
private void Event_MouseUp(object sender, MouseEventArgs e)
{
_mouseDown = false;
}
private void Event_MouseMove(object sender, MouseEventArgs e)
{
if (_mouseDown)
{
Control s = sender as Control;
s.Location = new Point(e.X + s.Left - _startPoint.X, e.Y + s.Top - _startPoint.Y);
}
}

How to prevent other mouse buttons from interrupting MouseMove?

If you place a panel in a new C# project and capture it's MouseMove event like this:
private void panel1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left)
return;
Console.WriteLine("e.X: {0}, e.Y: {1}", e.X, e.Y);
}
It tells you the client coordinates of the mouse while the left mouse button is held down, even if the cursor goes outside of the panel.
However, if you are holding down left mouse button in the container and then, while holding down left mouse button, click any other mouse button on your mouse, it no longer calls MouseMove while outside the bounds of the container.
Is there any way to change this? Thanks for reading.
1: If you are trying to get it to work only when the left button is down, try the following:
bool mouseDown = false;
private void panel1_MouseMove(object sender, MouseEventArgs e)
{
if (!mouseDown)
return;
Console.WriteLine("e.X: {0}, e.Y: {1}", e.X, e.Y);
}
private void panel1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
mouseDown = true;
}
private void panel1_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
mouseDown = false;
}
2: Otherwise, if you want it to work when any mouse button is down, try the following:
int mouseDown = 0;
private void panel1_MouseMove(object sender, MouseEventArgs e)
{
if (mouseDown == 0)
return;
Console.WriteLine("e.X: {0}, e.Y: {1}", e.X, e.Y);
}
private void panel1_MouseDown(object sender, MouseEventArgs e)
{
mouseDown++;
}
private void panel1_MouseUp(object sender, MouseEventArgs e)
{
mouseDown--;
}
In addition to Justin's solution will say that:
I think it's because, if during drag you go out of the panel right click out of the panel, forces panel to lose the focus, so control no more becomes an active one.
In case of when you're dragging mouse inside panel and click with right click inside panel, happens something like this.
I just captured with Spy++ windows explorer and did a test, so moved the mouse with LButton down and at some point without releasing it made a right click. And here is a result:
With arrows I sign the row where I clicked with right button, where WM_CAPTURECHANGED
message sent. This message according to documentation is:
Sent to the window that is losing the mouse capture.
Look on next line with arrow. The handle of the next window is 0, so there is no any window. So this means, like a simple command: You lost a capture on mouse.
Hope this helps.

Categories