【问题标题】:How can I send WM_DROPFILES from C#?如何从 C# 发送 WM_DROPFILES?
【发布时间】:2017-12-05 00:03:57
【问题描述】:

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

在包含一个 TextBox 和两个 Button 的 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;
        }
    }
}

【问题讨论】:

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


    【解决方案1】:

    您看不到MessageBox 的原因可能是因为TextBox 不处理WM_DROPFILES 消息。它使用 OLE Drag&Drop 来实现其放置支持,而不是通过实现 IDropTarget 接口(请参阅 WPF 文档中的 Drag and Drop Overview)。

    WM_DROPFILES 自从 DoDragDrop() 早在 Windows 95 中引入以来就已被弃用。OLE Drag&Drop 长期以来一直是在 Windows 上实现拖放的首选方式。 WM_DROPFILES 仍受 Windows 支持(但不支持 .NET),但只是为了向后兼容旧版应用程序。

    将项目从 Windows 资源管理器拖动到其他应用程序在后台使用 OLE 拖放,即使接收器没有实现 OLE 拖放。

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

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

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

    现在,话虽如此,如果您仍然决定将 WM_DROPFILES 消息发送到另一个进程(您的问题说这是您的最终目标),您可以这样做,Windows 将编组您的 DROPFILES 结构为您提供进程边界,但您需要使用PostMessage() 而不是SendMessage()(不知道为什么,只是它是必需的),并且确保只有在PostMessage() 失败时才释放DROPFILES。如果PostMessage() 成功,Windows 将为您释放DROPFILES

    但即便如此,也无法保证接收进程确实处理了WM_DROPFILES 消息(即使处理了,您的手动WM_DROPFILES 消息也可能会被UIPI 阻止,除非接收者自己调用ChangeWindowMessageFilter/Ex()允许WM_DROPFILESWM_COPYGLOBALDATA 消息)。接收者可能会期待IDataObject。如果接收器甚至完全支持拖放。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-01-08
      • 1970-01-01
      • 2012-02-21
      • 1970-01-01
      • 2011-08-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多