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

醜萌氣質狗發表於2021-12-17

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="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:BehindTheDesktop"
    xmlns:mc="http://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按鈕中內容的定時器,這裡就不貼了,隨意發揮自己的想象吧!

簡單心得

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

又水一篇!

哈哈!

相關文章