如何通过编程断开Windows系统中指定的显示器?
如何在Windows 10中编程禁用指定显示器(使其从系统列表消失)
这确实是个实用的需求——单纯关闭屏幕没法解决图形密集型任务的额外开销,必须让系统彻底停止识别该显示器才行。下面我给你两个可行的方案,都是基于Windows原生API实现的,而且能精准控制指定显示器:
方案一:使用Display Config API(推荐,官方显示配置接口)
这个方案是Windows官方推荐的显示配置方式,支持Windows 8及以上系统,能直接修改系统的显示布局,让指定显示器从设置列表中消失。核心思路是获取当前显示配置,移除目标显示器的路径信息,然后应用新配置。
关键步骤:
- 获取当前显示配置的缓冲区大小,再调用
QueryDisplayConfig获取所有显示设备的路径和模式信息。 - 遍历获取到的路径信息,找到你要禁用的显示器(可以通过设备名称、EDID或硬件ID匹配)。
- 构造不包含目标显示器的新配置数组,调用
SetDisplayConfig应用修改。
C#代码示例:
首先需要定义必要的P/Invoke结构体和函数(完整结构体需参考Windows官方文档补充):
using System; using System.Collections.Generic; using System.Runtime.InteropServices; public class DisplayManager { [StructLayout(LayoutKind.Sequential)] private struct DISPLAYCONFIG_PATH_INFO { public DISPLAYCONFIG_PATH_SOURCE_INFO sourceInfo; public DISPLAYCONFIG_PATH_TARGET_INFO targetInfo; public uint flags; } [StructLayout(LayoutKind.Sequential)] private struct DISPLAYCONFIG_PATH_SOURCE_INFO { public LUID adapterId; public uint id; public uint modeInfoIdx; public uint statusFlags; } [StructLayout(LayoutKind.Sequential)] private struct DISPLAYCONFIG_PATH_TARGET_INFO { public LUID adapterId; public uint id; public uint modeInfoIdx; public DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY outputTechnology; public DISPLAYCONFIG_ROTATION rotation; public DISPLAYCONFIG_SCALING scaling; public DISPLAYCONFIG_RATIONAL refreshRate; public DISPLAYCONFIG_SCANLINE_ORDERING scanLineOrdering; public bool targetAvailable; public uint statusFlags; } [StructLayout(LayoutKind.Sequential)] private struct LUID { public uint LowPart; public int HighPart; } // 省略其余必要结构体定义(如DISPLAYCONFIG_MODE_INFO、DISPLAYCONFIG_TARGET_DEVICE_NAME等) [DllImport("user32.dll")] private static extern long GetDisplayConfigBufferSizes(uint flags, out uint numPathArrayElements, out uint numModeArrayElements); [DllImport("user32.dll")] private static extern long QueryDisplayConfig(uint flags, ref uint numPathArrayElements, DISPLAYCONFIG_PATH_INFO[] pathArray, ref uint numModeArrayElements, DISPLAYCONFIG_MODE_INFO[] modeArray, IntPtr currentTopologyId); [DllImport("user32.dll")] private static extern long SetDisplayConfig(uint numPathArrayElements, DISPLAYCONFIG_PATH_INFO[] pathArray, uint numModeArrayElements, DISPLAYCONFIG_MODE_INFO[] modeArray, uint flags); [DllImport("user32.dll")] private static extern long DisplayConfigGetDeviceInfo(ref DISPLAYCONFIG_TARGET_DEVICE_NAME deviceName); private const uint QDC_ALL_PATHS = 0x00000001; private const uint SDC_APPLY = 0x00000008; private const uint SDC_USE_SUPPLIED_DISPLAY_CONFIG = 0x00000020; private const uint SDC_ALLOW_CHANGES = 0x00000001; public static bool DisableMonitor(string targetDeviceName) { // 获取缓冲区大小 GetDisplayConfigBufferSizes(QDC_ALL_PATHS, out uint numPaths, out uint numModes); var paths = new DISPLAYCONFIG_PATH_INFO[numPaths]; var modes = new DISPLAYCONFIG_MODE_INFO[numModes]; // 获取当前显示配置 if (QueryDisplayConfig(QDC_ALL_PATHS, ref numPaths, paths, ref numModes, modes, IntPtr.Zero) != 0) return false; // 筛选出要保留的路径(移除目标显示器) var newPaths = new List<DISPLAYCONFIG_PATH_INFO>(); foreach (var path in paths) { // 获取目标设备名称 var targetName = new DISPLAYCONFIG_TARGET_DEVICE_NAME(); targetName.header.size = (uint)Marshal.SizeOf(targetName); targetName.header.type = DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME; targetName.header.adapterId = path.targetInfo.adapterId; targetName.header.id = path.targetInfo.id; if (DisplayConfigGetDeviceInfo(ref targetName) == 0) { string deviceName = Marshal.PtrToStringUni(targetName.monitorFriendlyDeviceName); if (!deviceName.Equals(targetDeviceName, StringComparison.OrdinalIgnoreCase)) { newPaths.Add(path); } } } // 应用新配置 return SetDisplayConfig((uint)newPaths.Count, newPaths.ToArray(), numModes, modes, SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_ALLOW_CHANGES) == 0; } }
方案二:使用SetupAPI直接禁用设备(更底层)
这个方案直接操作Windows设备管理器,禁用指定的显示器设备,效果和手动在设备管理器里禁用一样,显示器会从显示设置中消失。
关键步骤:
- 枚举所有显示器设备,找到目标设备的实例ID。
- 使用
SetupDiCallClassInstaller发送禁用设备的指令。
C#代码示例:
using System; using System.ComponentModel; using System.Runtime.InteropServices; public class DeviceManager { [DllImport("setupapi.dll", SetLastError = true)] private static extern IntPtr SetupDiGetClassDevs(ref Guid classGuid, string enumerator, IntPtr hwndParent, uint flags); [DllImport("setupapi.dll", SetLastError = true)] private static extern bool SetupDiEnumDeviceInfo(IntPtr deviceInfoSet, uint memberIndex, ref SP_DEVINFO_DATA deviceInfoData); [DllImport("setupapi.dll", SetLastError = true)] private static extern bool SetupDiSetClassInstallParams(IntPtr deviceInfoSet, ref SP_DEVINFO_DATA deviceInfoData, ref SP_PROPCHANGE_PARAMS classInstallParams, uint size); [DllImport("setupapi.dll", SetLastError = true)] private static extern bool SetupDiCallClassInstaller(uint installFunction, IntPtr deviceInfoSet, ref SP_DEVINFO_DATA deviceInfoData); [DllImport("setupapi.dll", SetLastError = true)] private static extern bool SetupDiDestroyDeviceInfoList(IntPtr deviceInfoSet); [DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern bool SetupDiGetDeviceRegistryProperty(IntPtr deviceInfoSet, ref SP_DEVINFO_DATA deviceInfoData, uint property, out uint propertyRegDataType, IntPtr propertyBuffer, uint propertyBufferSize, out uint requiredSize); private const uint DIGCF_PRESENT = 0x00000002; private const uint DIGCF_DEVICEINTERFACE = 0x00000010; private const uint DIF_PROPERTYCHANGE = 0x00000012; private const uint DICS_DISABLE = 0x00000002; private const uint SPDRP_FRIENDLYNAME = 0x0000000C; [StructLayout(LayoutKind.Sequential)] private struct SP_DEVINFO_DATA { public uint cbSize; public Guid classGuid; public uint devInst; public IntPtr reserved; } [StructLayout(LayoutKind.Sequential)] private struct SP_PROPCHANGE_PARAMS { public SP_CLASSINSTALL_HEADER ClassInstallHeader; public uint StateChange; public uint Scope; public uint HwProfile; } [StructLayout(LayoutKind.Sequential)] private struct SP_CLASSINSTALL_HEADER { public uint cbSize; public uint InstallFunction; } private static readonly Guid GUID_DEVINTERFACE_MONITOR = new Guid("{E6F07B5F-EE97-4a90-B076-33F57BF4EAA7}"); public static bool DisableMonitor(string deviceName) { IntPtr deviceInfoSet = SetupDiGetClassDevs(ref GUID_DEVINTERFACE_MONITOR, null, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); if (deviceInfoSet == IntPtr.Zero) throw new Win32Exception(); try { SP_DEVINFO_DATA deviceInfoData = new SP_DEVINFO_DATA(); deviceInfoData.cbSize = (uint)Marshal.SizeOf(deviceInfoData); for (uint i = 0; SetupDiEnumDeviceInfo(deviceInfoSet, i, ref deviceInfoData); i++) { // 获取设备友好名称 string friendlyName = GetDeviceFriendlyName(deviceInfoSet, ref deviceInfoData); if (friendlyName.Equals(deviceName, StringComparison.OrdinalIgnoreCase)) { // 构造禁用参数 SP_PROPCHANGE_PARAMS propChangeParams = new SP_PROPCHANGE_PARAMS(); propChangeParams.ClassInstallHeader.cbSize = (uint)Marshal.SizeOf(propChangeParams.ClassInstallHeader); propChangeParams.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE; propChangeParams.StateChange = DICS_DISABLE; propChangeParams.Scope = 0; propChangeParams.HwProfile = 0; if (!SetupDiSetClassInstallParams(deviceInfoSet, ref deviceInfoData, ref propChangeParams, (uint)Marshal.SizeOf(propChangeParams))) throw new Win32Exception(); return SetupDiCallClassInstaller(DIF_PROPERTYCHANGE, deviceInfoSet, ref deviceInfoData); } } return false; } finally { if (deviceInfoSet != IntPtr.Zero) SetupDiDestroyDeviceInfoList(deviceInfoSet); } } private static string GetDeviceFriendlyName(IntPtr deviceInfoSet, ref SP_DEVINFO_DATA deviceInfoData) { SetupDiGetDeviceRegistryProperty(deviceInfoSet, ref deviceInfoData, SPDRP_FRIENDLYNAME, out _, IntPtr.Zero, 0, out uint requiredSize); IntPtr buffer = Marshal.AllocHGlobal((int)requiredSize); try { if (SetupDiGetDeviceRegistryProperty(deviceInfoSet, ref deviceInfoData, SPDRP_FRIENDLYNAME, out _, buffer, requiredSize, out _)) { return Marshal.PtrToStringUni(buffer); } return string.Empty; } finally { Marshal.FreeHGlobal(buffer); } } }
重要注意事项:
- 权限要求:这两个方案都需要以管理员权限运行程序,否则会因为权限不足失败。
- 恢复方法:要恢复显示器,只需重新启用设备(方案二)或者重新应用包含该显示器的显示配置(方案一)。
- 避免禁用主显示器:如果不小心禁用了主显示器,可能需要通过远程桌面或者重启系统来恢复,测试时建议先从副显示器开始。
- 关于之前的方案:你提到的"Turn on/off monitor"方案是通过
SendMessage发送SC_MONITORPOWER消息,这只是让显示器进入低功耗模式,系统仍然认为设备是连接状态,所以无法消除性能开销,而且部分显卡驱动会自动唤醒显示器,导致几秒后恢复。
内容的提问来源于stack exchange,提问作者Elmub
相关产品推荐
相关产品推荐

