我希望使用C#代码来模拟用户在单独的进程中将文件拖放到控件上。作为实现这一目标的垫脚石,我试图向我自己的WM_DROPFILES发送一条TextBox消息,并验证DragDrop事件是否被触发。
下面的代码包含一个TextBox和两个按钮,单击button1成功地将textBox1的文本设置为"Hello“。因此,我似乎正确地使用了SendMessage,并且能够通过指针提供参数。将文件从Windows拖放到textBox1上将显示MessageBox,因此textBox1被设置为正确接收拖放文件。但是,当我单击button2时,什么都不会发生。为什么我点击MessageBox时看不到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;
}
}
}
发布于 2017-12-05 03:09:58
您没有看到MessageBox
出现的原因很可能是因为TextBox
一开始就不处理WM_DROPFILES
消息。它通过实现IDropTarget
接口(参见WPF文档中的拖放概述 ),使用OLE拖放来实现其drop支持。
自从Windows 95中引入WM_DROPFILES
以来,DoDragDrop()
就一直受到人们的反对。OLE拖放长期以来一直是Windows上实现拖放的首选方法。Windows仍然支持WM_DROPFILES
(但不支持.NET),但只支持与遗留应用程序的向后兼容性。
将项目从Windows拖到其他应用程序使用OLE拖放到引擎盖下,即使接收方没有实现OLE拖放。
如果您将一个IDataObject
拖放到已经调用了RegisterDragDrop()
的窗口上(就像您的TextBox
一样),那么IDataObject
将被传递给窗口的IDropTarget
接口进行处理。
如果您将一个IDataObject
拖放到一个没有实现IDropTarget
但已经调用了DragAcceptFiles()
的窗口上,或者至少具有WS_EX_ACCEPTFILES
窗口样式,那么如果IDataObject
中包含CF_HDROP
数据,Windows将生成一个WM_DROPFILES
消息。
因此,要执行您正在尝试使用TextBox
的操作,您需要实现IDropSource
和IDataObject
接口,然后使用它们调用DoDragDrop()
。让Windows为您处理实际的拖放操作。见Shell剪贴板格式和处理Shell数据传输方案。
现在,尽管如此,如果您仍然决心向另一个进程发送WM_DROPFILES
消息(您的问题说这是您的最终目标),那么您可以这样做,并且Windows将为您跨进程边界封送您的DROPFILES
结构,但是您需要使用PostMessage()
而不是SendMessage()
(不确定原因,只知道它是必需的),并且确保只有当PostMessage()
失败时才释放DROPFILES
。如果DROPFILES
成功,Windows将为您释放PostMessage()
。
但即便如此,也不能保证接收过程实际处理WM_DROPFILES
消息(即使处理了,手动WM_DROPFILES
消息也可能被UIPI阻塞,除非接收方本身调用ChangeWindowMessageFilter/Ex()
以允许WM_DROPFILES
和WM_COPYGLOBALDATA
消息)。接收方可能会期望使用IDataObject
。如果接收器甚至支持拖放。
https://stackoverflow.com/questions/47644004
复制相似问题