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

如何使用Delphi VCL修改副显示器的显示方向

Delphi VCL 副显示器显示方向修改方案

副屏修改不生效、System error 5、DLL调用异常三类问题的核心诱因集中在三点:

  • 调用ChangeDisplaySettingsEx时第一个参数传空,默认只会作用于主显示器,无法触发副屏配置变更
  • DEVMODE结构定义与当前系统/编译位宽不匹配,dmSize字段赋值错误,触发内存访问异常
  • 未正确设置dmFields标识位导致系统忽略方向参数,或调用场景不符合权限要求触发拒绝访问

前置说明

该方案完全基于Win32原生API实现,不需要引入Firemonkey任何单元,兼容Delphi 7到Delphi 12全版本,支持32位、64位编译。核心依赖的API都在VCL自带的Windows单元中,无需额外引入第三方库。

必要常量与结构定义

Delphi自带的DEVMODE结构在部分旧版本中缺少显示方向相关字段,直接使用会出现参数错位,手动定义适配版本即可:

const
  // 显示方向常量
  DMDO_DEFAULT = 0;  // 0度 标准横向
  DMDO_90      = 1;  // 90度 纵向
  DMDO_180     = 2;  // 180度 横向翻转
  DMDO_270     = 3;  // 270度 纵向翻转
  // DEVMODE字段有效标识
  DM_DISPLAYORIENTATION = $00000080;
  DM_PELSHEIGHT         = $00000010;
  DM_PELSWIDTH          = $00000008;
  // 配置变更标识
  CDS_UPDATEREGISTRY    = $00000001;
  DISP_CHANGE_SUCCESSFUL = 0;

type
  // 内存对齐的适配版DEVMODE结构,避免不同版本定义差异
  TDeviceMode = packed record
    dmDeviceName: array[0..CCHDEVICENAME - 1] of WideChar;
    dmSpecVersion: Word;
    dmDriverVersion: Word;
    dmSize: Word;
    dmDriverExtra: Word;
    dmFields: DWORD;
    dmOrientation: SHORT;
    dmPaperSize: SHORT;
    dmPaperLength: SHORT;
    dmPaperWidth: SHORT;
    dmScale: SHORT;
    dmCopies: SHORT;
    dmDefaultSource: SHORT;
    dmPrintQuality: SHORT;
    dmPosition: POINTL;
    dmDisplayOrientation: DWORD;
    dmDisplayFixedOutput: DWORD;
    dmColor: SHORT;
    dmDuplex: SHORT;
    dmYResolution: SHORT;
    dmTTOption: SHORT;
    dmCollate: SHORT;
    dmFormName: array[0..CCHFORMNAME - 1] of WideChar;
    dmLogPixels: Word;
    dmBitsPerPel: DWORD;
    dmPelsWidth: DWORD;
    dmPelsHeight: DWORD;
    dmDisplayFlags: DWORD;
    dmDisplayFrequency: DWORD;
    dmICMMethod: DWORD;
    dmICMIntent: DWORD;
    dmMediaType: DWORD;
    dmDitherType: DWORD;
    dmReserved1: DWORD;
    dmReserved2: DWORD;
    dmPanningWidth: DWORD;
    dmPanningHeight: DWORD;
  end;

实现逻辑

  • 调用EnumDisplayDevices枚举所有显示设备,通过DISPLAY_DEVICE_PRIMARY_DEVICE标识区分主副屏,记录目标副屏的设备名
  • 调用EnumDisplaySettings拉取目标副屏的当前生效配置,初始化DEVMODE结构,必须将dmSize赋值为结构实际内存大小
  • 根据目标旋转角度调整宽高参数:旋转90度、270度时需要交换屏幕宽高值,避免画面拉伸
  • 设置dmDisplayOrientation为目标角度,同时给dmFields加上对应有效位标记,告知系统哪些配置项需要更新
  • 调用ChangeDisplaySettingsEx传入目标副屏的设备名提交配置变更,校验返回值,失败时自动回滚

完整实现代码

uses Windows, SysUtils, Forms;

// 枚举指定索引的显示器,返回设备名与是否为主屏标识
function GetDisplayDevice(DeviceIndex: Integer; out DeviceName: string; out bPrimary: Boolean): Boolean;
var
  DispDev: TDisplayDevice;
begin
  Result := False;
  ZeroMemory(@DispDev, SizeOf(TDisplayDevice));
  DispDev.cb := SizeOf(TDisplayDevice);
  if EnumDisplayDevices(nil, DeviceIndex, DispDev, 0) then
  begin
    bPrimary := (DispDev.StateFlags and DISPLAY_DEVICE_PRIMARY_DEVICE) <> 0;
    DeviceName := DispDev.DeviceName;
    Result := True;
  end;
end;

// 修改指定显示器的显示方向
// RotateAngle取值: 0=0度 1=90度 2=180度 3=270度
function SetDisplayRotation(const DeviceName: string; RotateAngle: DWORD): Boolean;
var
  DevMode: TDeviceMode;
  Ret: Longint;
  nWidth, nHeight: DWORD;
begin
  Result := False;
  if not (RotateAngle in [DMDO_DEFAULT, DMDO_90, DMDO_180, DMDO_270]) then Exit;

  ZeroMemory(@DevMode, SizeOf(TDeviceMode));
  DevMode.dmSize := SizeOf(TDeviceMode);
  // 拉取目标显示器当前配置
  if not EnumDisplaySettings(PChar(DeviceName), ENUM_CURRENT_SETTINGS, DevMode) then Exit;

  // 90/270度旋转需要交换宽高,避免画面拉伸
  nWidth := DevMode.dmPelsWidth;
  nHeight := DevMode.dmPelsHeight;
  if (RotateAngle = DMDO_90) or (RotateAngle = DMDO_270) then
  begin
    DevMode.dmPelsWidth := nHeight;
    DevMode.dmPelsHeight := nWidth;
  end;

  DevMode.dmDisplayOrientation := RotateAngle;
  // 标记需要更新的配置项
  DevMode.dmFields := DM_DISPLAYORIENTATION or DM_PELSWIDTH or DM_PELSHEIGHT;

  // 传入设备名才能修改对应副屏,传空默认只改主屏
  Ret := ChangeDisplaySettingsEx(PChar(DeviceName), DevMode, 0, CDS_UPDATEREGISTRY, nil);
  if Ret = DISP_CHANGE_SUCCESSFUL then
    Result := True
  else
    // 配置失败自动回滚
    ChangeDisplaySettingsEx(PChar(DeviceName), DevMode, 0, 0, nil);
end;

// 调用示例:将第一个识别到的副屏设置为90度纵向
procedure TForm1.Button1Click(Sender: TObject);
var
  i: Integer;
  DevName: string;
  bIsPrimary: Boolean;
begin
  i := 0;
  while GetDisplayDevice(i, DevName, bIsPrimary) do
  begin
    if not bIsPrimary then
    begin
      if SetDisplayRotation(DevName, DMDO_90) then
        ShowMessage('副显示器方向修改成功')
      else
        ShowMessage('修改失败,错误码:' + IntToStr(GetLastError));
      Break;
    end;
    Inc(i);
  end;
end;

异常排查

  • System error 5 访问被拒绝:该错误不需要提权管理员,是调用场景问题:不要在系统锁屏、UAC安全桌面、非控制台远程会话下调用,确保程序运行在当前登录用户的交互桌面即可
  • DLL调用异常/内存报错:检查DEVMODE结构是否用packed修饰,dmSize是否用SizeOf()动态计算,不要硬编码固定数值,避免32/64位编译时结构大小差异导致内存越界
  • 配置不生效/只有主屏变化:检查ChangeDisplaySettingsEx第一个参数是否传入目标副屏的完整设备名,不能传空;检查dmFields是否包含DM_DISPLAYORIENTATION标记,未标记时系统会直接忽略方向参数
  • 旋转后画面拉伸/显示不全:90度、270度旋转时必须交换宽高值,180度旋转不需要交换宽高

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 22:03:29