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

CEF4Delphi中TChromium.OnDownloadUpdated事件自定义窗体DFM流异常问题

CEF4Delphi自定义下载对话框DFM加载异常问题

我在基于CEF4Delphi的项目中,实现了下载完成后弹出自定义打开/保存对话框的功能,但偶尔会出现DFM流加载失败的情况:窗体创建后、OnShow事件触发前,按钮、标签等控件的Width、Left属性全部变为0,导致窗体显示异常。

事件代码实现

procedure TMainForm.ChromiumDownloadUpdated(Sender: TObject;
  const browser: ICefBrowser; const downloadItem: ICefDownloadItem;
  const callback: ICefDownloadItemCallback);

var
  FileIcon: TIcon;
  Descripcion: string;
  originalFilename: string;
  openSaveForm: TOpenSaveForm;
begin
  else if downloadItem.IsComplete then
  begin
    FileIcon := TIcon.Create;
    openSaveForm := TOpenSaveForm.Create(Self);
    try
      try
        openSaveForm.LabelFilename.Caption := OpenSaveFormCaptionMsg(originalFilename, downloadItem.ReceivedBytes);
        GetFileIconAndDescription(downloadItem.GetFullPath, FileIcon, Descripcion);
        openSaveForm.RadioButtonOpen.Caption := openSaveForm.RadioButtonOpen.Caption + ' ' + Descripcion;
        openSaveForm.FileIcon.Picture.Icon := FileIcon;
        if openSaveForm.ShowModal = mrOk then
          ChooseShowSaveDialog(downloadItem.GetFullPath, openSaveForm.RadioButtonSave.Checked);
      except
        on E: Exception do
          ShowMessage('Excepcion opensaveform: ' + E.Message);
      end;
    finally
      openSaveForm.Free;
      FileIcon.Free;
    end;
  end;
end;

显示效果对比

  • 正常显示状态:
    正常显示的自定义下载对话框

  • 异常显示状态(DFM加载失败):
    显示异常的自定义下载对话框

问题根源

CEF4Delphi的ChromiumDownloadUpdated事件回调运行在非主线程(浏览器IO线程或渲染线程),而VCL控件的创建、属性修改必须在主线程中执行。直接在非主线程中创建窗体,会破坏DFM的加载流程,导致控件布局属性初始化失败。

解决方案:切换到主线程操作UI

将窗体创建和相关UI操作代码通过TThread.Synchronize或TThread.Queue切换到主线程执行,修改后的代码如下:

procedure TMainForm.ChromiumDownloadUpdated(Sender: TObject;
  const browser: ICefBrowser; const downloadItem: ICefDownloadItem;
  const callback: ICefDownloadItemCallback);
var
  originalFilename: string;
  fullPath: string;
  receivedBytes: Int64;
begin
  if downloadItem.IsComplete then
  begin
    // 先保存需要的参数,避免在同步闭包中直接引用downloadItem(可能存在生命周期问题)
    originalFilename := downloadItem.OriginalFileName;
    fullPath := downloadItem.GetFullPath;
    receivedBytes := downloadItem.ReceivedBytes;
    
    // 使用Synchronize切换到主线程执行UI操作
    TThread.Synchronize(nil, procedure
    var
      FileIcon: TIcon;
      Descripcion: string;
      openSaveForm: TOpenSaveForm;
    begin
      FileIcon := TIcon.Create;
      openSaveForm := TOpenSaveForm.Create(Self);
      try
        try
          openSaveForm.LabelFilename.Caption := OpenSaveFormCaptionMsg(originalFilename, receivedBytes);
          GetFileIconAndDescription(fullPath, FileIcon, Descripcion);
          openSaveForm.RadioButtonOpen.Caption := openSaveForm.RadioButtonOpen.Caption + ' ' + Descripcion;
          openSaveForm.FileIcon.Picture.Icon := FileIcon;
          if openSaveForm.ShowModal = mrOk then
            ChooseShowSaveDialog(fullPath, openSaveForm.RadioButtonSave.Checked);
        except
          on E: Exception do
            ShowMessage('Excepcion opensaveform: ' + E.Message);
        end;
      finally
        openSaveForm.Free;
        FileIcon.Free;
      end;
    end);
  end;
end;

补充说明

  • TThread.Synchronize会阻塞当前非主线程,直到主线程完成UI操作;如果不想阻塞线程,可以改用TThread.Queue,它会将任务放入主线程消息队列后立即返回。
  • 所有CEF事件回调中的UI操作都必须遵循主线程原则,否则可能引发各种UI异常或内存问题。

内容的提问来源于stack exchange,提问作者Héctor C.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.01 12:32:27