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

如何将Delphi监视器索引与Windows桌面显示器编号匹配

Delphi中Screen.Monitors与Windows系统显示器编号的匹配方案

问题根源很明确:Delphi自带Screen.Monitors数组的索引是VCL框架按设备枚举顺序生成的,枚举顺序受显卡驱动加载顺序、主显示器设置、设备接入先后等因素影响,和Windows显示设置里点击「识别」弹出的系统分配编号没有固定对应关系,直接拿索引做匹配必然错位。

核心匹配逻辑

Windows给显示器分配的可见编号,是和显示设备的唯一实例ID绑定存储的,可以通过系统API打通三层映射关系,实现100%准确匹配:
Screen.Monitors[i].Handle(HMONITOR句柄) → 对应显卡输出设备名 → 对应物理显示器实例ID → 系统分配的显示器编号

具体实现步骤

  • 第一层枚举:调用EnumDisplayDevicesAPI枚举所有显卡输出设备
    初始化循环变量iDevNum从0开始递增,每次调用EnumDisplayDevices(nil, iDevNum, dd, 0),直到API返回False为止。每次调用成功后,记录返回的DISPLAY_DEVICE结构中的DeviceName(格式如\\.\DISPLAY1)、DeviceKey(对应注册表中显卡配置项的路径)。
  • 第二层枚举:针对每个显卡输出设备,枚举其挂载的物理显示器
    对第一步拿到的每个显卡设备,再次调用EnumDisplayDevices(PChar(dd.DeviceName), 0, ddMon, EDD_GET_DEVICE_INTERFACE_NAME),拿到该输出口连接的物理显示器信息,此时ddMon.DeviceID就是物理显示器的全局唯一实例路径。
  • 提取系统编号:从注册表读取对应显示器的编号
    打开第一步拿到的DeviceKey对应的注册表项,遍历其下的0000、0001这类子项,找到子项中DeviceID值和第二步拿到的物理显示器ddMon.DeviceID一致的项,读取该项下的DisplayNumberDWORD值,这个值就是Windows设置里显示的对应显示器编号。
  • 句柄关联:将编号和Screen.Monitors的元素绑定
    Screen.Monitors数组中每个元素的Handle属性就是该显示器对应的HMONITOR句柄,调用GetMonitorInfo传入该句柄,可以拿到该显示器所属的显卡输出设备名(和第一步记录的DeviceName一致),通过设备名做关联,就能把每个Screen.Monitors元素和对应的系统编号一一对应。

核心代码示例

const
  EDD_GET_DEVICE_INTERFACE_NAME = $00000001; // 老版本Delphi未定义该常量可手动补充

type
  TMonitorNumMap = array of Integer; // 索引对应Screen.Monitors的索引,值为Windows系统显示的编号

function GetMonitorNumberMap: TMonitorNumMap;
var
  i, j: Integer;
  dd, ddMon: TDisplayDevice;
  mi: TMonitorInfo;
  hMon: HMONITOR;
  devNameMap: TDictionary<string, Integer>;
  reg: TRegistry;
  devID: string;
  dispNum: Integer;
begin
  SetLength(Result, Screen.MonitorCount);
  devNameMap := TDictionary<string, Integer>.Create;
  reg := TRegistry.Create(KEY_READ);
  try
    reg.RootKey := HKEY_LOCAL_MACHINE;
    ZeroMemory(@dd, SizeOf(dd));
    dd.cb := SizeOf(dd);
    // 枚举显卡输出+物理显示器,建立设备名和系统编号的映射
    i := 0;
    while EnumDisplayDevices(nil, i, dd, 0) do
    begin
      ZeroMemory(@ddMon, SizeOf(ddMon));
      ddMon.cb := SizeOf(ddMon);
      if EnumDisplayDevices(PChar(dd.DeviceName), 0, ddMon, EDD_GET_DEVICE_INTERFACE_NAME) then
      begin
        devID := ddMon.DeviceID;
        // 从注册表读取DisplayNumber值
        if reg.OpenKeyReadOnly(dd.DeviceKey) then
        begin
          j := 0;
          while True do
          begin
            if reg.OpenKeyReadOnly(Format('%s\VOLATILE%04d', [dd.DeviceKey, j])) then
            begin
              if reg.ReadString('DeviceID') = devID then
              begin
                dispNum := reg.ReadInteger('DisplayNumber');
                devNameMap.AddOrSetValue(dd.DeviceName, dispNum);
                reg.CloseKey;
                Break;
              end;
              reg.CloseKey;
            end
            else Break;
            Inc(j);
          end;
          reg.CloseKey;
        end;
      end;
      Inc(i);
    end;
    // 关联Screen.Monitors句柄和对应系统编号
    for i := 0 to Screen.MonitorCount - 1 do
    begin
      hMon := Screen.Monitors[i].Handle;
      ZeroMemory(@mi, SizeOf(mi));
      mi.cbSize := SizeOf(mi);
      GetMonitorInfo(hMon, @mi);
      if not devNameMap.TryGetValue(mi.szDevice, Result[i]) then
        Result[i] := -1; // 未匹配到物理设备(虚拟显示器、远程桌面会话等场景兜底)
    end;
  finally
    reg.Free;
    devNameMap.Free;
  end;
end;

注意事项

  • 不要用字符串截取DeviceID的方式提取编号,不同Windows版本的DeviceID格式存在差异,直接读注册表的DisplayNumber值是跨版本兼容的方案,从Win7到最新Win11均有效。
  • 系统分配的显示器编号不会因为调整显示器排列、修改主显示器而改变,只会在显示器首次接入系统时分配,仅需要在收到WM_DISPLAYCHANGE消息(显示配置变更)时刷新匹配结果即可。
  • 部分虚拟显示器、远程桌面会话的显示器没有对应的物理设备和DisplayNumber值,代码中做了-1的兜底标记,可根据业务需求自行处理。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 03:42:20