如何从C#发送WM_DROPFILES?

时间:2017-12-05 00:03:58

标签: c# winapi drag-and-drop windows-messages

我想使用C#代码模拟用户在单独的进程中将文件拖放到控件上。作为实现此目标的垫脚石,我正在尝试将WM_DROPFILES消息发送到我自己的TextBox并验证是否已触发DragDrop事件。

使用包含单个TextBox和两个Buttons的Form中的代码,单击button1会成功将textBox1的文本设置为“Hello world”。所以,似乎我正确使用SendMessage并且能够通过指针提供参数。将文件从Windows资源管理器拖放到textBox1上会显示MessageBox,因此textBox1设置为正确接收拖放文件。但是,当我点击button2时,没有任何反应。单击button2时为什么看不到MessageBox?

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

1 个答案:

答案 0 :(得分:1)

您没有看到MessageBox出现的原因可能是因为TextBox无法处理WM_DROPFILES消息。它使用OLE Drag&amp; Drop实现其drop支持,而不是实现IDropTarget接口(请参阅WPF文档中的Drag and Drop Overview)。

自从{95}在Windows 95中引入DoDragDrop()以来,

WM_DROPFILES已被弃用.LAP Drag&amp; Drop一直是首选在Windows上实现拖放的方法很长一段时间。 Windows仍然支持WM_DROPFILES(但不支持.NET),但仅用于向后兼容旧版应用程序。

将项目从Windows资源管理器拖动到其他应用程序使用OLE Drag&amp; Drop引擎盖,即使接收器没有实现OLE Drag&amp; Drop。

如果您将IDataObject拖放到已调用RegisterDragDrop()的窗口上(就像您的TextBox一样),IDataObject将被传递到窗口的IDropTarget界面进行处理。

如果您将IDataObject拖放到一个未实现IDropTarget的窗口上,但已调用DragAcceptFiles(),或者至少有{{1}如果WS_EX_ACCEPTFILES包含WM_DROPFILES数据,Windows将生成IDataObject消息。

因此,要使用CF_HDROP执行您尝试的操作,您需要实现IDropSourceIDataObject接口,然后使用它们调用DoDragDrop()。让Windows为您处理实际的拖放操作。请参阅Shell Clipboard FormatsHandling Shell Data Transfer Scenarios

现在,话虽如此,如果您仍然决定向另一个进程发送TextBox消息(您的问题是您的最终目标),那么您可以这样做,Windows将整理您的{{1为您划分跨流程边界的结构,但您需要使用WM_DROPFILES而不是DROPFILES(不确定原因,只需要它),并确保仅释放PostMessage()如果SendMessage()失败。如果DROPFILES成功,Windows将为您释放PostMessage()

但即便如此,也无法保证接收进程实际处理DROPFILES个消息(即使它确实如此,您的手动PostMessage()消息可能会被UIPI阻止,除非接收方调用{{ 3}}本身允许WM_DROPFILESWM_DROPFILES消息)。接收者可能期待WM_DROPFILES。如果接收器甚至支持拖放。