我想使用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;
}
}
}
答案 0 :(得分:1)
您没有看到MessageBox
出现的原因可能是因为TextBox
无法处理WM_DROPFILES
消息。它使用OLE Drag&amp; Drop实现其drop支持,而不是实现IDropTarget
接口(请参阅WPF文档中的Drag and Drop Overview)。
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
执行您尝试的操作,您需要实现IDropSource
和IDataObject
接口,然后使用它们调用DoDragDrop()
。让Windows为您处理实际的拖放操作。请参阅Shell Clipboard Formats和Handling Shell Data Transfer Scenarios。
现在,话虽如此,如果您仍然决定向另一个进程发送TextBox
消息(您的问题是您的最终目标),那么您可以这样做,Windows将整理您的{{1为您划分跨流程边界的结构,但您需要使用WM_DROPFILES
而不是DROPFILES
(不确定原因,只需要它),并确保仅释放PostMessage()
如果SendMessage()
失败。如果DROPFILES
成功,Windows将为您释放PostMessage()
。
但即便如此,也无法保证接收进程实际处理DROPFILES
个消息(即使它确实如此,您的手动PostMessage()
消息可能会被UIPI阻止,除非接收方调用{{ 3}}本身允许WM_DROPFILES
和WM_DROPFILES
消息)。接收者可能期待WM_DROPFILES
。如果接收器甚至支持拖放。