WPF將窗口置於桌面下方(可用於動態桌面)

WPF將窗口置於桌面下方(可用於動態桌面)

先來看一下效果:

動畫

介面元素很簡單,就一個Button按鈕,然後寫個定時器,定時更新Button按鈕中的內容為當前時間,下面來介紹下原理,和介面組成。

窗口介紹

Windows作業系統所有的地方都是窗口,可能這也是系統名字的由來吧,包括你看到的文件夾,桌面,右鍵菜單,這些都是由介面組成的, 這麼多窗口需要有一個合理的顯示,就需要用到我們的層級關係,比如兩個窗體誰顯示在前,誰顯示在後。

VS給我們提供了一個查找和查看窗口資訊的工具,叫做Spy++,在工具裡面:

image-20211217151458112

打開之後了,這裡給我們展示了當前系統所有的窗口資訊,你也可以點擊紅色框中的查找工具,來查看你想知道的窗口資訊:

image-20211217151754602

來演示一下如何查找窗口,點擊上方紅色框中的查找窗口按鈕,兩個隨便選一個,會彈出如下窗口:

image-20211217152008249

然後你點擊紅色區域中的這個控制項拖動到你想獲取的資訊窗口,就能看到當前窗口的詳細資訊了,包括窗口的句柄、標題、類。

比如我直接將圖標拖到桌面上,可以看到這是他顯示桌面的資訊:

image-20211217154929038

這裡我們關掉這個窗口, 回到Spy++的主介面,拖到最底部:

image-20211217155055733

可以看到, Progman Manager是桌面窗口的父窗口,前面小窗口圖標是灰色的表示的是此窗口是隱藏的(子窗口擁有和父窗口一致的顯示層級)。

原理操作

現在,我們只需要把我們的介面,也就是放到 Program Manager下面,然後再適當調整它的顯示順序,就可以了,但是這一塊我們不好操作。有一個其他路子就是給窗口發送一個特殊的消息,來讓我們有操作的空間。

只需要給 Program Manager窗口發送一個消息0x52C,就可以將Program Manager拆分為多個窗口,分別是Program Manager窗口和兩個WorkerW窗口。

下面是已經發送過此消息後的樣子:

image-20211217160422668

可以看到Program Manager下面已經什麼都沒有了,內容全都轉移到第一個WokerW窗口下,這時候我們只需要將我們的窗口掛在到Program Manager窗口的下方就能又有和它一樣的顯示層級了(窗口從下到上依次顯示,所以這裡Program Manager顯示在最底層),不過需要注意的是,在Program Manager和第一個WorkerW窗口之間,還存在另外一個WorkerW窗口,在我的系統中,它默認隱藏了,為了確保效果一致,我們需要手動將它隱藏起來。

具體操作

窗體的部分很簡單,就創建個窗體,帶一個按鈕,設置一些窗口基本的資訊,截個程式碼:

image-20211217161713144

程式碼部分也貼一下吧(複製程式碼注意你的命名空間):

<Window
    x:Class="BehindTheDesktop.MainWindow"
    xmlns="//schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="//schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="//schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:BehindTheDesktop"
    xmlns:mc="//schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="800"
    Height="450"
    AllowsTransparency="True"
    Background="Transparent"
    ResizeMode="NoResize"
    WindowStyle="None"
    mc:Ignorable="d">

    <Button
        x:Name="btnTime"
        Click="Button_Click"
        Content="開始"
        FontSize="300"
        Foreground="WhiteSmoke" />

</Window>

然後呢就是後台的程式碼的部分,獲取窗口資訊,查找窗口和向桌面窗口發送消息 0x52C,需要用到一些Win32的API,下面直接列出來。

    //Win32方法
    public static class Win32Func
    {
        [DllImport("user32.dll")]
        public static extern IntPtr FindWindow(string className, string winName);

        [DllImport("user32.dll")]
        public static extern IntPtr SendMessageTimeout(IntPtr hwnd, uint msg, IntPtr wParam, IntPtr lParam, uint fuFlage, uint timeout, IntPtr result);

        //查找窗口的委託 查找邏輯
        public delegate bool EnumWindowsProc(IntPtr hwnd, IntPtr lParam);
        [DllImport("user32.dll")]
        public static extern bool EnumWindows(EnumWindowsProc proc, IntPtr lParam);

        [DllImport("user32.dll")]
        public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string className, string winName);

        [DllImport("user32.dll")]
        public static extern bool ShowWindow(IntPtr hwnd, int nCmdShow);

        [DllImport("user32.dll")]
        public static extern IntPtr SetParent(IntPtr hwnd, IntPtr parentHwnd);
    }

下面的程式碼就是向窗口發送消息,並且隱藏中間WorkerW窗口的方法:

        /// <summary>
        /// 向桌面發送消息
        /// </summary>
        public void SendMsgToProgman()
        {
            // 桌面窗口句柄,在外部定義,用於後面將我們自己的窗口作為子窗口放入
            programHandle = Win32Func.FindWindow("Progman", null);

            IntPtr result = IntPtr.Zero;
            // 向 Program Manager 窗口發送消息 0x52c 的一個消息,超時設置為2秒
            Win32Func.SendMessageTimeout(programHandle, 0x52c, IntPtr.Zero, IntPtr.Zero, 0, 2, result);

            // 遍歷頂級窗口
            Win32Func.EnumWindows((hwnd, lParam) =>
            {
                // 找到第一個 WorkerW 窗口,此窗口中有子窗口 SHELLDLL_DefView,所以先找子窗口
                if (Win32Func.FindWindowEx(hwnd, IntPtr.Zero, "SHELLDLL_DefView", null) != IntPtr.Zero)
                {
                    // 找到當前第一個 WorkerW 窗口的,後一個窗口,及第二個 WorkerW 窗口。
                    IntPtr tempHwnd = Win32Func.FindWindowEx(IntPtr.Zero, hwnd, "WorkerW", null);

                    // 隱藏第二個 WorkerW 窗口
                    Win32Func.ShowWindow(tempHwnd, 0);
                }
                return true;
            }, IntPtr.Zero);
        }

然後在MainWindow的構造函數中調用下就行了:

        public MainWindow()
        {
            InitializeComponent();

            //向桌面發送消息
            SendMsgToProgman();
        }

最後貼一下頁面Button的Click方法:

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            // 設置當前窗口為 Program Manager的子窗口
            Win32Func.SetParent(new WindowInteropHelper(this).Handle, programHandle);

            //設置button背景色為透明
            btnTime.Background = new SolidColorBrush(Colors.Transparent);
            //設置當前介面的寬高,因為我是雙螢幕,所以不能設置全螢幕,就以這種方式來設置介面了
            this.Width = 1920;
            this.Height = 1080;
            this.Left = 0;
            this.Top = 0;

            //啟動定時器,更新Button按鈕中的內容
        }

關於改變Button按鈕中內容的定時器,這裡就不貼了,隨意發揮自己的想像吧!

簡單心得

既然可以把當前的介面放置到桌面圖標的下方, 那麼在介面中可以設置一些更加好玩的效果,比如播放一個影片,這就是我們的動態桌面了,要是再有點奇思妙想,弄個科技桌面,或者簡單粗暴做個俄羅斯方塊,也不是不可以! 自由發揮!

又水一篇!

哈哈!

Tags: