如何自定义CefSharp 63(WinForms)本地目录列表显示样式?
如何在CefSharp 63 (WinForms)中自定义本地文件夹目录的显示模板
刚好之前在CefSharp 63里折腾过类似的需求——默认的file://目录渲染确实简陋,而且你遇到的占位符未填充问题,大概率是默认模板加载异常导致的。不过咱们完全可以绕开它,核心思路就是拦截file://开头的目录请求,禁用默认处理逻辑,返回咱们自己生成的自定义HTML内容,具体步骤拆解如下:
1. 实现自定义请求处理器(IRequestHandler)
首先要创建一个IRequestHandler的实现类,用来识别并拦截本地目录的请求:
using CefSharp; using System.IO; public class CustomDirectoryRequestHandler : IRequestHandler { public IResourceRequestHandler GetResourceRequestHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling) { // 只处理file://协议的请求 if (request.Url.StartsWith("file:///")) { var localPath = new Uri(request.Url).LocalPath; // 判断请求路径是否是一个目录 if (Directory.Exists(localPath)) { disableDefaultHandling = true; // 告诉CefSharp不要用默认方式处理这个请求 return new CustomDirectoryResourceHandler(localPath); } } return null; // 非目录请求交给默认处理 } // 以下是接口的其他默认实现,直接照搬即可 public bool OnBeforeBrowse(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, bool isRedirect) => false; public bool OnOpenUrlFromTab(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture) => false; public void OnRenderProcessTerminated(IWebBrowser chromiumWebBrowser, IBrowser browser, CefTerminationStatus status) { } public bool OnCertificateError(IWebBrowser chromiumWebBrowser, IBrowser browser, CefErrorCode errorCode, string requestUrl, ISslInfo sslInfo, IRequestCallback callback) => false; public void OnPluginCrashed(IWebBrowser chromiumWebBrowser, IBrowser browser, string pluginPath) { } public bool OnAuthRequired(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback) => false; public bool OnQuotaRequest(IWebBrowser chromiumWebBrowser, IBrowser browser, string originUrl, long newSize, IRequestCallback callback) => false; public void OnResourceRedirect(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, IResponse response, ref string newUrl) { } public bool OnProtocolExecution(IWebBrowser chromiumWebBrowser, IBrowser browser, string url) => false; public bool OnBeforeResourceLoad(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback) => false; }
2. 实现自定义资源处理器(ResourceHandler)
接下来创建ResourceHandler的子类,负责读取目录内容、生成自定义HTML并返回给CefSharp:
using CefSharp; using System.IO; using System.Text; public class CustomDirectoryResourceHandler : ResourceHandler { private readonly string _directoryPath; private string _finalHtml; private long _responseLength; public CustomDirectoryResourceHandler(string directoryPath) { _directoryPath = directoryPath; } public override bool ProcessRequestAsync(IRequest request, ICallback callback) { // 1. 读取目录中的子目录和文件 var subDirectories = Directory.GetDirectories(_directoryPath); var files = Directory.GetFiles(_directoryPath); // 2. 定义你的自定义HTML模板(这里可以随便改样式、加功能) var htmlTemplate = @" <!DOCTYPE html> <html lang='zh-CN'> <head> <meta charset='UTF-8'> <title>我的文件管理器 - {DirectoryName}</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: #f5f5f5; padding: 2rem; } .container { max-width: 1200px; margin: 0 auto; background: white; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); padding: 1.5rem; } h1 { color: #333; margin-bottom: 1.5rem; } .breadcrumb { color: #666; margin-bottom: 1rem; } .file-list { list-style: none; } .list-item { display: flex; align-items: center; padding: 0.8rem; border-radius: 4px; margin-bottom: 0.5rem; transition: background-color 0.2s; } .list-item:hover { background-color: #f0f0f0; } .folder-icon { font-size: 1.5rem; margin-right: 0.8rem; } .file-icon { font-size: 1.5rem; margin-right: 0.8rem; } .item-name { flex: 1; color: #333; text-decoration: none; } .folder .item-name { color: #2196F3; font-weight: 500; } </style> </head> <body> <div class='container'> <h1>📂 {DirectoryName}</h1> <div class='breadcrumb'>当前路径:{DirectoryPath}</div> <ul class='file-list'> {DirectoryItems} </ul> </div> </body> </html>"; // 3. 填充模板中的占位符 var directoryName = Path.GetFileName(_directoryPath) ?? "根目录"; var directoryItems = new StringBuilder(); // 添加上级目录(如果不是系统根目录) if (!Path.GetPathRoot(_directoryPath).Equals(_directoryPath, StringComparison.OrdinalIgnoreCase)) { var parentPath = Path.GetDirectoryName(_directoryPath); var parentUrl = $"file:///{parentPath.Replace("\\", "/")}"; directoryItems.Append($"<li class='list-item folder'><span class='folder-icon'>⬆️</span><a class='item-name' href='{parentUrl}'>.. (上级目录)</a></li>"); } // 添加子目录 foreach (var dir in subDirectories) { var dirName = Path.GetFileName(dir); var dirUrl = $"file:///{dir.Replace("\\", "/")}"; directoryItems.Append($"<li class='list-item folder'><span class='folder-icon'>📁</span><a class='item-name' href='{dirUrl}'>{dirName}</a></li>"); } // 添加文件 foreach (var file in files) { var fileName = Path.GetFileName(file); var fileUrl = $"file:///{file.Replace("\\", "/")}"; directoryItems.Append($"<li class='list-item file'><span class='file-icon'>📄</span><a class='item-name' href='{fileUrl}'>{fileName}</a></li>"); } // 替换模板变量 _finalHtml = htmlTemplate .Replace("{DirectoryName}", directoryName) .Replace("{DirectoryPath}", _directoryPath) .Replace("{DirectoryItems}", directoryItems.ToString()); // 4. 设置响应参数 _responseLength = Encoding.UTF8.GetByteCount(_finalHtml); MimeType = "text/html"; StatusCode = (int)CefStatusCode.Ok; StatusText = "OK"; // 通知CefSharp请求处理完成 callback.Continue(); return true; } public override void GetResponseHeaders(IResponse response, out long responseLength, out string redirectUrl) { responseLength = _responseLength; redirectUrl = null; base.GetResponseHeaders(response, out responseLength, out redirectUrl); } public override bool ReadResponse(Stream response, out int bytesRead, ICallback callback) { // 将生成的HTML写入响应流 var buffer = Encoding.UTF8.GetBytes(_finalHtml); response.Write(buffer, 0, buffer.Length); bytesRead = buffer.Length; // 完成响应,释放回调 callback.Dispose(); return false; // 返回false表示响应已全部写入 } }
3. 给ChromiumWebBrowser绑定自定义处理器
最后在WinForms窗体初始化时,把咱们的请求处理器绑定到ChromiumWebBrowser上:
public partial class MainForm : Form { public MainForm() { InitializeComponent(); // 初始化CefSharp(如果还没初始化的话) Cef.Initialize(new CefSettings()); // 创建浏览器控件 var chromeBrowser = new ChromiumWebBrowser(); chromeBrowser.RequestHandler = new CustomDirectoryRequestHandler(); chromeBrowser.Dock = DockStyle.Fill; // 添加到窗体 this.Controls.Add(chromeBrowser); // 测试导航到本地目录 chromeBrowser.Load("file:///C:/fonts"); } protected override void OnFormClosing(FormClosingEventArgs e) { // 关闭时释放Cef资源 Cef.Shutdown(); base.OnFormClosing(e); } }
一些注意事项
- 权限问题:确保你的程序有访问目标目录的读写权限,否则读取目录内容会抛出异常。
- 路径转换:Windows路径的反斜杠
\要转换成URL的正斜杠/,否则file://链接会失效。 - 模板自定义:你可以完全修改
htmlTemplate的内容,比如添加文件大小、修改日期、搜索框、排序功能等等,只要在ProcessRequestAsync里对应填充占位符即可。 - CefSharp版本兼容:这个代码是针对63版本写的,新版本的API可能有变化,但核心拦截+自定义响应的思路一致。
内容的提问来源于stack exchange,提问作者Optimax
相关产品推荐
相关产品推荐

