如何从 C# 发送 WM_DROPFILES?
How can I send WM_DROPFILES from C#?
我想使用 C# 代码在单独的进程中模拟用户将文件拖放到控件上。作为实现此目标的垫脚石,我正在尝试向我自己的 TextBox 发送 WM_DROPFILES 消息并验证是否触发了 DragDrop 事件。
在包含一个文本框和两个按钮的表单中使用以下代码,单击按钮 1 成功将文本框 1 的文本设置为 "Hello world"。因此,我似乎正确地使用了 SendMessage 并且能够通过指针提供参数。将文件从 Windows Explorer 拖放到 textBox1 上会显示 MessageBox,因此 textBox1 已设置为可以正确接收拖放的文件。但是,当我单击 button2 时,没有任何反应。为什么我在单击 button2 时看不到 MessageBox?
using System;
using System.Data;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace Whosebug
{
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[=11=]";
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" + "[=11=][=11=]";
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;
}
}
}
您看不到 MessageBox
的原因可能是因为 TextBox
不处理 WM_DROPFILES
消息。它通过在 WPF 文档中实现 IDropTarget
interface (see Drag and Drop Overview 而不是使用 OLE 拖放来实现拖放支持。
WM_DROPFILES
自从 Windows 95 引入 DoDragDrop()
以来就已被弃用。OLE 拖放一直是 首选 方式在 Windows 上实现拖放很长时间了。 WM_DROPFILES
仍受 Windows 支持(但不支持 .NET),但只是为了与旧版应用程序向后兼容。
将项目从 Windows Explorer 拖到其他应用程序在后台使用 OLE 拖放,即使接收方没有实现 OLE 拖放也是如此。
如果您将 IDataObject
拖放到 RegisterDragDrop()
调用过的 window 上(就像您的 TextBox
一样),IDataObject
将传递给window的IDropTarget
接口进行处理。
如果您将 IDataObject
拖放到未实现 IDropTarget
但已调用 DragAcceptFiles()
的 window 上,或者至少具有 WS_EX_ACCEPTFILES
window 样式,如果 IDataObject
包含 CF_HDROP
数据,Windows 将生成一条 WM_DROPFILES
消息。
因此,要使用 TextBox
执行您尝试的操作,您需要实施 IDropSource
和 IDataObject
接口,然后用它们调用DoDragDrop()
。让 Windows 为您处理实际的拖放操作。请参阅Shell 剪贴板格式 和 处理Shell 数据传输方案。
现在,话虽如此,如果您仍然决定向另一个进程发送 WM_DROPFILES
消息(您的问题说这是您的最终目标),您可以这样做,并且 Windows将为您跨进程边界编组您的 DROPFILES
结构,但您需要使用 PostMessage()
而不是 SendMessage()
(不知道为什么,只是它是必需的),并确保释放DROPFILES
仅当 PostMessage()
失败时。如果 PostMessage()
成功,Windows 将为您释放 DROPFILES
。
但即便如此,也无法保证接收进程实际处理 WM_DROPFILES
消息(即使处理了,您的手动 WM_DROPFILES
消息也可能会被 UIPI 阻止,除非接收方调用ChangeWindowMessageFilter/Ex()
本身允许 WM_DROPFILES
和 WM_COPYGLOBALDATA
消息)。接收者可能期望 IDataObject
相反。如果接收器甚至完全支持拖放。
我想使用 C# 代码在单独的进程中模拟用户将文件拖放到控件上。作为实现此目标的垫脚石,我正在尝试向我自己的 TextBox 发送 WM_DROPFILES 消息并验证是否触发了 DragDrop 事件。
在包含一个文本框和两个按钮的表单中使用以下代码,单击按钮 1 成功将文本框 1 的文本设置为 "Hello world"。因此,我似乎正确地使用了 SendMessage 并且能够通过指针提供参数。将文件从 Windows Explorer 拖放到 textBox1 上会显示 MessageBox,因此 textBox1 已设置为可以正确接收拖放的文件。但是,当我单击 button2 时,没有任何反应。为什么我在单击 button2 时看不到 MessageBox?
using System;
using System.Data;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace Whosebug
{
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[=11=]";
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" + "[=11=][=11=]";
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;
}
}
}
您看不到 MessageBox
的原因可能是因为 TextBox
不处理 WM_DROPFILES
消息。它通过在 WPF 文档中实现 IDropTarget
interface (see Drag and Drop Overview 而不是使用 OLE 拖放来实现拖放支持。
WM_DROPFILES
自从 Windows 95 引入 DoDragDrop()
以来就已被弃用。OLE 拖放一直是 首选 方式在 Windows 上实现拖放很长时间了。 WM_DROPFILES
仍受 Windows 支持(但不支持 .NET),但只是为了与旧版应用程序向后兼容。
将项目从 Windows Explorer 拖到其他应用程序在后台使用 OLE 拖放,即使接收方没有实现 OLE 拖放也是如此。
如果您将 IDataObject
拖放到 RegisterDragDrop()
调用过的 window 上(就像您的 TextBox
一样),IDataObject
将传递给window的IDropTarget
接口进行处理。
如果您将 IDataObject
拖放到未实现 IDropTarget
但已调用 DragAcceptFiles()
的 window 上,或者至少具有 WS_EX_ACCEPTFILES
window 样式,如果 IDataObject
包含 CF_HDROP
数据,Windows 将生成一条 WM_DROPFILES
消息。
因此,要使用 TextBox
执行您尝试的操作,您需要实施 IDropSource
和 IDataObject
接口,然后用它们调用DoDragDrop()
。让 Windows 为您处理实际的拖放操作。请参阅Shell 剪贴板格式 和 处理Shell 数据传输方案。
现在,话虽如此,如果您仍然决定向另一个进程发送 WM_DROPFILES
消息(您的问题说这是您的最终目标),您可以这样做,并且 Windows将为您跨进程边界编组您的 DROPFILES
结构,但您需要使用 PostMessage()
而不是 SendMessage()
(不知道为什么,只是它是必需的),并确保释放DROPFILES
仅当 PostMessage()
失败时。如果 PostMessage()
成功,Windows 将为您释放 DROPFILES
。
但即便如此,也无法保证接收进程实际处理 WM_DROPFILES
消息(即使处理了,您的手动 WM_DROPFILES
消息也可能会被 UIPI 阻止,除非接收方调用ChangeWindowMessageFilter/Ex()
本身允许 WM_DROPFILES
和 WM_COPYGLOBALDATA
消息)。接收者可能期望 IDataObject
相反。如果接收器甚至完全支持拖放。