Hi all,

I have an application with a ListView of remote files. I'm trying to implement a drag-drop of these files between my application and e.g. windows explorer. I have an object implementing IDataObject used for the DragDrop operation and the files are passed in a string[].

The problem is that explorer calls GetData() multiple times (e.g. > 20 times) before the drop has occurred. Because the source files are remote, they need to be downloaded so I'd only like to download them after the drop has occurred and not before. How is this done?

Thanks in advance,
Doug

Can you paste your implementation of your DragDrop event please and any other events you have linked up related to DragDrop operations.

Hi Ketsuekiame, thanks for the reply.

The drag-drop handlers are:

private void lvFiles_DragEnter(object sender, DragEventArgs e)
{
    if (e.Data.GetDataPresent(DataFormats.FileDrop)) 
        e.Effect = DragDropEffects.Copy;
}

private void lvFiles_DragDrop(object sender, DragEventArgs e)
{
    if (e.Data.GetDataPresent(DataFormats.FileDrop))
    {
        string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
        foreach (string file in files)
        {
            Text = "Copying " + file + " to " + remoteDir + "...";
            // ...
        }
    }
}

private void lvFiles_ItemDrag(object sender, ItemDragEventArgs e)
{
    System.Diagnostics.Debug.Print("ItemDrag");
    MyFileDataObject dfo = new MyFileDataObject();
    dfo.getDataCallback += new Func<object>(dfo_getDataCallback);
    DoDragDrop(dfo, DragDropEffects.Move);

    System.Diagnostics.Debug.Print("{0} = {1}\r\n{2} = {3}", "Button", e.Button, "Item", e.Item);
}

object dfo_getDataCallback()
{            
    System.Diagnostics.Debug.Print("dfo_getDataCallback");
    return new string[] { "c:\\temp\\junk.txt" };
}

private void lvFiles_DragLeave(object sender, EventArgs e)
{
    System.Diagnostics.Debug.Print("DragLeave");
}

private void lvFiles_DragOver(object sender, DragEventArgs e)
{
    System.Diagnostics.Debug.Print("DragOver: " + string.Join(", ", e.Data.GetFormats()));
}

The class implementing IDataObject is thus:

class MyFileDataObject : IDataObject
{
    public event Func<object> getDataCallback;

    // Returns: The data associated with the specified format, or null.
    public object GetData(string format)
    {
        if (format == DataFormats.FileDrop && getDataCallback != null)
            return getDataCallback();

        return null;
    }

    public bool GetDataPresent(string format)
    {
        return format == DataFormats.FileDrop;
    }

    public string[] GetFormats()
    {
        return new string[] { DataFormats.FileDrop };
    }

    public object GetData(Type format){return GetData(format.ToString());}
    public object GetData(string format, bool autoConvert){return GetData(format);}

    public bool GetDataPresent(Type format){return GetDataPresent(format.ToString());}
    public bool GetDataPresent(string format, bool autoConvert){return GetDataPresent(format);}

    public string[] GetFormats(bool autoConvert){return GetFormats();}
    public void SetData(object data){throw new Exception("Unimplemented");}
    public void SetData(string format, object data){SetData(data);}
    public void SetData(Type format, object data){SetData(data);}
    public void SetData(string format, bool autoConvert, object data){SetData(data);}
}

Edited 3 Years Ago by dougy83

It's a bit complicated to explain in a post but this link will explain how, why and when GetData is called. MSDN Link

I can't really help you any more than that without writing a couple of sides of A4 ;)

Edited 3 Years Ago by Ketsuekiame

Comments
Very appropriate link

Thanks very much for the link, it looks like it deals with exactly what I'm after for lazy load. I haven't got a chance to try it out yet.

I also found an already implemented virtual file DataObject which can pass the stream, which I will try first
http://blogs.msdn.com/b/delay/archive/2009/10/26/creating-something-from-nothing-developer-friendly-virtual-file-implementation-for-net.aspx

So there's still the problem I was having before of the GetData being called before the drop is complete.. how do you tell when the final drop has happened?

You can call the method QueryContinueDrag this will return one of the following:

S_OK (keep the drag/drop loop going)
DRAGDROP_S_DROP (Do the drop and complete the drag/drop operation)
DRAGDROP_S_CANCEL (Drag/Drop operation was cancelled)

It would probably be appropriate to call this in your GetData method. Once the result becomes DRAGDROP_S_DROP you can actually fill the data object with real data.

I can't think of any other way around this as Explorer isn't under our control ^^

Comments
good lead

That sounds simple way to go about it! Apparently that function shouldn't be called directly (http://msdn.microsoft.com/en-us/library/windows/desktop/ms690076(v=vs.85).aspx), but no matter.

In System.Windows.Forms, the Control.QueryContrinueDrag is an event that provides me with the mouse button status (and Alt/Ctrl/Shift), so I can just check for a mouse button release (without abortion request) to signal that the drop should go ahead. Alternately, if this happens at the wrong time, I can just check the mouse buttons (and drop abortion) in the GetData callback.

I'll try this tonight. Thanks again for your help.

This article has been dead for over six months. Start a new discussion instead.