解决C#拖放文件无效的问题

我们可以向ArcMap窗口中直接拖放mxd工程文件、shp矢量文件 、tif栅格文件等,十分方便;使用控件的DragEnter和DragDrop即可实现拖放操作,但在Win7和Win10系统中,如果程序以管理员运行,则实现的拖动操作无效。

原因分析

Windows消息是一种进程间通信机制,为了防止较低等级的进程窗口向较等级进程窗口发送消息,Windows引用了用户界面特权隔离(简称UIPI)机制。正是由于这种机制,导致了如果以管理员运行应用程序,拖放操作就会被系统过滤而无效。

解决方案

采用 ChangeWindowMessageFilterEx 函数,为指定窗口修改用户界面特权隔离 (UIPI) 消息过滤器。

先使用Windows API实现如下类:

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

namespace Frame.Utility.Handler
{
    public sealed class FileDropHandler : IMessageFilter, IDisposable
    {

        #region API函数

        /// <summary>
        /// 指定窗口修改用户界面特权隔离 (UIPI) 消息过滤器
        /// </summary>
        /// <param name="hWnd">窗口句柄</param>
        /// <param name="message">允许或阻止的消息</param>
        /// <param name="action">要执行的操作</param>
        /// <param name="pChangeFilterStruct"></param>
        /// <returns></returns>
        [DllImport("user32.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool ChangeWindowMessageFilterEx(IntPtr hWnd, uint message, ChangeFilterAction action, in ChangeFilterStruct pChangeFilterStruct);

        /// <summary>
        /// 窗口是否接受从shell拖放过来的文件
        /// </summary>
        /// <param name="hWnd">窗口句柄</param>
        /// <param name="fAccept">true接收拖拽,false拒绝拖拽</param>
        [DllImport("shell32.dll", SetLastError = false, CallingConvention = CallingConvention.Winapi)]
        private static extern void DragAcceptFiles(IntPtr hWnd, bool fAccept);

        /// <summary>
        /// 成功拖放操作后获取被拖放文件的名称等信息
        /// </summary>
        /// <param name="hWnd">句柄</param>
        /// <param name="iFile">文件索引编号</param>
        /// <param name="lpszFile">存放函数返回的文件路径</param>
        /// <param name="cch">缓冲区的字符数</param>
        /// <returns></returns>
        [DllImport("shell32.dll", SetLastError = false, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        private static extern uint DragQueryFile(IntPtr hWnd, uint iFile, StringBuilder lpszFile, int cch);

        /// <summary>
        /// 释放shell为传递文件名而开辟的内存空间
        /// </summary>
        /// <param name="hDrop">窗口句柄</param>
        [DllImport("shell32.dll", SetLastError = false, CallingConvention = CallingConvention.Winapi)]
        private static extern void DragFinish(IntPtr hDrop);

        [StructLayout(LayoutKind.Sequential)]
        private struct ChangeFilterStruct
        {
            public uint CbSize;
            private readonly ChangeFilterStatu ExtStatus;
        }

        private enum ChangeFilterAction : uint
        {
            /// <summary>
            /// 允许消息通过过滤器(包括来自低特权的进程)
            /// </summary>
            MSGFLT_ALLOW,

            /// <summary>
            /// 如果消息来自低特权的进程,阻止它转递给窗口
            /// </summary>
            MSGFLT_DISALLOW,

            /// <summary>
            /// 为窗口重置消息过滤器为默认
            /// </summary>
            MSGFLT_RESET

        }

        private enum ChangeFilterStatu : uint
        {
            MSGFLTINFO_NONE,
            MSGFLTINFO_ALREADYALLOWED_FORWND,
            MSGFLTINFO_ALREADYDISALLOWED_FORWND,
            MSGFLTINFO_ALLOWED_HIGHER
        }

        private const uint WM_COPYGLOBALDATA = 0x0049;
        private const uint WM_COPYDATA = 0x004A;
        private const uint WM_DROPFILES = 0x0233;

        #endregion


        private const uint GetIndexCount = 0xFFFFFFFFU;

        private Control _containerControl;

        private readonly bool _disposeControl;

        public FileDropHandler(Control containerControl) : this(containerControl, false) { }

        public FileDropHandler(Control containerControl, bool releaseControl)
        {
            _containerControl = containerControl ?? throw new ArgumentNullException("control", "control is null.");

            if (containerControl.IsDisposed) throw new ObjectDisposedException("control");

            _disposeControl = releaseControl;

            var status = new ChangeFilterStruct() { CbSize = 8 };

            if (!ChangeWindowMessageFilterEx(containerControl.Handle, WM_DROPFILES, ChangeFilterAction.MSGFLT_ALLOW, in status)) throw new Win32Exception(Marshal.GetLastWin32Error());
            if (!ChangeWindowMessageFilterEx(containerControl.Handle, WM_COPYGLOBALDATA, ChangeFilterAction.MSGFLT_ALLOW, in status)) throw new Win32Exception(Marshal.GetLastWin32Error());
            if (!ChangeWindowMessageFilterEx(containerControl.Handle, WM_COPYDATA, ChangeFilterAction.MSGFLT_ALLOW, in status)) throw new Win32Exception(Marshal.GetLastWin32Error());
            DragAcceptFiles(containerControl.Handle, true);

            Application.AddMessageFilter(this);
        }

        public bool PreFilterMessage(ref Message m)
        {
            if (_containerControl == null || _containerControl.IsDisposed) return false;
            if (_containerControl.AllowDrop) return _containerControl.AllowDrop = false;
            if (m.Msg == WM_DROPFILES)
            {
                var handle = m.WParam;

                var fileCount = DragQueryFile(handle, GetIndexCount, null, 0);

                var fileNames = new string[fileCount];

                var sb = new StringBuilder(262);
                var charLength = sb.Capacity;
                for (uint i = 0; i < fileCount; i++)
                {
                    if (DragQueryFile(handle, i, sb, charLength) > 0) fileNames[i] = sb.ToString();
                }
                DragFinish(handle);
                _containerControl.AllowDrop = true;
                _containerControl.DoDragDrop(fileNames, DragDropEffects.All);
                _containerControl.AllowDrop = false;
                return true;
            }
            return false;
        }

        public void Dispose()
        {
            if (_containerControl == null)
            {
                if (_containerControl != null && _disposeControl && !_containerControl.IsDisposed) _containerControl.Dispose();
                Application.RemoveMessageFilter(this);
                _containerControl = null;
            }
        }
    }
}

在窗体中新建全局变量:

public FileDropHandler FileDropHandler;

在窗体的Load函数中初始化需要拖放的控件:

FileDropHandler = new FileDropHandler(gridData);
posted @ 2021-11-20 10:36  我也是个傻瓜  阅读(898)  评论(0编辑  收藏  举报