Moveable Custom Control - c#

I have a custom control and I want the user to be able to drag it. So I put in the following code in the custom control:
void MoveableStackPanel_MouseMove(object sender, MouseEventArgs e)
{
if (IsMoving)
{
Point newLoc = e.GetPosition(null);
MainWindow.Instance.Title = newLoc.ToString(); // Debug
Margin = new Thickness(newLoc.X - 48, newLoc.Y - 48, 0, 0);
}
}
Note the "-48" in the code.
When the mouse is moved up or left then the mouse is not in the controls area anymore and thus does no longer trigger the MouseMove event. So I added the -48 twice to work around that. But when the user moves the mouse faster than the framework can update then the mouse will get outside the controls area and the control also won't move anymore.
I was thinking about assigning an IMovableInterface and keep lists of controls that are moving in the main form and such but that is all such a hassle and such... What is the proper solution?
P.S.: the controls are generated dynamically so I need the solution in C# code and not in XML.

Try using the CaptureMouse Method.
See if something like this works for you.:
void moveableStackPanel1_MouseUp(object sender, MouseButtonEventArgs e)
{
ReleaseMouseCapture();
}
void moveableStackPanel1_MouseDown(object sender, MouseButtonEventArgs e)
{
if (IsEnabled && IsVisible)
CaptureMouse();
}
void moveableStackPanel1_MouseMove(object sender, MouseEventArgs e)
{
if (IsMouseCaptured)
{
Point newLoc = e.GetPosition(null);
Margin = new Thickness(newLoc.X, newLoc.Y, 0, 0);
}
}

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
}

How can I make images drag and drop-able to reorder them?

I'm building an application in which I'd like a user to be able to reorder pictures in a form in two columns. I've got a flowLayoutPanel of a set width, and pictures are added via the OpenFileDialog and scaled to half the width (minus an allowance for a scroll bar) of the flow layout panel.
This is where I'm stuck - I've tried adding the images as Labels, Buttons, and now PictureBoxes and I can't work out how to actually move them around. I gave up on labels because CanSelect is false - although I didn't know if that would have made a difference - and I moved on from buttons because I realised picture boxes existed. I'm open to switching out which controls I use but the images will always need to be in two columns.
Here's the code I currently have for the DragEnter and DragDrop events:
private void flowLayoutPanel_6_Cards_DragEnter(object sender, DragEventArgs e)
{
e.Effect = DragDropEffects.All;
}
private void flowLayoutPanel_6_Cards_DragDrop(object sender, DragEventArgs e)
{
MessageBox.Show("dropped");
}
How can I implement this? What controls should I use and what properties should I be looking at to make this possible?
So thanks to #TaW's comment I now know that you have to add a DoDragDrop call to the MouseDown event on whatever you're dragging. My now-working code is below (thanks mostly to this tutorial):
private void flowLayoutPanel_6_Cards_DragDrop(object sender, DragEventArgs e)
{
PictureBox picture = (PictureBox)e.Data.GetData(typeof(PictureBox));
FlowLayoutPanel _source = (FlowLayoutPanel)picture.Parent;
FlowLayoutPanel _destination = (FlowLayoutPanel)sender;
if (_source != _destination)
{
//where did you even get this from?
}
else
{
Point p = _destination.PointToClient(new Point(e.X, e.Y));
var item = _destination.GetChildAtPoint(p);
int index = _destination.Controls.GetChildIndex(item, false);
_destination.Controls.SetChildIndex(picture, index);
_destination.Invalidate();
}
}
private void flowLayoutPanel_6_Cards_DragEnter(object sender, DragEventArgs e)
{
e.Effect = DragDropEffects.All;
}
void p_MouseDown(object sender, MouseEventArgs e)
{
PictureBox p = (PictureBox)sender;
p.DoDragDrop(p, DragDropEffects.All);
}

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

C# Moving own controls around and make them droppable

A good morning to you all!
Yesterday I ran into a problem while trying to implement a custom DragDrop for my own controls in a WinForms application.
I have a form which can dynamically create instances of two of my own controls. These controls consist of some controls themselves, such as buttons, labels and listboxes/treeviews. The controls serve as a representation for a certain dataset. Now, we all know the class diagrams in VS. There you have these boxes representing classes. You can move the boxes around on the canvas by doing - what I would call - dragging them around, much like you would drag around files. To accomplish this with my own controls I have done the following:
public partial class MyControl: UserControl
{
private Control activeControl;
private void GeneralMouseDown(MouseEventArgs e)
{
activeControl = this;
previousLocation = e.Location;
Cursor = Cursors.Hand;
}
private void GeneralMouseMove(Control sender, MouseEventArgs e)
{
if (activeControl == null || activeControl != sender)
return;
var location = activeControl.Location;
location.Offset(e.Location.X - previousLocation.X, e.Location.Y - previousLocation.Y);
activeControl.Location = location;
}
private void GeneralMouseUp()
{
activeControl = null;
Cursor = Cursors.Default;
}
}
The controls on my control which I want to "grab" for dragging MyControl have their MouseDown-, MouseMove- and MouseUp-events pointing to these three methods. As a result I can move my control about on the form freely, just as I want to.
Here comes the tricky bit:
The datasets I have controls for can be in hierarchical dependencies, which means, one control represents detailling of a component of the other, which is why my controls have Listboxes or TreeViews. To establish such a hierarchical dependency I would very much like to DragDrop the lower-order-control on the listbox of my higher-order-control, causing data to be transfered.
I know how to set up my DragEnter and DragDrop methods for the listbox, as I have done so previously with files. Just for completeness:
private void lst_MyControl_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(typeof(MyControl)))
e.Effect = DragDropEffects.Move;
else e.Effect = DragDropEffects.None;
}
Here's the problem: As I am moving my control about (which gets repainted at every position, giving a very much wanted effect!), when I "drag" it over the target-listbox, the DragEnter-event does not get fired. I thought I could work around this problem by telling Windows "Hey, I'm, Dragging'n'Dropping here!", thus adding to my GeneralMouseDown-method:
this.DoDragDrop(this, DragDropEffects.Move);
This, on the one hand, gets the DragEnter-event to fire => Yeah! On the other hand is the moving-around-part only working after I release the mouse, causing the control to hang on the mousepointer forever => Anti-Yeah!
Here's the question: Is there a way, to have both actions at the same time? So that I can move my control around, seing it at every position as I do now and fire the DragEnter-event when I get to that area of the other control?
Moving your Control around interferes with the automatic DragDrop handling.
I'd recommend to staying with the normal DragDrop procedures, that is leaving all visuals to the system: It will display a cursor that indicates when a valid target is entered, then change to one that indicates the operation.
You need just 3 lines, no hassle and the user won't seen bulky controls moving around.
Here is a version where I drag a PictureBox onto a ListBox:
private void listBox1_DragEnter(object sender, DragEventArgs e)
{
e.Effect = DragDropEffects.Copy;
}
private void listBox1_DragDrop(object sender, DragEventArgs e)
{
listBox1.Items.Add( e.Data.GetData(DataFormats.Text));
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
pictureBox1.DoDragDrop(pictureBox1.ImageLocation, DragDropEffects.Copy);
}
Obviously you will set up and receive your data in your own ways..
Edit:
Now, if on the other hand you need to move controls around to rearrange them, maybe you should give up on Drag&Drop to handle the additional data transfers and code this portion on your own as well. You could use the MouseEnter event of a receiving control..
After a bit of fiddeling I did it. I switched the level on which the dragging is handled.
First I need just the MouseDown-event
public Point GrabPoint;
private void GeneralMouseDown(MouseEventArgs e)
{
GrabPoint = e.Location;
this.DoDragDrop(this, DragDropEffects.Move);
}
I set the point where I grab the control and initiate a DragDrop. On my form I handle all the dragging:
private void frmMain_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(typeof(MyControl1)) || e.Data.GetDataPresent(typeof(MyControl2)) || e.Data.GetDataPresent(typeof(MyControl3)))
e.Effect = DragDropEffects.Move;
else e.Effect = DragDropEffects.None;
}
private void frmMain_DragOver(object sender, DragEventArgs e)
{
Point DragTarget = new Point(e.X, e.Y);
Point GrabPoint = new Point(0, 0);
if (e.Data.GetDataPresent(typeof(MyControl1)))
GrabPoint = ((MyControl1)e.Data.GetData(typeof(MyControl1))).GrabPoint;
else if (e.Data.GetDataPresent(typeof(MyControl2)))
GrabPoint = ((MyControl2)e.Data.GetData(typeof(MyControl2))).GrabPoint;
else if (e.Data.GetDataPresent(typeof(MyControl3)))
GrabPoint = ((MyControl3)e.Data.GetData(typeof(MyControl3))).GrabPoint;
DragTarget.X -= GrabPoint.X;
DragTarget.Y -= GrabPoint.Y;
DragTarget = this.PointToClient(DragTarget);
if (e.Data.GetDataPresent(typeof(MyControl1)))
((MyControl1)e.Data.GetData(typeof(MyControl1))).Location = DragTarget;
else if (e.Data.GetDataPresent(typeof(MyControl2)))
((MyControl2)e.Data.GetData(typeof(MyControl2))).Location = DragTarget;
else if (e.Data.GetDataPresent(typeof(MyControl3)))
((MyControl3)e.Data.GetData(typeof(MyControl3))).Location = DragTarget;
}
At the moment I don't need the DragDrop-event, since nothing should happen when any control is dropped on the form. This way I always paint my control while it is being dragged => Yeah!
The next part is easy: Since I am really dragging the control, this bit of code does DragDrop-handling on my listbox Edit: ListView:
private void lst_SubControls_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(typeof(MyControl2)))
e.Effect = DragDropEffects.Move;
else e.Effect = DragDropEffects.None;
}
private void lst_SubControls_DragDrop(object sender, DragEventArgs e)
{
lst_SubControls.Items.Add(((MyControl2)e.Data.GetData(typeof(MyControl2))).SpecificDrive);
((MyControl2)e.Data.GetData(typeof(MyControl2))).DeleteThisControl();
}
This results in an entry added to the list and deletion of the dragged control. At this point there could be a check, wether the ctrl-key is pressed to copy the contents and not to delete the control.

.NET C# MouseEnter listener on a Control WITH Scrollbar

As long as the mouse is over a specific control, we show some form. When the mouse leaves the control, we hide the control after a small timeout. This is standard hover behavior.
However, when a control (for example a Treeview) has a scrollbar, and the mouse is ON or OVER the scrollbar, the events don't fire ...
If we could get a reference to the scrollbar control, this would solve our problem, as we would add the same listener events to the scrollbar. However, the scrollbar isn't accessible as far as I know ...
How can we solve this problem ?
The scrollbar is in the tree view's non-client area. When the mouse moves there, it starts generating non-client messages like WM_NCMOUSEMOVE and WM_NCMOUSELEAVE. You would have to sub-class the TreeView and override WndProc() to detect these message.
This doesn't really solve your problem though, you'll still have a hard time with edge cases. A low-tech approach with a Timer always works:
private Form frmPopup;
private void treeView1_MouseEnter(object sender, EventArgs e) {
timer1.Enabled = true;
if (frmPopup == null) {
frmPopup = new Form2();
frmPopup.StartPosition = FormStartPosition.Manual;
frmPopup.Location = PointToScreen(new Point(treeView1.Right + 20, treeView1.Top));
frmPopup.FormClosed += (o, ea) => frmPopup = null;
frmPopup.Show();
}
}
private void timer1_Tick(object sender, EventArgs e) {
Rectangle rc = treeView1.RectangleToScreen(new Rectangle(0, 0, treeView1.Width, treeView1.Height));
if (!rc.Contains(Control.MousePosition)) {
timer1.Enabled = false;
if (frmPopup != null) frmPopup.Close();
}
}
I think there are several different ways to do this, but the key is your desire to have a timeout on the action. I think a combination of two techniques might work:
Put the control on a panel, docked to fill, and use the MouseEnter of the panel to turn on your behavior -- this will include the control's scrollbar. You can use the MouseLeave event of the panel as well, but you'll have to check the cursor's position to ensure it hasn't moved into the contained control. This method is mostly reliable, but moving the mouse quickly can confuse it.
If you combine this with a timer that starts when your shown/hidden control is shown and check the cursor position periodically. This will work, but your timeout before hiding the control won't necessarily be consistent (because the timer starts when they enter the control). You could stop/start the timer on mousemoves in the control to alleviate this somewhat.
I put together a project of the different methods I tried here: http://lovethedot.s3.amazonaws.com/100609StackoverflowScrollbarQuestion.zip
By docking the control you want to track in the panel, it essentially wraps it and you'll get MouseEnter at the very edge of the tracked control:
private void panel1_MouseEnter(object sender, EventArgs e)
{
this.Text = "in";
}
private void panel1_MouseLeave(object sender, EventArgs e)
{
if (!new Rectangle(new Point(0, 0), panel1.Size).Contains(panel1.PointToClient(Control.MousePosition)))
this.Text = "out";
}
You're tracking entry into the panel surrounding the control, and exit from that panel provided the cursor isn't inside the tracked control.
To get a better "leave" experience, it's combined with a Timer that checks to see where the cursor is as well:
private void listBox3_MouseEnter(object sender, EventArgs e)
{
button1.Visible = true;
visibleTimer.Stop();
visibleTimer.Start();
}
void visibleTimer_Tick(object sender, EventArgs e)
{
if (!new Rectangle(new Point(0, 0), listBox3.Size).Contains(listBox3.PointToClient(Control.MousePosition)))
{
visibleTimer.Stop();
button1.Visible = false;
}
}

Categories