Ben
Ben

Reputation: 1311

How can I send WM_DROPFILES from C#?

I want to use C# code to simulate a user dragging and dropping a file onto a control in a separate process. As a stepping stone to this goal, I am trying to send a WM_DROPFILES message to my own TextBox and verifying that the DragDrop event is triggered.

With the code below in a Form containing a single TextBox and two Buttons, clicking on button1 successfully sets the text of textBox1 to "Hello world". So, it seems that I'm using SendMessage correctly and am able to supply arguments via pointers. Dragging and dropping a file from Windows Explorer onto textBox1 displays the MessageBox, so textBox1 is set up to receive drag-dropped files correctly. However, when I click button2, nothing happens. Why don't I see a MessageBox when I click button2?

using System;
using System.Data;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace StackOverflow
{
    public partial class BadDragDrop : Form
    {
        #region WINAPI

        [Serializable]
        [StructLayout(LayoutKind.Sequential)]
        struct POINT
        {
            public Int32 X;
            public Int32 Y;
        }

        [Serializable]
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        class DROPFILES
        {
            public Int32 size;
            public POINT pt;
            public Int32 fND;
            public Int32 WIDE;
        }

        const uint WM_DROPFILES = 0x0233;
        const uint WM_SETTEXT = 0x000C;

        [DllImport("Kernel32.dll", SetLastError = true)]
        static extern int GlobalLock(IntPtr Handle);

        [DllImport("Kernel32.dll", SetLastError = true)]
        static extern int GlobalUnlock(IntPtr Handle);

        [DllImport("user32.dll", SetLastError = true)]
        static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

        #endregion

        public BadDragDrop()
        {
            InitializeComponent();
            textBox1.AllowDrop = true;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            string textToSet = "Hello world\0";
            IntPtr p = Marshal.AllocHGlobal(textToSet.Length);
            Marshal.Copy(textToSet.Select(c => (byte)c).ToArray(), 0, p, textToSet.Length);
            int success = GlobalUnlock(p);
            SendMessage(textBox1.Handle, WM_SETTEXT, IntPtr.Zero, p);
            Marshal.FreeHGlobal(p);
        }

        private void button2_Click(object sender, EventArgs e)
        {
            string filePath = @"C:\Windows\win.ini" + "\0\0";

            DROPFILES s = new DROPFILES()
            {
                size = Marshal.SizeOf<DROPFILES>(),
                pt = new POINT() { X = 10, Y = 10 },
                fND = 0,
                WIDE = 0,
            };

            int wparamLen = s.size + filePath.Length;

            IntPtr p = Marshal.AllocHGlobal(wparamLen);
            int iSuccess = GlobalLock(p);

            Marshal.StructureToPtr(s, p, false);
            Marshal.Copy(filePath.Select(c => (byte)c).ToArray(), 0, p + s.size, filePath.Length);

            iSuccess = GlobalUnlock(p);

            var verify = new byte[wparamLen];
            Marshal.Copy(p, verify, 0, wparamLen);

            var ipSuccess = SendMessage(textBox1.Handle, WM_DROPFILES, p, IntPtr.Zero);
            Marshal.FreeHGlobal(p);
        }

        private void textBox1_DragDrop(object sender, DragEventArgs e)
        {
            MessageBox.Show(this, "Drag drop!");
        }

        private void textBox1_DragOver(object sender, DragEventArgs e)
        {
            e.Effect = DragDropEffects.Copy;
        }
    }
}

Upvotes: 0

Views: 2156

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 595367

The reason you don't see your MessageBox appear is likely because the TextBox doesn't handle WM_DROPFILES messages to begin with. It implements its drop support using OLE Drag&Drop instead by implementing the IDropTarget interface (see Drag and Drop Overview in the WPF documentation).

WM_DROPFILES has been deprecated ever since DoDragDrop() was introduced way back in Windows 95. OLE Drag&Drop has been the preferred way to implement drag&drop on Windows for a very long time. WM_DROPFILES is still supported by Windows (but not .NET) but only for backwards compatibility with legacy apps.

Dragging items from Windows Explorer to other apps uses OLE Drag&Drop under the hood, even if the receiver doesn't implement OLE Drag&Drop.

If you drag&drop an IDataObject onto a window that has had RegisterDragDrop() called on it (like your TextBox has), the IDataObject will be passed to the window's IDropTarget interface for handling.

If you drag&drop an IDataObject onto a window that doesn't implement IDropTarget, but has had DragAcceptFiles() called on it, or at least has the WS_EX_ACCEPTFILES window style, Windows will generate a WM_DROPFILES message if the IDataObject contains CF_HDROP data in it.

So, to do what you are attempting with your TextBox, you need to implement the IDropSource and IDataObject interfaces, and then call DoDragDrop() with them. Let Windows handle the actual dragging and dropping for you. See Shell Clipboard Formats and Handling Shell Data Transfer Scenarios.

Now, with that said, if you are still determined to send a WM_DROPFILES message to another process (which your question says is your ultimate goal), you can do that, and Windows will marshal your DROPFILES struct across process boundaries for you, but you need to use PostMessage() instead of SendMessage() (not sure why, only that it is required), and be sure to free the DROPFILES only if PostMessage() fails. Windows will free the DROPFILES for you if PostMessage() is successful.

But even then, there is no guarantee that the receiving process actually handles WM_DROPFILES messages (and even if it does, your manual WM_DROPFILES message might get blocked by UIPI, unless the receiver calls ChangeWindowMessageFilter/Ex() on itself to allow WM_DROPFILES and WM_COPYGLOBALDATA messages). The receiver might be expecting IDataObject instead. If the receiver even supports drag&drop at all.

Upvotes: 3

Related Questions