前言

  最近写的一个小工具因涉及到系统文件的操作,故需要以管理员身份运行,但这影响到了程序的文件拖放功能,即DragDrop事件。而一番搜索后,发现这个问题还是令很多人感到棘手的,而大部分网友给出的解决方法均是效果不佳。很幸运的是我发现了一个很稳的解决方法,这里做个记录。

原因

  不同权限提升级别的程序之间是无法共享拖放消息的,这就导致了例如RequireAdministrator权限提升级别的程序读取不到InvokeAsUser权限提升级别的Windows Explorer中的文件数据的问题。即低权限的进程无法向高权限的进程发送任何高于WM_USER的消息,而低于WM_USER 的消息一部分也会因为安全原因被禁止。故同样级别的权限提升账户运行的程序则可以共享拖放消息。

解决

  综合原因分析,可以得知通常所用的AllowDrop只限于同级别权限的程序之间共享消息。故要解决这个问题则可以使用传统的拖放方法,即WndProc函数中的消息。
  这里需要注意的是,此解决方案无需启用控件的AllowDrop属性。

 public class ElevatedDragDropManager : IMessageFilter
    {

        #region "P/Invoke"
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool ChangeWindowMessageFilterEx(IntPtr hWnd, uint msg, ChangeWindowMessageFilterExAction action, ref CHANGEFILTERSTRUCT changeInfo);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool ChangeWindowMessageFilter(uint msg, ChangeWindowMessageFilterFlags flags);

        [DllImport("shell32.dll")]
        private static extern void DragAcceptFiles(IntPtr hwnd, bool fAccept);

        [DllImport("shell32.dll")]
        private static extern uint DragQueryFile(IntPtr hDrop, uint iFile, [Out()]
        StringBuilder lpszFile, uint cch);

        [DllImport("shell32.dll")]
        private static extern bool DragQueryPoint(IntPtr hDrop, ref POINT lppt);

        [DllImport("shell32.dll")]
        private static extern void DragFinish(IntPtr hDrop);

        [StructLayout(LayoutKind.Sequential)]
        private struct POINT
        {
            public int X;

            public int Y;
            public POINT(int newX, int newY)
            {
                X = newX;
                Y = newY;
            }

            public static implicit operator System.Drawing.Point(POINT p)
            {
                return new System.Drawing.Point(p.X, p.Y);
            }

            public static implicit operator POINT(System.Drawing.Point p)
            {
                return new POINT(p.X, p.Y);
            }
        }

        private enum MessageFilterInfo : uint
        {
            None,
            AlreadyAllowed,
            AlreadyDisAllowed,
            AllowedHigher
        }

        private enum ChangeWindowMessageFilterExAction : uint
        {
            Reset,
            Allow,
            Disallow
        }

        private enum ChangeWindowMessageFilterFlags : uint
        {
            Add = 1,
            Remove = 2
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct CHANGEFILTERSTRUCT
        {
            public uint cbSize;
            public MessageFilterInfo ExtStatus;
        }
        #endregion

        public static ElevatedDragDropManager Instance = new ElevatedDragDropManager();
        public event EventHandler<ElevatedDragDropArgs> ElevatedDragDrop;

        private const uint WM_DROPFILES = 0x233;
        private const uint WM_COPYDATA = 0x4a;

        private const uint WM_COPYGLOBALDATA = 0x49;
        private readonly bool IsVistaOrHigher = Environment.OSVersion.Version.Major >= 6;

        private readonly bool Is7OrHigher = (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor >= 1) || Environment.OSVersion.Version.Major > 6;
        protected ElevatedDragDropManager()
        {
            Application.AddMessageFilter(this);
        }

        public void EnableDragDrop(IntPtr hWnd)
        {
            if (Is7OrHigher)
            {
                CHANGEFILTERSTRUCT changeStruct = new CHANGEFILTERSTRUCT();
                changeStruct.cbSize = Convert.ToUInt32(Marshal.SizeOf(typeof(CHANGEFILTERSTRUCT)));
                ChangeWindowMessageFilterEx(hWnd, WM_DROPFILES, ChangeWindowMessageFilterExAction.Allow, ref changeStruct);
                ChangeWindowMessageFilterEx(hWnd, WM_COPYDATA, ChangeWindowMessageFilterExAction.Allow, ref changeStruct);
                ChangeWindowMessageFilterEx(hWnd, WM_COPYGLOBALDATA, ChangeWindowMessageFilterExAction.Allow, ref changeStruct);
            }
            else if (IsVistaOrHigher)
            {
                ChangeWindowMessageFilter(WM_DROPFILES, ChangeWindowMessageFilterFlags.Add);
                ChangeWindowMessageFilter(WM_COPYDATA, ChangeWindowMessageFilterFlags.Add);
                ChangeWindowMessageFilter(WM_COPYGLOBALDATA, ChangeWindowMessageFilterFlags.Add);
            }

            DragAcceptFiles(hWnd, true);
        }

        public bool PreFilterMessage(ref Message m)
        {
            if (m.Msg == WM_DROPFILES)
            {
                HandleDragDropMessage(m);
                return true;
            }

            return false;
        }

        private void HandleDragDropMessage(Message m)
        {
            dynamic sb = new StringBuilder(260);
            uint numFiles = DragQueryFile(m.WParam, 0xffffffffu, sb, 0);
            dynamic list = new List<string>();

            for (uint i = 0; i <= numFiles - 1; i++)
            {
                if (DragQueryFile(m.WParam, i, sb, Convert.ToUInt32(sb.Capacity) * 2) > 0)
                {
                    list.Add(sb.ToString());
                }
            }

            POINT p = default(POINT);
            DragQueryPoint(m.WParam, ref p);
            DragFinish(m.WParam);

            dynamic args = new ElevatedDragDropArgs();
            args.HWnd = m.HWnd;
            args.Files = list;
            args.X = p.X;
            args.Y = p.Y;

            if (ElevatedDragDrop != null)
            {
                ElevatedDragDrop(this, args);
            }
        }
    }


    public class ElevatedDragDropArgs : EventArgs
    {
        public IntPtr HWnd
        {
            get { return m_HWnd; }
            set { m_HWnd = value; }
        }
        private IntPtr m_HWnd;
        public List<string> Files
        {
            get { return m_Files; }
            set { m_Files = value; }
        }
        private List<string> m_Files;
        public int X
        {
            get { return m_X; }
            set { m_X = value; }
        }
        private int m_X;
        public int Y
        {
            get { return m_Y; }
            set { m_Y = value; }
        }

        private int m_Y;
        public ElevatedDragDropArgs()
        {
            Files = new List<string>();
        }
    }

使用示例

   private void Form1_ElevatedDragDrop(System.Object sender, ElevatedDragDropArgs e)
   {
        // Add the files to listview
        if (e.HWnd == listView1.Handle)
        {
            foreach (string file in e.Files)
            {
                listView1.Items.Add(file);
            }
        }
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        ElevatedDragDropManager.Instance.EnableDragDrop(listView1.Handle);
        // Enable elevated drag drop on listView1. Note that I used the Handle property
        ElevatedDragDropManager.Instance.ElevatedDragDrop += Form1_ElevatedDragDrop;
    }
如果觉得我的文章对您有用,请我喝一杯咖啡吧~