Question

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?

Was it helpful?

Solution 2

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.

OTHER TIPS

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..

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top