【问题标题】:Desktop wallpaper is not visible through a transparent AppBar通过透明的 AppBar 看不到桌面壁纸
【发布时间】:2014-05-24 16:51:16
【问题描述】:

我实际上想制作一个为自己保留桌面空间的侧边栏应用程序。 appBar 本身不是问题。但是我希望它是透明的。

示例:

在上图中,您可以看到stardock 是如何保留其桌面空间的,但我的背景图片仍然完全可见,并且应用程序本身是透明的。

当我运行我的 appBar 时,背景会自动变为黑色,并且我的背景会随之缩放。而且显然我也不能截图,所以我不能举例说明它的外观。

这是我的 AppBar 的代码:(注意:代码设置为 1920px 偏移量,因此它可以停靠在我的第二台显示器上(没有时间进行抽象编码。)

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Threading;

namespace ProjectSideBar
{
public enum ABEdge : int
{
    Left = 0,
    Top,
    Right,
    Bottom,
    None
}

internal static class AppBarFunctions
{
    [StructLayout(LayoutKind.Sequential)]
    private struct RECT
    {
        public int left;
        public int top;
        public int right;
        public int bottom;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct APPBARDATA
    {
        public int cbSize;
        public IntPtr hWnd;
        public int uCallbackMessage;
        public int uEdge;
        public RECT rc;
        public IntPtr lParam;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct MONITORINFO
    {
        public int cbSize;
        public RECT rcMonitor;
        public RECT rcWork;
        public int dwFlags;
    }

    private enum ABMsg : int
    {
        ABM_NEW = 0,
        ABM_REMOVE,
        ABM_QUERYPOS,
        ABM_SETPOS,
        ABM_GETSTATE,
        ABM_GETTASKBARPOS,
        ABM_ACTIVATE,
        ABM_GETAUTOHIDEBAR,
        ABM_SETAUTOHIDEBAR,
        ABM_WINDOWPOSCHANGED,
        ABM_SETSTATE
    }
    private enum ABNotify : int
    {
        ABN_STATECHANGE = 0,
        ABN_POSCHANGED,
        ABN_FULLSCREENAPP,
        ABN_WINDOWARRANGE
    }

    [DllImport("SHELL32", CallingConvention = CallingConvention.StdCall)]
    private static extern uint SHAppBarMessage(int dwMessage, ref APPBARDATA pData);

    [DllImport("User32.dll", CharSet = CharSet.Auto)]
    private static extern int RegisterWindowMessage(string msg);

    [DllImport("User32.dll", CharSet = CharSet.Auto)]
    private static extern IntPtr MonitorFromWindow(IntPtr hwnd, uint dwFlags);

    [DllImport("User32.dll", CharSet = CharSet.Auto)]
    private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFO mi);


    private const int MONITOR_DEFAULTTONEAREST = 0x2;
    private const int MONITORINFOF_PRIMARY = 0x1;

    private class RegisterInfo
    {
        public int CallbackId { get; set; }
        public bool IsRegistered { get; set; }
        public Window Window { get; set; }
        public ABEdge Edge { get; set; }
        public WindowStyle OriginalStyle { get; set; }
        public Point OriginalPosition { get; set; }
        public Size OriginalSize { get; set; }
        public ResizeMode OriginalResizeMode { get; set; }


        public IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam,
                                IntPtr lParam, ref bool handled)
        {
            if (msg == CallbackId)
            {
                if (wParam.ToInt32() == (int)ABNotify.ABN_POSCHANGED)
                {
                    ABSetPos(Edge, Window);
                    handled = true;
                }
            }
            return IntPtr.Zero;
        }

    }
    private static Dictionary<Window, RegisterInfo> s_RegisteredWindowInfo
        = new Dictionary<Window, RegisterInfo>();
    private static RegisterInfo GetRegisterInfo(Window appbarWindow)
    {
        RegisterInfo reg;
        if (s_RegisteredWindowInfo.ContainsKey(appbarWindow))
        {
            reg = s_RegisteredWindowInfo[appbarWindow];
        }
        else
        {
            reg = new RegisterInfo()
            {
                CallbackId = 0,
                Window = appbarWindow,
                IsRegistered = false,
                Edge = ABEdge.Top,
                OriginalStyle = appbarWindow.WindowStyle,
                OriginalPosition = new Point(appbarWindow.Left, appbarWindow.Top),
                OriginalSize =
                    new Size(appbarWindow.ActualWidth, appbarWindow.ActualHeight),
                OriginalResizeMode = appbarWindow.ResizeMode,
            };
            s_RegisteredWindowInfo.Add(appbarWindow, reg);
        }
        return reg;
    }

    private static void RestoreWindow(Window appbarWindow)
    {
        RegisterInfo info = GetRegisterInfo(appbarWindow);

        appbarWindow.WindowStyle = info.OriginalStyle;
        appbarWindow.ResizeMode = info.OriginalResizeMode;
        appbarWindow.Topmost = false;

        Rect rect = new Rect(info.OriginalPosition.X, info.OriginalPosition.Y,
            info.OriginalSize.Width, info.OriginalSize.Height);
        appbarWindow.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle,
                new ResizeDelegate(DoResize), appbarWindow, rect);

    }

    public static void SetAppBar(Window appbarWindow, ABEdge edge)
    {
        RegisterInfo info = GetRegisterInfo(appbarWindow);

        info.Edge = edge;

        APPBARDATA abd = new APPBARDATA();
        abd.cbSize = Marshal.SizeOf(abd);
        abd.hWnd = new WindowInteropHelper(appbarWindow).Handle;

        if (edge == ABEdge.None)
        {
            if (info.IsRegistered)
            {
                SHAppBarMessage((int)ABMsg.ABM_REMOVE, ref abd);
                info.IsRegistered = false;
            }
            RestoreWindow(appbarWindow);
            return;
        }

        if (!info.IsRegistered)
        {
            info.IsRegistered = true;
            info.CallbackId = RegisterWindowMessage("AppBarMessage");
            abd.uCallbackMessage = info.CallbackId;

            uint ret = SHAppBarMessage((int)ABMsg.ABM_NEW, ref abd);

            HwndSource source = HwndSource.FromHwnd(abd.hWnd);
            source.AddHook(new HwndSourceHook(info.WndProc));
        }

        appbarWindow.WindowStyle = WindowStyle.None;
        appbarWindow.ResizeMode = ResizeMode.NoResize;
        appbarWindow.Topmost = true;

        ABSetPos(info.Edge, appbarWindow);
    }

    private delegate void ResizeDelegate(Window appbarWindow, Rect rect);
    private static void DoResize(Window appbarWindow, Rect rect)
    {
        appbarWindow.Width = rect.Width;
        appbarWindow.Height = rect.Height;
        appbarWindow.Top = rect.Top;
        appbarWindow.Left = rect.Left;
    }

    private static void GetActualScreenData(ABEdge edge, Window appbarWindow, ref int leftOffset, ref int topOffset, ref int actualScreenWidth, ref int actualScreenHeight)
    {
        IntPtr handle = new WindowInteropHelper(appbarWindow).Handle;
        IntPtr monitorHandle = MonitorFromWindow(handle, MONITOR_DEFAULTTONEAREST);

        MONITORINFO mi = new MONITORINFO();
        mi.cbSize = Marshal.SizeOf(mi);

        if (GetMonitorInfo(monitorHandle, ref mi))
        {
            if (mi.dwFlags == MONITORINFOF_PRIMARY)
            {
                return;
            }
            leftOffset = mi.rcWork.left;
            topOffset = mi.rcWork.top;
            actualScreenWidth = mi.rcWork.right - leftOffset;
            actualScreenHeight = mi.rcWork.bottom - mi.rcWork.top;
        }
    }

    private static void ABSetPos(ABEdge edge, Window appbarWindow)
    {
        APPBARDATA barData = new APPBARDATA();
        barData.cbSize = Marshal.SizeOf(barData);
        barData.hWnd = new WindowInteropHelper(appbarWindow).Handle;
        barData.uEdge = (int)edge;

        int leftOffset = 1920;
        int topOffset = 0;
        int actualScreenWidth = (int)SystemParameters.PrimaryScreenWidth;
        int actualScreenHeight = (int)SystemParameters.PrimaryScreenHeight;

        GetActualScreenData(edge, appbarWindow, ref leftOffset, ref topOffset, ref actualScreenWidth, ref actualScreenHeight);

        if (barData.uEdge == (int)ABEdge.Left || barData.uEdge == (int)ABEdge.Right)
        {
            barData.rc.top = topOffset;
            barData.rc.bottom = actualScreenHeight;
            if (barData.uEdge == (int)ABEdge.Left)
            {
                barData.rc.left = leftOffset;
                barData.rc.right = (int)Math.Round(appbarWindow.ActualWidth) + leftOffset;
            }
            else
            {
                barData.rc.right = actualScreenWidth + leftOffset;
                barData.rc.left = barData.rc.right - (int)Math.Round(appbarWindow.ActualWidth);
            }
        }
        else
        {
            barData.rc.left = leftOffset;
            barData.rc.right = actualScreenWidth + leftOffset;
            if (barData.uEdge == (int)ABEdge.Top)
            {
                barData.rc.top = topOffset;
                barData.rc.bottom = (int)Math.Round(appbarWindow.ActualHeight) + topOffset;
            }
            else
            {
                barData.rc.bottom = actualScreenHeight + topOffset;
                barData.rc.top = barData.rc.bottom - (int)Math.Round(appbarWindow.ActualHeight);
            }
        }

        SHAppBarMessage((int)ABMsg.ABM_QUERYPOS, ref barData);
        SHAppBarMessage((int)ABMsg.ABM_SETPOS, ref barData);

        Rect rect = new Rect((double)barData.rc.left, (double)barData.rc.top,
            (double)(barData.rc.right - barData.rc.left), (double)(barData.rc.bottom - barData.rc.top));
        //This is done async, because WPF will send a resize after a new appbar is added.  
        //if we size right away, WPFs resize comes last and overrides us.
        appbarWindow.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle,
            new ResizeDelegate(DoResize), appbarWindow, rect);
    }
}
}

这是我主窗口中的代码:

<Window x:Class="ProjectSideBar.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Loaded="Window_Loaded" Height="1080" Width="300" ResizeMode="NoResize" ShowInTaskbar="False" Background="{x:Null}">
<Grid>
    <Label Content="My application" HorizontalAlignment="Left" Margin="27,40,0,0" VerticalAlignment="Top" Foreground="White"/>
</Grid>

有没有人知道如何让它像stardock? 此外,我不了解窗口句柄的工作原理,也不了解我使用的大部分代码。如果有人可以解释(或提供一些阅读材料)窗口句柄的作用以及如何使用它们,那就太好了。

作为奖励,我会制作一个 ClassLibrary 并上传给大家使用。

提前致谢。

【问题讨论】:

    标签: c# wpf desktop wallpaper appbar


    【解决方案1】:

    编辑 1

    关于什么是窗口句柄有几个问题:

    What is a Windows Handle?

    Difference between HANDLE and HWND in Windows API?

    现在如果我们看一下Windows Data Types

    HWND    
    A handle to a window.
    This type is declared in WinDef.h as follows:
    typedef HANDLE HWND;
    
    HANDLE  
    A handle to an object.
    This type is declared in WinNT.h as follows:
    typedef PVOID HANDLE;
    
    PVOID   
    A pointer to any type.
    This type is declared in WinNT.h as follows:
    typedef void *PVOID;
    

    换句话说,它是一个空指针,它指向类型未知的东西,这是故意的。

    这里有一些解释:What is a void pointer in C++?

    结论

    一个 HWND 或窗口句柄指向这个类:Windows

    在 .NET 框架中,您不能直接访问此类型,您可以使用 FormWindow,它们本身使用该类型。现在,当您必须像正在做的那样做一些较低级别的事情时,您可以在 Window 上执行它们,因此您需要指定要执行此操作的 HWND。现在在 .NET 中,您通常将其表示为 IntPtr


    编辑 实际上有一个背景,但由于某些原因,WPF 应用程序是黑色的。

    我的建议仍然适用。


    我的appBar背景变黑了

    这是有道理的,因为您要保留后面没有任何东西的空间,因此它是黑色的。

    可能的解决方案:

    • 获取当前壁纸
    • 获取与应用栏一样高的区域
    • 将该区域设置为应用栏的背景
    • 将剩余区域设为当前 Windows 背景
    • 当您的应用栏关闭时恢复原始背景

    除非他们告诉你怎么做,否则我们无法真正说出 Stardock 是怎么做的。

    如果 MSDN 中没有关于它的内容和/或您不能从 C# 中使用,那么 他们可能就是这样做的

    【讨论】:

    • 感谢您的回复,我已经想到了这个,但实际上我的背景图像缩小到了新的剩余分辨率。所以仍然不适合。如果你知道我的意思,它从 1920x1080 到 1620x1080(假设我的 appbar 是 300px 宽)。
    【解决方案2】:

    【讨论】:

    • 没错,我正要发布它,但你之前发现了它:D
    • @Aybe 你能否向我解释一下代码中发生了什么,因为我试图制作一个很好的抽象 dll,但我无法弄清楚这些东西是如何工作的以及会发生什么。或者给我一个参考。如果您将其表述为答案,我将接受它作为答案。感谢您的帮助。
    • 编辑您的问题并解释您不理解的内容,我会看看。
    • 你不明白窗口句柄是什么,你只需要知道吗?
    • 我已经做了一些解释,希望您现在清楚:D 关于您的库,如果您打算发布它,我强烈建议您制作一个 Nuget 包;人们使用它会更容易,你也会知道有多少人下载了这很酷顺便说一句。您还应该将您的源代码放在 GitHub 上,以便人们最终可以增强它。这是我最近发布的一个库示例:nuget.org/packages/SonicApigithub.com/aybe/SonicApi
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-10-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-03
    • 1970-01-01
    相关资源
    最近更新 更多