1

In short, trying to do what the following two lines of C# code do without using System.Windows.Forms, because it is .NET Core and not a WinForms project.

var data = new System.Windows.Forms.DataObject(
    System.Windows.Forms.DataFormats.FileDrop, new string[] { @"C:\test.txt"});
dummyControl.DoDragDrop(data, System.Windows.Forms.DragDropEffects.Copy);

But it does not work. What have I done wrong? My procedure was,

  1. When mouse is down on a UI control, call SetCapture to capture the event.
  2. The IDropSource's QueryContinueDrag keep returning S_OK until the mouse button is up, and then DRAGDROP_S_DROP.
  3. My "DataObject" implementing IDataObject has EnumFormatEtc. In which I return only one FORMATETC.

        new FORMATETC()
        {
            cfFormat = CF_HDROP,
            ptd = IntPtr.Zero,
            dwAspect = DVASPECT.DVASPECT_ICON,
            lindex = -1,
            tymed = TYMED.TYMED_FILE
        }
    
    1. In QueryGetData, if the format's tymed is TYMED_FILE, return S_OK meaning I am dragging a file. Otherwise return DV_E_TYMED meaning I don't have that type of data.

    2. In GetData, if the format's tymed is TYMED_FILE, I set a file like this

          medium = new STGMEDIUM();
          medium.tymed = TYMED.TYMED_FILE;
          medium.unionmember = Marshal.StringToHGlobalUni(@"C:\test.txt");
          medium.pUnkForRelease = IntPtr.Zero;
      

I tried to drag-and-drop to Notepad. Problems are,

  1. When DoDragDrop is not called on the mouse down event, mouse is captured and mouse up event is received. But when DoDragDrop is called, QueryContinueDrag is endlessly called and I don't get the mouse up event, even after I released the mouse. I tried to call DoDragDrop in another thread, but that did not work.
  2. I get callbacks like QueryGetData(TYMED_HGLOBAL) or QueryGetData(TYMED_HGLOBAL, TYMED_ISTREAM, TYMED_GDI, TYMED_MFPICT, TYMED_ENHMF), but not for TYMED_FILE. Why isn't Notepad requesting that?

The drag and drop seems unnecessarily complex, but for now, I am only interested in dragging a file, so I did not want to implement parts other than that. What in the above procedure or assumptions are wrong?

Damn Vegetables
  • 11,484
  • 13
  • 80
  • 135
  • Surely you'd prefer updating to .NETCore v3.0, no hacks necessary. – Hans Passant Sep 05 '19 at 07:39
  • I knew that .NET Core 3.0 comes with WinForms, but in this case, I am trying to use GTK# for cross-platformness. But, unfortunately, it seemed that GTK# does not support Windows file drag-and-drop (GTK# app to Win32 app). I think I could use WinForms's DoDragDrop even in a GTK# app by creating a dummy `Control`, but doing so could load unnecessary DLLs, so I thought using Win32 API directly would be more efficient. It is just that it is A LOT complicated that I expected. – Damn Vegetables Sep 05 '19 at 21:16
  • That's courageous. The Mono team tried it but they gave up due to the relentless compatibility problems. You're having some of that ;) The [DataObject class](https://github.com/dotnet/winforms/blob/master/src/System.Windows.Forms/src/System/Windows/Forms/DataObject.cs) gets the real job done. – Hans Passant Sep 05 '19 at 21:43

1 Answers1

2

When DoDragDrop is not called on the mouse down event, mouse is captured and mouse up event is received. But when DoDragDrop is called, QueryContinueDrag is endlessly called and I don't get the mouse up event, even after I released the mouse.

You don't get a mouse up event because DoDragDrop() blocks your UI message loop until the drag operation is completed. So you need to use the input flags given to your QueryContinueDrag() implementation to decide whether to continue dragging, perform the drop, or abort the operation.

If you start the drag on a left mouse down, return S_OK if the grfKeyState parameter includes the MK_LBUTTON flag, and return DRAGDROP_S_DROP if the MK_LBUTTON flag is cleared. Return DRAGDROP_S_CANCEL if the fEscapePressed parameter is true. This is documented on MSDN:

IDropSource::QueryContinueDrag method

Parameters

fEscapePressed

Indicates whether the Esc key has been pressed since the previous call to QueryContinueDrag or to DoDragDrop if this is the first call to QueryContinueDrag. A TRUE value indicates the end user has pressed the escape key; a FALSE value indicates it has not been pressed.

grfKeyState

The current state of the keyboard modifier keys on the keyboard. Possible values can be a combination of any of the flags MK_CONTROL, MK_SHIFT, MK_ALT, MK_BUTTON, MK_LBUTTON, MK_MBUTTON, and MK_RBUTTON.

Return Value

This method can return the following values.

S_OK
The drag operation should continue. This result occurs if no errors are detected, the mouse button starting the drag-and-drop operation has not been released, and the Esc key has not been detected.

DRAGDROP_S_DROP
The drop operation should occur completing the drag operation. This result occurs if grfKeyState indicates that the key that started the drag-and-drop operation has been released.

DRAGDROP_S_CANCEL
The drag operation should be canceled with no drop operation occurring. This result occurs if fEscapePressed is TRUE, indicating the Esc key has been pressed.

Remarks

The DoDragDrop function calls QueryContinueDrag whenever it detects a change in the keyboard or mouse button state during a drag-and-drop operation. QueryContinueDrag must determine whether the drag-and-drop operation should be continued, canceled, or completed based on the contents of the parameters grfKeyState and fEscapePressed.


I get callbacks like QueryGetData(TYMED_HGLOBAL) or QueryGetData(TYMED_HGLOBAL, TYMED_ISTREAM, TYMED_GDI, TYMED_MFPICT, TYMED_ENHMF), but not for TYMED_FILE. Why isn't Notepad requesting that?

You can't use TYMED_FILE for CF_HDROP, you must use TYMED_HGLOBAL. And the content of the allocated HGLOBAL must be a DROPFILES struct followed by a double-null-terminated list of file paths. This is documented on MSDN:

Shell Clipboard Formats

CF_HDROP

This clipboard format is used when transferring the locations of a group of existing files. Unlike the other Shell formats, it is predefined, so there is no need to call RegisterClipboardFormat. The data consists of an STGMEDIUM structure that contains a global memory object. The structure's hGlobal member points to a DROPFILES structure as its hGlobal member.

The pFiles member of the DROPFILES structure contains an offset to a double null-terminated character array that contains the file names. If you are extracting a CF_HDROP format from a data object, you can use DragQueryFile to extract individual file names from the global memory object. If you are creating a CF_HDROP format to place in a data object, you will need to construct the file name array.

The file name array consists of a series of strings, each containing one file's fully qualified path, including the terminating NULL character. An additional null character is appended to the final string to terminate the array. For example, if the files c:\temp1.txt and c:\temp2.txt are being transferred, the character array looks like this:

c:\temp1.txt'\0'c:\temp2.txt'\0''\0'

Note
In this example, '\0' is used to represent the null character, not the literal characters that should be included.

If the object was copied to the clipboard as part of a drag-and-drop operation, the pt member of the DROPFILES structure contains the coordinates of the point where the object was dropped. You can use DragQueryPoint to extract the cursor coordinates.

If this format is present in a data object, an OLE drag loop simulates WM_DROPFILES functionality with non-OLE drop targets. This is important if your application is the source of a drag-and-drop operation on a Windows 3.1 system.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thank you. For problem 1, I had skimmed the document and thought it was about keyboard and thus not needed. The argument's description is sort of tricky... For 2, after seeing your answer, I searched Google for an example DROPFILES in C# and found this [existing StackOverflow question](https://stackoverflow.com/questions/5762222/c-sharp-drag-drop-problem). Using the person's code, I finally got the file drag-and-drop working. Now, the only problem is that the mouse pointer is the "no symbol", instead of a rectangle with a plus sign. – Damn Vegetables Sep 05 '19 at 17:33
  • I got the copy pointer. It turned out that I am responsible for changing the mouse pointer, too. In the `GiveFeedback` callback, I set the copy pointer using Win32 API calls (because .NET's existing cursor code was inside `Windows.Forms`). [This existing answer](https://stackoverflow.com/questions/49485890/using-the-windows-drag-copy-cursor) showed how to load the the copy cursor from "ole32.dll". I wish there were an independent library (not WinForms, WPF, etc) that provides wrappers for Win32 GUI APIs... – Damn Vegetables Sep 05 '19 at 18:28