You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何自定义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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 07:34:24