SetLayeredWindowAttributes

时间:2017-05-30 16:37:42

标签: c# wpf winapi pinvoke

为了便于调试,我在我的WPF应用程序中添加了一个控制台窗口,我可以在其中监视应用程序中的所有正在进行的事件。我还添加了一些很好的方法,可以自动将窗口移动到我的辅助屏幕,最大化它并将其不透明度设置为90%。

使用调试配置,它可以正常工作并且符合预期。但是一旦我使用发布配置,我就会发现一些奇怪的异常。使用此P / Invoke实现:

[DllImport("user32.dll", SetLastError = true)]
private static extern bool SetLayeredWindowAttributes(IntPtr hwnd, uint crKey, byte bAlpha, uint dwFlags);

public static void SetOpacity(byte alpha)
{
    IntPtr handle = GetConsoleWindow();
    if (handle == IntPtr.Zero) return;

    Logging.Trace("Found console handle.");
    Logging.Trace($"Setting console opacity to: {alpha}.");

    if (!SetLayeredWindowAttributes(handle, 0, alpha, 0x2))
    {
        int error = Marshal.GetLastWin32Error();

        Logging.Trace($"Return value: {error}");
        if (error != 0)
            throw new Win32Exception(error);
    }

    Logging.Trace("Set LW attributes.");
}

在没有记录的情况下运行它我回来了“ 87 :参数不正确”。当我添加日志时,我得到(很奇怪)“ 183 :当该文件已经存在时,无法创建文件。”

我真的不明白为什么会这样。为什么伐木会影响结果。为什么这可以使用调试配置。

2 个答案:

答案 0 :(得分:1)

SetLayeredWindowAttributes()仅适用于具有WS_EX_LAYERED属性的窗口。有可能控制台窗口没有那种样式,这可以解释你所看到的错误87。因此,您必须事先使用SetWindowLongPtr()设置样式,例如:

[DllImport("user32.dll", EntryPoint="GetWindowLong", SetLastError=true)]
private static extern IntPtr GetWindowLongPtr32(IntPtr hWnd, int nIndex);

[DllImport("user32.dll", EntryPoint="GetWindowLongPtr", SetLastError=true)]
private static extern IntPtr GetWindowLongPtr64(IntPtr hWnd, int nIndex);

[DllImport("user32.dll", EntryPoint="SetWindowLong", SetLastError=true)]
private static extern IntPtr SetWindowLongPtr32(IntPtr hWnd, int nIndex, IntPtr dwNewLong);

[DllImport("user32.dll", EntryPoint="SetWindowLongPtr", SetLastError=true)]
private static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, int nIndex, IntPtr dwNewLong);

[DllImport("user32.dll", SetLastError=true)]
private static extern bool SetLayeredWindowAttributes(IntPtr hwnd, uint crKey, byte bAlpha, uint dwFlags); 

[DllImport("kernel32.dll")]
static extern void SetLastError(uint dwErrorCode);

private static IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex)
{
    if (IntPtr.Size == 8)
        return GetWindowLongPtr64(hWnd, nIndex);
    else
        return GetWindowLongPtr32(hWnd, nIndex);
}

private static IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong)
{
    if (IntPtr.Size == 8)
        return SetWindowLongPtr64(hWnd, nIndex, dwNewLong);
    else
        return SetWindowLongPtr32(hWnd, nIndex, dwNewLong);
}

private const int GWL_EXSTYLE = -20;
private const int WS_EX_LAYERED = 0x00080000;

public static void SetOpacity(byte alpha)
{
    if (alpha == byte.MaxValue)
    {
        MakeOpaque();
        return;
    }

    IntPtr handle = GetConsoleWindow();
    if (handle == IntPtr.Zero)
        return;

    Logging.Trace("Found console handle.");

    int error;

    SetLastError(0);
    int winFlags = (int) GetWindowLongPtr(handle, GWL_EXSTYLE);
    if (winFlags == 0)
    {
        error = Marshal.GetLastWin32Error();
        if (error != 0)
        {
            Logging.Trace($"GetWindowLongPtr error: {error}");
            throw new Win32Exception(error);
        }
    }

    if ((winFlags & WS_EX_LAYERED) == 0)
    {
        Logging.Trace($"Setting console layered style.");

        winFlags |= WS_EX_LAYERED;
        SetLastError(0);
        if (SetWindowLongPtr(handle, GWL_EXSTYLE, new IntPtr(winFlags)) == 0)
        {
            error = Marshal.GetLastWin32Error();
            if (error != 0)
            {
                Logging.Trace($"SetWindowLongPtr error: {error}");
                throw new Win32Exception(error);
            }
        }
    }

    Logging.Trace($"Setting console opacity to: {alpha}."); 

    if (!SetLayeredWindowAttributes(handle, 0, alpha, 0x2))
    {
        error = Marshal.GetLastWin32Error();
        Logging.Trace($"SetLayeredWindowAttributes error: {error}");
        throw new Win32Exception(error);
    }

    Logging.Trace("Set LW attributes.");
}

答案 1 :(得分:0)

经过大量研究后我发现SetLayeredWindowAttributes方法还有更多内容。我无法解释为什么在构建调试配置时这确实可以正常工作,但为了使其与发布配置一起工作,您需要首先使用SetWindowLongPtr增强窗口样式(您还需要GetWindowLongPtr )。 请注意,您应该使用带有后缀的方法' Ptr'因为只有那些适用于x86和x64 Windows版本

[DllImport("user32.dll", EntryPoint = "SetWindowLong")]
private static extern int SetWindowLongPtr(HandleRef hWnd, int nIndex, int dwNewLong);

[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")]
private static extern IntPtr SetWindowLongPtr64(HandleRef hWnd, int nIndex, IntPtr dwNewLong);

[DllImport("user32.dll", EntryPoint = "GetWindowLong")]
private static extern IntPtr GetWindowLongPtr32(IntPtr hWnd, int nIndex);

[DllImport("user32.dll", EntryPoint = "GetWindowLongPtr")]
private static extern IntPtr GetWindowLongPtr64(IntPtr hWnd, int nIndex);

private const int GWL_EXSTYLE = -20;
private const int WS_EX_LAYERED = 0x00080000;

public static void SetOpacity(byte alpha)
{
    if (alpha == byte.MaxValue)
    {
        MakeOpaque();
        return;
    }

    IntPtr handle = GetConsoleWindow();
    if (handle == IntPtr.Zero) return;

    int error;

    if (IntPtr.Size == 4) // x86
    {
        int winFlags = GetWindowLongPtr32(handle, GWL_EXSTYLE).ToInt32();
        winFlags |= WS_EX_LAYERED;
        if (SetWindowLongPtr(new HandleRef(Core.IDE, handle), GWL_EXSTYLE, winFlags) == 0)
        {
            error = Marshal.GetLastWin32Error();
            throw new Win32Exception(error);
        }
    }
    else // x64
    {
        long winFlags = GetWindowLongPtr64(handle, GWL_EXSTYLE).ToInt64();
        winFlags |= WS_EX_LAYERED;
        if (SetWindowLongPtr64(new HandleRef(Core.IDE, handle), GWL_EXSTYLE, new IntPtr(winFlags)).ToInt64() == 0)
        {
            error = Marshal.GetLastWin32Error();
            throw new Win32Exception(error);
        }
    }

    if (!SetLayeredWindowAttributes(handle, 0, alpha, 0x2))
    {
        error = Marshal.GetLastWin32Error();

        Logging.Trace($"Return value: {error}");
        if (error != 0)
            throw new Win32Exception(error);
    }
}

有了这一切,两种架构都能完美运行。简而言之,您可以在MSDN上找到该代码,您应该可以轻松地将其转换为C#。