You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

如何在Delphi的TWinControl中播放GIF动画?

解决Delphi中UI线程繁忙时显示自动播放GIF加载图标问题

核心思路

要让GIF在UI线程繁忙时也能自动播放,关键是要么让UI线程空闲下来处理动画帧,要么用非UI线程触发帧更新但安全通知UI绘制,下面给两种实用方案:


方案1:将耗时操作移到后台线程(推荐)

这是最稳妥的方式,从根源解决UI线程阻塞问题,同时用VCL自带组件就能实现自动GIF动画:

  • 准备一个顶层的TWinControl载体:比如用TPanel(设置Align:=alClient,调用BringToFront确保在最上层),或者直接创建一个无边框的TForm作为加载弹窗。
  • 在载体上放置TImage控件,加载GIF文件:
    var
      LGif: TGIFImage;
    begin
      LGif := TGIFImage.Create;
      try
        LGif.LoadFromFile('loading.gif');
        LGif.Animate := True; // 开启自动播放
        Image1.Picture.Assign(LGif);
      finally
        LGif.Free;
      end;
    end;
    
  • 把耗时操作放到TThread子类中执行:
    type
      TLongTaskThread = class(TThread)
      protected
        procedure Execute; override;
        procedure UpdateUI;
      end;
    
    procedure TLongTaskThread.Execute;
    begin
      // 执行耗时操作,比如数据处理、文件读写等
      Sleep(5000); // 模拟耗时任务
      Synchronize(UpdateUI); // 任务完成后通知UI
    end;
    
    procedure TLongTaskThread.UpdateUI;
    begin
      // 关闭加载控件/窗口
      PanelLoading.Visible := False;
    end;
    
  • 启动线程时显示加载控件:
    PanelLoading.Visible := True;
    TLongTaskThread.Create(True).Start;
    
    此时UI线程不被阻塞,TGIFImage内部的定时器能正常触发帧切换,自动播放动画。

方案2:用多媒体定时器触发帧更新(无需修改耗时操作)

如果必须在UI线程执行耗时操作,可借助Windows多媒体定时器(在独立线程触发回调),绕过UI线程的定时器阻塞:

  • 自定义一个继承自TPanelTGifLoadingPanel控件,内部处理GIF帧切换:
    type
      TGifLoadingPanel = class(TPanel)
      private
        FGif: TGIFImage;
        FFrameIndex: Integer;
        FTimerID: UINT;
        procedure WMCustomUpdateFrame(var Msg: TMessage); message WM_USER + 1;
        procedure TimerCallback(uTimerID, uMsg: UINT; dwUser, dw1, dw2: DWORD_PTR); stdcall;
      protected
        procedure Paint; override;
        procedure CreateWnd; override;
        procedure DestroyWnd; override;
      public
        constructor Create(AOwner: TComponent); override;
        destructor Destroy; override;
        procedure LoadGif(const AFileName: string);
      end;
    
    constructor TGifLoadingPanel.Create(AOwner: TComponent);
    begin
      inherited;
      FGif := TGIFImage.Create;
      FFrameIndex := 0;
    end;
    
    destructor TGifLoadingPanel.Destroy;
    begin
      FGif.Free;
      inherited;
    end;
    
    procedure TGifLoadingPanel.LoadGif(const AFileName: string);
    begin
      FGif.LoadFromFile(AFileName);
      FFrameIndex := 0;
      Invalidate;
    end;
    
    procedure TGifLoadingPanel.CreateWnd;
    var
      LDelay: Integer;
    begin
      inherited;
      if FGif.Images.Count > 1 then
      begin
        // 获取第一帧的延迟时间,单位毫秒
        LDelay := FGif.Images[0].Delay * 10;
        // 设置多媒体定时器,在独立线程触发回调
        FTimerID := timeSetEvent(LDelay, 0, @TimerCallback, DWORD_PTR(Self), TIME_PERIODIC);
      end;
    end;
    
    procedure TGifLoadingPanel.DestroyWnd;
    begin
      if FTimerID <> 0 then
      begin
        timeKillEvent(FTimerID);
        FTimerID := 0;
      end;
      inherited;
    end;
    
    procedure TGifLoadingPanel.TimerCallback(uTimerID, uMsg: UINT; dwUser, dw1, dw2: DWORD_PTR);
    begin
      // 发送自定义消息通知UI线程更新帧
      PostMessage(TGifLoadingPanel(dwUser).Handle, WM_USER + 1, 0, 0);
    end;
    
    procedure TGifLoadingPanel.WMCustomUpdateFrame(var Msg: TMessage);
    begin
      Inc(FFrameIndex);
      if FFrameIndex >= FGif.Images.Count then
        FFrameIndex := 0;
      Invalidate;
    end;
    
    procedure TGifLoadingPanel.Paint;
    begin
      inherited;
      if FGif.Images.Count > 0 then
        FGif.Images[FFrameIndex].Draw(Canvas, ClientRect);
    end;
    
  • 使用时直接创建这个控件,加载GIF并显示:
    var
      LGifPanel: TGifLoadingPanel;
    begin
      LGifPanel := TGifLoadingPanel.Create(Self);
      LGifPanel.Align := alClient;
      LGifPanel.BringToFront;
      LGifPanel.LoadGif('loading.gif');
      LGifPanel.Visible := True;
    
      // 执行耗时操作(UI线程)
      Sleep(5000);
    
      LGifPanel.Free;
    end;
    
    多媒体定时器在独立线程运行,即使UI线程繁忙,也能定时发送消息触发帧更新,只要UI线程有间隙处理消息,就能完成绘制。

额外提示

  • 如果用Delphi XE2及以上版本,也可以直接用TAnimation控件:设置AnimationType := atGif,指定GifFileName,它会自动处理GIF动画播放,无需手动写代码,前提是UI线程能处理消息。
  • 确保加载控件在最上层:调用BringToFront,如果是独立窗口,设置PopupMode := pmExplicitPopupParent := Self避免被其他窗口遮挡。

内容的提问来源于stack exchange,提问作者Arcus

火山引擎 最新活动