WinForm中管理員許可權下獲取拖拽文件路徑的解決方案
今天某個群里有人問為什麼拖拽文件到程序里的操作無效。正好我遇到過這個問題,就翻出來自己以前寫的代碼,改成一個類,算做這個問題的解決方案。
導致拖拽無效的原因可以看這篇文章:Q: Why Doesn』t Drag-and-Drop work when my Application is Running Elevated? - A: Mandatory Integrity Control and UIPI ,其中也給出了解決的方法,但對於WinForm而言不太夠,因為需要處理其他額外的細節。
我已經忘記當初我寫這些代碼時的心路歷程了,所以就不仔細分析,就說關鍵的內容:
- 使程序能夠接收相關消息。
- 對WinForm而言會以自己的方式處理拖拽,所以控制項啟用了AllowDrop屬性,則無法接收WM_DROPFILES消息。
- 同第2條,即使程序可以接收消息,也需要手動處理消息和引發事件。這裡我選擇的時使用Control.DoDragDrop()方法。
其他更多細節見代碼:
public sealed class FileDropHandler : IMessageFilter, IDisposable
{
#region native members
[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);
[DllImport("shell32.dll", SetLastError = false, CallingConvention = CallingConvention.Winapi)]
private static extern void DragAcceptFiles(IntPtr hWnd, bool fAccept);
[DllImport("shell32.dll", SetLastError = false, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
private static extern uint DragQueryFile(IntPtr hWnd, uint iFile, StringBuilder lpszFile, int cch);
[DllImport("shell32.dll", SetLastError = false, CallingConvention = CallingConvention.Winapi)]
private static extern void DragFinish(IntPtr hDrop);
[StructLayout(LayoutKind.Sequential)]
private struct ChangeFilterStruct
{
public uint CbSize;
public ChangeFilterStatu ExtStatus;
}
private enum ChangeFilterAction : uint
{
MSGFLT_RESET,
MSGFLT_ALLOW,
MSGFLT_DISALLOW
}
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 Control ContainerControl { get; }
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 (_DisposeControl && !_ContainerControl.IsDisposed) _ContainerControl.Dispose();
Application.RemoveMessageFilter(this);
_ContainerControl = null;
}
}
}
這個類雖不優雅,但用法很簡單:
public FileDropHandler FileDroper; //全局的
private void Form1_Load(object sender, EventArgs e)
{
FileDroper = new FileDropHandler(label1); //初始化
}
//這是 label1 的DragEnter事件。
private void label1_DragEnter(object sender, DragEventArgs e)
{
var paths = e.Data.GetData(typeof(string[]));
}
這個類是專供WinForm用的,雖然很多人說WinForm過時了,然而我還是喜歡用它寫小工具。這個類很容易就改成WPF版的,這裡不贅述。自己查資料即可,關鍵詞是「消息」。
當然如果你覺得這個類太糙,也可以用複雜的方式處理消息,自己搞一套事件,或者自己實現一個IDataObject類型來存數據……
此代碼基於WTFPL發布。
推薦閱讀: