在 WinForms 應用中使用 `WebBrowser` 控件,希望實現:從桌面拖拽多個文件到控件內,獲取所有文件的完整路徑並顯示在 TextBox 中。但實際運行時,`HtmlElementEventArgs e` 的 `e.Url` 屬性只能獲取到最後一個文件的路徑。
`WebBrowser` 控件基於 IE 內核(MSHTML),原生支持文件拖放導航:
當觸發 `DocumentCompleted` 或 `Navigating` 事件時,前面的文件導航已被後續導航覆蓋,所以 `e.Url` 只能反映最終狀態。
// ❌ 無效:只能捕獲最後一次導航
private void webBrowser1_Navigating(object sender, WebBrowserNavigatingEventArgs e)
{
string url = e.Url.ToString(); // 只能獲取最後一個文件
textBox1.AppendText(url + Environment.NewLine);
}
必須繼承 `WebBrowser` 並重寫 `WndProc`,在 Windows 消息層面攔截文件拖放消息(`WM_DROPFILES = 0x0233`),在控件導航行為發生前提取所有文件路徑。
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class ExtendedWebBrowser : WebBrowser
{
// Windows API 常量
private const int WM_DROPFILES = 0x0233;
// 拖放信息結構
[StructLayout(LayoutKind.Sequential)]
private struct DROPFILES
{
public IntPtr pFiles;
public POINT pt;
public bool fNC;
public bool fWide;
}
[StructLayout(LayoutKind.Sequential)]
private struct POINT
{
public int x;
public int y;
}
// API 導入
[DllImport("shell32.dll", CharSet = CharSet.Auto)]
private static extern uint DragQueryFile(IntPtr hDrop, uint iFile, StringBuilder lpszFile, uint cch);
[DllImport("shell32.dll")]
private static extern void DragFinish(IntPtr hDrop);
[DllImport("kernel32.dll")]
private static extern IntPtr GlobalLock(IntPtr hMem);
[DllImport("kernel32.dll")]
private static extern bool GlobalUnlock(IntPtr hMem);
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_DROPFILES)
{
// 阻止默認導航行為
m.Result = IntPtr.Zero;
// 提取所有文件路徑
IntPtr hDrop = m.WParam;
uint fileCount = DragQueryFile(hDrop, 0xFFFFFFFF, null, 0);
var files = new System.Collections.Generic.List<string>();
var sb = new StringBuilder(260);
for (uint i = 0; i < fileCount; i++)
{
DragQueryFile(hDrop, i, sb, (uint)sb.Capacity);
files.Add(sb.ToString());
sb.Clear();
}
DragFinish(hDrop);
// 觸發自定義事件,傳遞文件列表
OnFilesDropped(new FilesDroppedEventArgs(files));
return; // 關鍵:不調用 base.WndProc,阻止默認導航
}
base.WndProc(ref m);
}
// 自定義事件
public event EventHandler<FilesDroppedEventArgs> FilesDropped;
protected virtual void OnFilesDropped(FilesDroppedEventArgs e)
{
FilesDropped?.Invoke(this, e);
}
}
public class FilesDroppedEventArgs : EventArgs
{
public System.Collections.Generic.List<string> Files { get; }
public FilesDroppedEventArgs(System.Collections.Generic.List<string> files)
{
Files = files;
}
}
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
// 替換設計器中的 WebBrowser 為 ExtendedWebBrowser
// 或在代碼中動態創建
extendedWebBrowser1.FilesDropped += (s, e) =>
{
foreach (var file in e.Files)
{
textBox1.AppendText(file + Environment.NewLine);
}
};
}
}
如果只是為了獲取文件路徑,完全不需要 WebBrowser:
// 簡單方案:在 Form 或 Panel 上啟用拖放
this.AllowDrop = true;
this.DragEnter += (s, e) =>
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
e.Effect = DragDropEffects.Copy;
};
this.DragDrop += (s, e) =>
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
foreach (var file in files)
textBox1.AppendText(file + Environment.NewLine);
};
這才是 WinForms 處理文件拖放的標準、輕量、無副作用方式。
WebBrowser 拖放多文件只能獲取最後一個路徑,是因為其原生導航機制覆蓋了前序導航。 解決途徑有二:
除非必須在 WebBrowser 中渲染文件內容(如預覽 HTML/PDF),否則請選擇方案二。