SetWindowDisplayAffinity 是否适用于 ComboBox 下拉列表?

问题描述 投票:0回答:1

半功能答案:

尽管循环遍历每个控件一开始似乎是一个不错的概念,但执行得很糟糕。某些控件可能不会出现在控件列表中。 Spy++ 指示使用 ComboBox 时出现的小迷你窗口是一个编辑控件。您可以采用一种可以改进的 hacky 方法,以迭代进程中的每个窗口并向其应用 SetWindowDisplayAffinity 函数。这就是我决定要做的事情,直到我能弄清楚如何简化“修复”。

delegate bool EnumThreadDelegate(IntPtr Handle, IntPtr LPARAM);

[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi)]
public static extern bool EnumThreadWindows(int ThreadID, EnumThreadDelegate ETD, IntPtr LPARAM);

public static IEnumerable<IntPtr> EnumerateProcessWindowHandles(int TargetProcessID)
{
    // We could possibly check the list of handles for our target.
    // However, I just brute-force all of the handles and hide them all.
    var Handles = new List<IntPtr>();

    foreach(ProcessThread Thread in Process.GetProcessesById(TargetProcessID).Threads)
    {
        EnumThreadWindows(Thread.Id, (HWND, LPARAM) => {
            Handles.Add(HWND);

            return true;
        }, IntPtr.Zero);
    }

    return Handles;
}

void ApplySWDA()
{
    // The up-side to this function is that it applies to all handles.
    // This removes the need to repeat the same SWDA call to our Form.
    foreach(var Handle in EnumerateProcessWindowHandles(Process.GetCurrentProcess().Id))
    {
        SetWindowDisplayAffinity(Handle, DisplayAffinity.ExcludeFromCapture);
    }
}

我发现实现此“修复”并避免不断重新应用的最简单方法是创建一个 bool 来指示您已经运行了该函数,或者添加一个根据是否运行返回 bool 值的函数程序UI已经通过SetWindowDisplayAffinity调整过了。

我选择的方法是挂钩 ComboBox DropDown 事件并使用那里的代码。然后,我在代码中设置一个标志,以防止再次触发相同的函数:

public static bool AlreadyRanSWDA = false;

// This action event can be created in Visual Studio's Event Handler UI.
void MyComboBox_DropDown(object Sender, EventArgs EventArguments)
{
    if(!AlreadyRanSWDA)
    {
        foreach(var Handle in EnumerateProcessWindowHandles(Process.GetCurrentProcess().Id))
        {
            SetWindowDisplayAffinity(Handle, DisplayAffinity.ExcludeFromCapture);
        }

        // This should prevent the above code from tripping again.
        AlreadyRanSWDA = true;
    }
}
  • 以下是最初提出的问题。以上是我的解决方案。

我一直在摆弄我的沙箱应用程序,在将它们提交到主项目之前测试功能。这些功能之一是能够使用 SetWindowDisplayAffinity 隐藏程序的 UI,以防止捕获程序。当我尝试使用 Windows 中的屏幕捕获功能或 OBS Studio 等屏幕录制应用程序时,它工作得很好,但当我使用 ComboBox 控件下拉可选项目列表时,就会出现问题。

看来 SetWindowDisplayAffinity 对显示的菜单没有影响。我尝试了“监视器显示关联性”选项和较新的“排除捕获”模式,但两者的结果相同。我不确定访问 ComboBox 列表时创建的控件(或 UI)是否有自己不受影响的句柄,或者 SetWindowDisplayAffinity 是否只是忽略 ComboBox。

我提供了两个例子,希望有助于澄清情况。红色文本和红色框是我看到的,而黄色文本和黄色框是屏幕捕获实用程序看到的。显示亲和力由浅蓝色文本指示。

SetWindowDisplayAffinity is disabled. When using screen-capture software, all UI components become visible.& SetWindowDisplayAffinity is enabled and configured. All UI components should be visible exclusively to the user and not the screen-capture program. The ComboBox list appears to ignore this request.

我使用的代码很混乱(因为它仅用于测试),但我选择对其进行修改以便于阅读。您可以在下面查看我的测试项目中使用的代码:

这是我使用的 DLL 导入请求。它似乎没有造成任何问题,并且似乎是有效的导入请求。不会生成 Win32 错误:

[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi)]
public static extern uint SetWindowDisplayAffinity(IntPtr Handle, DisplayAffinity Affinity);

public enum DisplayAffinity : uint
{
    None = 0x0,
    Monitor = 0x1,
    ExcludeFromCapture = 0x11
}

这就是我使用API调用的方式。第一个代码块是我通常使用的“官方方式”,而第二个和第三个代码块是我所做的“黑客尝试”:

void HideFromCapture()
{
    // <Handle> being the Form's main window handle. In this case, <MainForm.Handle>.
    SetWindowDisplayAffinity(Handle, DisplayAffinity.ExcludeFromCapture);
    // I would call <HideFromCapture()> once all UI components are loaded.
}
void HideFromCaptureFixOne()
{
    // Now I'm attempting to apply the call to both the Form and the ComboBox directly.
    SetWindowDisplayAffinity(Handle, DisplayAffinity.ExcludeFromCapture);
    SetWindowDisplayAffinity(MyComboBox.Handle, DisplayAffinity.ExcludeFromCapture);
    // As expected, this had no effect.
}
void HideFromCaptureOrLoseAllHope()
{
    // I've determined that brute-forcing will undoubtedly cure everything wrong.
    foreach(Control Target in Controls)
    {
        try
        {
            // I'm quite sure this is NOT how you should use this function. Oh well...
            SetWindowDisplayAffinity(Target.Handle, DisplayAffinity.ExcludeFromCapture);
        }
        catch
        {
            // Ignorance is bliss.
            continue;
        }
    }

    /*
     * Now I want to reinstate that I am calling these functions when running my
     * program. It does work on the Form but not the ComboBox. I've even tried
     * the Control loop method above and I even tried calling this function in a loop.
     * It didn't work either. It did make the program chomp on memory however. No surprise.
    */
}

所以现在我陷入困境了。我很犹豫是否要在这里提出问题,因为我觉得我在尝试“解决问题”时回答了自己的问题,但在测试我的项目时它一直困扰着我。我不喜欢开发我认为无法正常运行的代码,导致此功能显得很突出,并且无法将其纳入官方项目构建中。我已经确定,就这个问题寻求一些帮助不会有什么坏处,如果我无法解决它,我就会用我所拥有的来弥补。预先感谢。

c# .net windows winforms winapi
1个回答
0
投票

答案已添加到原帖中。如果您有其他方法,请分享。我使用的策略似乎没有任何问题。我打算对其进行更多测试以避免任何内存问题,但到目前为止,代码运行完美,没有产生太大的惩罚影响。我添加了一项检查以防止相同的“修复”被应用两次,这应该可以避免任何重新调用问题。

该修复基于针对不同问题的不同解决方案。您可以在此处查看该解决方案:https://stackoverflow.com/a/2584672/23223164

答案的简短版本是正确地循环我们的进程拥有的窗口句柄并对它们应用SetWindowDisplayAffinity。我使用的控制循环方法似乎只适用于由 WinForms 父级(?)创建的窗口句柄。不过我可能是错的。不过,这种新方法的工作原理没有任何问题。

© www.soinside.com 2019 - 2024. All rights reserved.