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

如何通过编程断开Windows系统中指定的显示器?

如何在Windows 10中编程禁用指定显示器(使其从系统列表消失)

这确实是个实用的需求——单纯关闭屏幕没法解决图形密集型任务的额外开销,必须让系统彻底停止识别该显示器才行。下面我给你两个可行的方案,都是基于Windows原生API实现的,而且能精准控制指定显示器:

方案一:使用Display Config API(推荐,官方显示配置接口)

这个方案是Windows官方推荐的显示配置方式,支持Windows 8及以上系统,能直接修改系统的显示布局,让指定显示器从设置列表中消失。核心思路是获取当前显示配置,移除目标显示器的路径信息,然后应用新配置。

关键步骤:

  1. 获取当前显示配置的缓冲区大小,再调用QueryDisplayConfig获取所有显示设备的路径和模式信息。
  2. 遍历获取到的路径信息,找到你要禁用的显示器(可以通过设备名称、EDID或硬件ID匹配)。
  3. 构造不包含目标显示器的新配置数组,调用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设备管理器,禁用指定的显示器设备,效果和手动在设备管理器里禁用一样,显示器会从显示设置中消失。

关键步骤:

  1. 枚举所有显示器设备,找到目标设备的实例ID。
  2. 使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:21:46