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

TCommonDialog未触发OnModalBegin/改变ModalLevel,如何检测其是否激活?

检测TCommonDialog派生类的激活状态

首先得明确为什么你原来的方法行不通:TCommonDialog(比如TOpenDialog、TSaveDialog)本质是调用Windows原生的系统对话框,并非VCL自身实现的模态窗口。所以VCL的OnModalBegin/OnModalEnd事件不会触发,Application.ModalLevel也不会有变化——因为这些VCL机制只针对VCL自己的模态窗体(比如TForm.ShowModal)。

下面给你几种实用的解决方案,根据你的场景选择:

方法1:利用对话框的OnShow/OnClose事件(最简单,适合自己维护的对话框)

如果这些对话框是你自己代码里创建和调用的,直接用它们的OnShow和OnClose事件来维护一个状态标志即可。

实现步骤:

  1. 声明一个全局变量或者主窗体的字段来记录状态:
var
  g_CommonDialogIsActive: Boolean = False;
  1. 给每个TCommonDialog派生类的OnShow和OnClose事件绑定处理函数:
procedure TMainForm.OpenDialogShow(Sender: TObject);
begin
  g_CommonDialogIsActive := True;
end;

procedure TMainForm.OpenDialogClose(Sender: TObject);
begin
  g_CommonDialogIsActive := False;
end;
  1. 在你拦截键盘事件的逻辑里,直接检查这个变量:
if g_CommonDialogIsActive then
begin
  // 对话框处于激活状态,执行对应的键盘事件处理逻辑
end;

优点:代码简单,无额外开销;缺点:需要给每个对话框手动绑定事件,无法覆盖第三方组件或系统内部调用的对话框。

方法2:使用CBT钩子(通用,覆盖所有对话框)

如果需要检测所有在当前进程中弹出的系统对话框(包括第三方组件调用的),可以用Windows的CBT钩子来监听对话框的创建和销毁。

实现代码:

unit DialogHook;

interface

uses
  Windows, Messages;

type
  TDialogHook = class
  private
    class var FHookHandle: HHOOK;
    class var FActiveDialogCount: Integer;
    class function CBTProc(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
  public
    class procedure StartMonitoring;
    class procedure StopMonitoring;
    class function IsAnyDialogActive: Boolean;
  end;

implementation

class procedure TDialogHook.StartMonitoring;
begin
  if FHookHandle = 0 then
    // 只挂钩当前线程,因为对话框都在主线程弹出
    FHookHandle := SetWindowsHookEx(WH_CBT, @CBTProc, 0, GetCurrentThreadId);
end;

class procedure TDialogHook.StopMonitoring;
begin
  if FHookHandle <> 0 then
  begin
    UnhookWindowsHookEx(FHookHandle);
    FHookHandle := 0;
    FActiveDialogCount := 0;
  end;
end;

class function TDialogHook.CBTProc(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT;
var
  ClassName: array[0..255] of Char;
begin
  // 先调用下一个钩子,保证系统正常工作
  Result := CallNextHookEx(FHookHandle, nCode, wParam, lParam);
  if nCode < 0 then Exit;

  case nCode of
    // 窗口创建时触发
    HCBT_CREATEWND:
      begin
        GetClassName(wParam, ClassName, SizeOf(ClassName));
        // #32770是Windows通用对话框的类名
        if CompareText(ClassName, '#32770') = 0 then
          Inc(FActiveDialogCount);
      end;
    // 窗口销毁时触发
    HCBT_DESTROY:
      begin
        GetClassName(wParam, ClassName, SizeOf(ClassName));
        if CompareText(ClassName, '#32770') = 0 then
          Dec(FActiveDialogCount);
      end;
  end;
end;

class function TDialogHook.IsAnyDialogActive: Boolean;
begin
  Result := FActiveDialogCount > 0;
end;

end.

使用方式:

在程序启动时(比如主窗体的OnCreate事件)调用TDialogHook.StartMonitoring,退出时(OnDestroy)调用TDialogHook.StopMonitoring。需要检查状态时,调用TDialogHook.IsAnyDialogActive即可。

优点:自动检测所有系统对话框,无需手动绑定事件;缺点:需要处理钩子的生命周期,代码稍复杂。

方法3:枚举当前线程窗口(按需检查,无持久钩子)

如果不需要实时监听,只是在拦截键盘事件时偶尔检查状态,可以直接枚举当前线程的窗口,判断是否存在激活的系统对话框。

实现代码:

function EnumThreadWindowsProc(hwnd: HWND; lParam: LPARAM): BOOL; stdcall;
var
  ClassName: array[0..255] of Char;
  WindowStyle: DWORD;
begin
  Result := True;
  GetClassName(hwnd, ClassName, SizeOf(ClassName));
  // 检查是否是通用对话框类,并且是模态窗口
  if CompareText(ClassName, '#32770') = 0 then
  begin
    WindowStyle := GetWindowLongPtr(hwnd, GWL_STYLE);
    if (WindowStyle and WS_MODALFRAME) <> 0 then
    begin
      PBoolean(lParam)^ := True;
      Result := False; // 找到后停止枚举
    end;
  end;
end;

function IsCommonDialogActive: Boolean;
var
  DialogActive: Boolean;
begin
  DialogActive := False;
  // 枚举当前线程的所有窗口
  EnumThreadWindows(GetCurrentThreadId, @EnumThreadWindowsProc, LPARAM(@DialogActive));
  Result := DialogActive;
end;

使用方式:

直接在键盘事件拦截逻辑里调用IsCommonDialogActive获取状态即可。

优点:无需钩子,代码独立;缺点:每次检查都要枚举窗口,性能略差(但对键盘事件来说完全可以忽略)。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 10:06:37