【问题标题】:The Win32 API procedure of drag-and-dropping a file from my app to another app将文件从我的应用程序拖放到另一个应用程序的 Win32 API 过程
【发布时间】:2019-09-05 04:57:48
【问题描述】:

简而言之,尝试在不使用System.Windows.Forms 的情况下执行以下两行 C# 代码所做的事情,因为它是 .NET Core 而不是 WinForms 项目。

var data = new System.Windows.Forms.DataObject(
    System.Windows.Forms.DataFormats.FileDrop, new string[] { @"C:\test.txt"});
dummyControl.DoDragDrop(data, System.Windows.Forms.DragDropEffects.Copy);

但它不起作用。我做错了什么?我的程序是,

  1. 当鼠标在 UI 控件上按下时,调用 SetCapture 来捕获事件。
  2. IDropSourceQueryContinueDrag 不断返回S_OK,直到鼠标按钮向上,然后DRAGDROP_S_DROP
  3. 我的“DataObject”实现IDataObjectEnumFormatEtc。其中我只返回一个FORMATETC

        new FORMATETC()
        {
            cfFormat = CF_HDROP,
            ptd = IntPtr.Zero,
            dwAspect = DVASPECT.DVASPECT_ICON,
            lindex = -1,
            tymed = TYMED.TYMED_FILE
        }
    
    1. QueryGetData中,如果formattymedTYMED_FILE,则返回S_OK,表示我正在拖动文件。否则返回DV_E_TYMED 表示我没有那种类型的数据。

    2. GetData中,如果formattymedTYMED_FILE,我设置一个这样的文件

          medium = new STGMEDIUM();
          medium.tymed = TYMED.TYMED_FILE;
          medium.unionmember = Marshal.StringToHGlobalUni(@"C:\test.txt");
          medium.pUnkForRelease = IntPtr.Zero;
      

我尝试拖放到记事本。问题是,

  1. 如果没有在鼠标按下事件上调用DoDragDrop,则会捕获鼠标并接收鼠标向上事件。但是当DoDragDrop 被调用时,QueryContinueDrag 被无休止地调用并且我没有得到鼠标向上事件,即使我释放了鼠标。我尝试在另一个线程中调用DoDragDrop,但没有成功。
  2. 我收到类似QueryGetData(TYMED_HGLOBAL)QueryGetData(TYMED_HGLOBAL, TYMED_ISTREAM, TYMED_GDI, TYMED_MFPICT, TYMED_ENHMF) 的回调,但不是TYMED_FILE。为什么记事本不要求这样做?

拖放似乎不必要地复杂,但目前我只对拖动文件感兴趣,所以我不想实现除此之外的部分。上述程序或假设有什么错误?

【问题讨论】:

  • 当然,您更喜欢更新到 .NETCore v3.0,无需 hack。
  • 我知道 .NET Core 3.0 带有 WinForms,但在这种情况下,我尝试使用 GTK# 来实现跨平台性。但是,不幸的是,GTK# 似乎不支持 Windows 文件拖放(GTK# 应用程序到 Win32 应用程序)。我想我甚至可以通过创建一个虚拟Control 在 GTK# 应用程序中使用 WinForms 的 DoDragDrop,但这样做可能会加载不必要的 DLL,所以我认为直接使用 Win32 API 会更有效。只是它比我预期的要复杂得多。
  • 这很勇敢。 Mono 团队尝试过,但由于持续存在的兼容性问题,他们放弃了。你有一些;)DataObject class 完成了真正的工作。

标签: c# winapi .net-core pinvoke


【解决方案1】:

当鼠标按下事件未调用 DoDragDrop 时,会捕获鼠标并接收鼠标向上事件。但是当 DoDragDrop 被调用时,QueryContinueDrag 被无休止地调用,我没有得到鼠标向上事件,即使我释放了鼠标。

您不会收到鼠标向上事件,因为DoDragDrop() 会阻止您的 UI 消息循环,直到拖动操作完成。因此,您需要使用为 QueryContinueDrag() 实现提供的输入标志来决定是继续拖动、执行拖放还是中止操作。

如果您开始用鼠标左键向下拖动,如果grfKeyState 参数包含MK_LBUTTON 标志,则返回S_OK,如果MK_LBUTTON 标志被清除,则返回DRAGDROP_S_DROP。如果fEscapePressed 参数为真,则返回DRAGDROP_S_CANCEL。这记录在 MSDN 上:

IDropSource::QueryContinueDrag method

参数

fEscapePressed

表示自上次调用 QueryContinueDragDoDragDrop 后是否按下了 Esc 键(如果这是第一次调用 QueryContinueDragTRUE 值表示最终用户按下了退出键; FALSE 值表示尚未按下。

grfKeyState

键盘上键盘修饰键的当前状态。可能的值可以是任何标志 MK_CONTROL、MK_SHIFT、MK_ALT、MK_BUTTON、MK_LBUTTON、 MK_MBUTTON 和 MK_RBUTTON。

返回值

此方法可以返回以下值。

S_OK
拖动操作应继续。如果未检测到错误,开始拖放操作的鼠标按钮尚未释放,并且未检测到 Esc 键,则会出现此结果。

DRAGDROP_S_DROP
拖放操作应完成拖动操作。 如果grfKeyState 表示启动拖放操作的键已被释放,则会出现此结果。

DRAGDROP_S_CANCEL
应取消拖动操作,而不会发生拖放操作。 如果fEscapePressed 为 TRUE,则会出现此结果,表示已按下 Esc 键。

备注

DoDragDrop 函数在拖放操作期间只要检测到键盘或鼠标按钮状态发生变化,就会调用 QueryContinueDragQueryContinueDrag 必须根据参数grfKeyStatefEscapePressed 的内容来决定是继续、取消还是完成拖放操作。


我收到 QueryGetData(TYMED_HGLOBAL) 或 QueryGetData(TYMED_HGLOBAL, TYMED_ISTREAM, TYMED_GDI, TYMED_MFPICT, TYMED_ENHMF) 之类的回调,但 TYMED_FILE 没有。为什么记事本不要求这样做?

您不能将TYMED_FILE 用于CF_HDROP,您必须使用TYMED_HGLOBAL。并且分配的HGLOBAL 的内容必须是一个DROPFILES 结构,后跟一个以双空结尾的文件路径列表。这记录在 MSDN 上:

Shell Clipboard Formats

CF_HDROP

在传输一组现有文件的位置时使用此剪贴板格式。与其他 Shell 格式不同,它是预定义的,因此无需调用RegisterClipboardFormat数据由包含全局内存对象的STGMEDIUM 结构组成。结构的hGlobal 成员指向DROPFILES 结构作为其hGlobal 成员。

DROPFILES 结构的 pFiles 成员包含一个以双空结尾的字符数组的偏移量,该数组包含文件名。如果您要从数据中提取 CF_HDROP 格式对象,您可以使用 DragQueryFile 从全局内存对象中提取单个文件名。 如果您要创建CF_HDROP 格式以放置在数据对象中,则需要构造文件名数组。

文件名数组由一系列字符串组成,每个字符串包含一个文件的完全限定路径,包括终止 NULL 字符。一个额外的空字符附加到最后的字符串以终止数组。例如,如果正在传输文件 c:\temp1.txtc:\temp2.txt,则字符数组如下所示:

c:\temp1.txt'\0'c:\temp2.txt'\0''\0'

注意
在此示例中,'\0' 用于表示空字符,而不是应包含的文字字符。

如果对象作为拖放操作的一部分复制到剪贴板,则DROPFILES 结构的pt 成员包含对象被拖放点的坐标。可以使用DragQueryPoint提取光标坐标。

如果此格式存在于数据对象中,则 OLE 拖动循环会模拟具有非 OLE 放置目标的 WM_DROPFILES 功能。如果您的应用程序是 Windows 3.1 系统上拖放操作的来源,这一点很重要。

【讨论】:

  • 谢谢。对于问题 1,我浏览了文档并认为它是关于键盘的,因此不需要。该论点的描述有点棘手...对于2,在看到您的答案后,我在Google 中搜索了C# 中的示例DROPFILES 并找到了existing StackOverflow question。使用该人的代码,我终于让文件拖放工作。现在,唯一的问题是鼠标指针是“无符号”?,而不是带加号的矩形。
  • 我得到了复制指针。原来我也负责更改鼠标指针。在GiveFeedback 回调中,我使用Win32 API 调用设置了复制指针(因为.NET 的现有游标代码在Windows.Forms 中)。 This existing answer 展示了如何从“ole32.dll”加载复制光标。我希望有一个独立的库(不是 WinForms、WPF 等)为 Win32 GUI API 提供包装器......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-08
  • 2011-11-23
  • 2010-12-14
  • 2012-07-08
相关资源
最近更新 更多