WPF 实现文件、图标拖放功能(支持UAC的那种)

WPF实现文件拖放功能,正常情况并没有什么问题,但是如果你的程序使用管理员身份启动,你就会发现文件拖放功能就会失效。

这是因为WPF 在不同UAC等级下,是不允许拖放的。

原理很简单,与桌面相关联的进程为 explorer.exe,即 explorer.exe 这个进程启动的方式是非管理员身份,当你的程序使用管理员身份启动时,就会导致拖放失败。

因为二者的权限不一样,系统不允许不同权限的进程进行通讯,包括进程通讯等操作。

解决方案:

方案一(不推荐):让 explorer.exe 也使用管理员身份启动。举例Win7系统只需这样设置下并重启系统即可,如下图:

这种方案能解决问题,但是你不可能让用户去做这种操作,所以只能算一个解决方案,并不能解决实际的问题。


方案二:让你的程序使用非管理员启动,程序中需要管理员身份的操作,一般为涉及到注册表操作或驱动操作,可以考虑将这部分操作放到一个服务里单独操作,可以理解为程序分成服务与应用程序两块,需要管理员身份操作的

功能部分放到服务里实现,界面相关的操作在应用程序里实现。

这种方案也能解决,并且问题解决的比较彻底,但是项目工程量比较大的情况下,工作量就比较大了,为一个文件拖放的功能,增加了较大的工作量,得不偿失。

 

方案三:提供一个折中的办法,WPF经过我较长的上网搜索及研究,没有找到合适的办法解决这个问题,但是 WinForm 通过消息Hook却能实现,所以这个折中的办法就是WPF+WinForm来解决这个问题。

 

下面我们将主要讲解如何使用 WPF+WinForm 解决WPF程序使用管理员身份启动后不能拖放文件的问题。

 

第一部分:使用 WinForm 解决使用不能拖动的问题,关键代码如下

ElevatedDragDropManager.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Windows;

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;

    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>();
    }
}

Form1.cs

注:需要将Form1窗口的AllowDrop属性设置为false,否则无法拖动文件。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace FileDragDrop
{
    public partial class FileDragDrop : Form
    {
        public FileDragDrop()
        {
            InitializeComponent();
            //this.AllowDrop设置为false
            this.AllowDrop = false;
            ElevatedDragDropManager filter = new ElevatedDragDropManager();
            //开启拖放功能
            filter.EnableDragDrop(this.Handle);
            //添加消息过滤器
            Application.AddMessageFilter(filter);
            //设置拖放结束回调
            filter.ElevatedDragDrop += this.ElevatedDragDrop;
        }

        //拖放结束事件
        private void ElevatedDragDrop(System.Object sender, ElevatedDragDropArgs e)
        {
            try
            {
                if (e.HWnd == this.Handle)
                {
                    foreach (string file in e.Files)
                    {
                        //拖动文件
                        MessageBox.Show("ElevatedDragDrop File=" + (file) + "!");
                    }
                }
            }
            catch (Exception ex)
            {
                //异常信息
                MessageBox.Show("ElevatedDragDrop error=" + (ex.TargetSite?.Name) + "!");
            }
        }
    }
}

最终的效果:

WinForm项目代码链接:https://github.com/zhaobangyu/C-SHAP/tree/WinForm
 
第二部分:WPF+WinForm的组合使用
 
效果图:

WPF+WinForm项目代码链接:https://github.com/zhaobangyu/C-SHAP/tree/WPF/FileDragDrop

posted @ 2021-01-11 14:28  独一无二~  阅读(368)  评论(0编辑  收藏  举报