重新启动计算机后,SetDisplayConfig的配置无效

时间:2019-07-18 14:48:39

标签: c# windows winapi pinvoke multiple-monitors

我正在使用WINAPI通过调用方法来启用/禁用电视监视器。

我发现可以使用的方法是保存两种状态的整个pathInfo和modeInfo数组
(启用/禁用),并使用它们恢复到该状态。

该方法正常运行,但是在重新启动计算机后,设置已过时。
如果我只是重新启动程序,它会很好地工作,因此序列化是正确的。

目标是在计算机上一次保存/序列化这些设置,并能够永久使用它们。

我尝试仅保存我感兴趣的单个监视器的路径和模式(仅通过过滤活动显示),但是事实证明,当有一个屏幕出现时,其他屏幕的模式也会受到影响。在设置中额外显示。

主要方法是以下方法,但是you can find the whole class here!
(在此之上,我使用的是User32 PInvoke lib

[Serializable]
public class TVSettings {
    public DISPLAYCONFIG_PATH_INFO[] Path;
    public DISPLAYCONFIG_MODE_INFO[] Mode;
    public TVSettings(DISPLAYCONFIG_PATH_INFO[] pathArray, DISPLAYCONFIG_MODE_INFO[] modeArray) {
        Path = pathArray;
        Mode = modeArray;
    }
}
// The preset of the settings which I serialize.
public static TVSettings Enabled;
public static TVSettings Disabled;

// The main method. Merged the Save & ChangeState branches to save space.
public static void ChangeTVState(bool ChangeState = false, bool Save = false) {
    uint numPathArrayElements = 0;
    uint numModeInfoArrayElements = 0;
    uint id = QDC_ALL_PATHS; // Searching for ALL PATHS because I want the disabled screen inside the array after the Query.

    // Initialize and Query all the Display Config info.
    int bufferError = GetDisplayConfigBufferSizes(id, ref numPathArrayElements, ref numModeInfoArrayElements);
    DISPLAYCONFIG_PATH_INFO[] pathArray = new DISPLAYCONFIG_PATH_INFO[numPathArrayElements];
    DISPLAYCONFIG_MODE_INFO[] modeArray = new DISPLAYCONFIG_MODE_INFO[numModeInfoArrayElements];

    QueryDisplayConfig(id, ref numPathArrayElements, pathArray, ref numModeInfoArrayElements, modeArray, IntPtr.Zero);

    // Grab the active Screens -- was previously used for tests.
    var active_modeArray = modeArray.Where(x => x.targetMode.targetVideoSignalInfo.activeSize.cx != 0).ToArray();
    var active_pathArray = pathArray.Where(x => x.flags != 0).ToArray();

    bool ThirdScreenIsConnected = active_pathArray.Length >= 3 && active_modeArray.Length >= 3;

    if (Save) {
        // Save on the appropriate Preset field.
        if (ThirdScreenIsConnected) { Enabled = new TVSettings(pathArray, modeArray); }
        else { Disabled = new TVSettings(pathArray, modeArray); }
    }

    if (ChangeState) {
        // Safety measures because I don't wanna mess up the settings too much.
        if (Enabled == null || Disabled == null) {
            Console.WriteLine("Enabled & Disabled Settings are not configured properly.");
            Console.WriteLine("Please save both and try again.");
            return;
        }

        // Use the settings of the other state
        // eg: if 3rd monitor is currently disabled, we use the Disabled preset.
        var Settings = ThirdScreenIsConnected ? Disabled : Enabled;
        pathArray = Settings.Path;
        modeArray = Settings.Mode;

        // Call SetDisplayConfig to update the display config.
        // It works fine on a single windows boot, but the settings are not valid if I reboot.
        uint flag = (SDC_APPLY | SDC_SAVE_TO_DATABASE | SDC_ALLOW_CHANGES | SDC_USE_SUPPLIED_DISPLAY_CONFIG);
        int errorID = SetDisplayConfig((uint)pathArray.Length, pathArray, (uint)modeArray.Length, modeArray, flag);

        if (errorID == 0) { Console.WriteLine("Successfully updated Screen setup!"); }
        else { Console.WriteLine("ERROR: " + errorID); }
    }

}

我希望这些设置在多个Windows会话上均有效,但是它们却被视为无效。

重启后出现的错误是:87 -- INVALID PARAMETERS,当我尝试更改显示器的各个设置(在pathArraymodeArray中)时,也会出现该错误。

如果您足够在计算机上尝试使用此功能,请here's some usable Save/Load functionality, together with a simple context menu for WPF

(您必须通过上下文菜单上的“退出”按钮退出才能进行序列化)
(ps:您必须保存两次-一次启用第二个屏幕,一次禁用)

任何帮助将不胜感激! :)

1 个答案:

答案 0 :(得分:0)

我发现出现此问题是因为随着每台计算机的重新启动,GPU和主板的adapterID都在变化。

我找到了一种解决问题的好方法,而无需对先前的adapterID进行任何序列化!
这是它的代码:

static void UpdateAdapterID() {

    // Cache saved adapterIDs, for later comparison with the current ones.
    LUID savedAdapterID_GPU = Enabled.Path[0].sourceInfo.adapterId;
    LUID savedAdapterID_MB = Enabled.Path.First(x => x.sourceInfo.adapterId.LowPart != savedAdapterID_GPU.LowPart && x.sourceInfo.adapterId.LowPart != 0).sourceInfo.adapterId;

    bool isAdapterUpdated_GPU = savedAdapterID_GPU.LowPart == CurrentAdapterID_GPU.LowPart;
    bool isAdapterUpdated_MB = savedAdapterID_MB.LowPart == CurrentAdapterID_MB.LowPart;

    // Check if our saved states have already been updated.
    if (isAdapterUpdated_GPU && isAdapterUpdated_MB) { return; }

    for (int i = 0; i < Enabled.Path.Length; i++) {
        Update(ref Enabled.Path[i].sourceInfo.adapterId);
        Update(ref Enabled.Path[i].targetInfo.adapterId);
    }
    for (int i = 0; i < Disabled.Path.Length; i++) {
        Update(ref Disabled.Path[i].sourceInfo.adapterId);
        Update(ref Disabled.Path[i].targetInfo.adapterId);
    }

    for (int i = 0; i < Enabled.Mode.Length; i++) { Update(ref Enabled.Mode[i].adapterId); }
    for (int i = 0; i < Disabled.Mode.Length; i++) { Update(ref Disabled.Mode[i].adapterId); }



    void Update(ref LUID adapterID) {
        bool isInvalid = adapterID.LowPart == 0;
        bool isUpdated = adapterID.LowPart == CurrentAdapterID_GPU.LowPart || adapterID.LowPart == CurrentAdapterID_MB.LowPart;

        if (!isInvalid && !isUpdated) {
            bool adapterIsGPU = adapterID.LowPart == savedAdapterID_GPU.LowPart;
            if (adapterIsGPU) { adapterID = CurrentAdapterID_GPU; }
            else { adapterID = CurrentAdapterID_MB; }
        }
    }

    if (!isAdapterUpdated_GPU) { Console.WriteLine("Updated adapterID for GPU."); }
    if (!isAdapterUpdated_MB) { Console.WriteLine("Updated adapterID for MB."); }
}

Here's the whole script in case anyone can find it helpful :)