WPF將窗口置於桌面下方(可用於動態桌面)
WPF將窗口置於桌面下方(可用於動態桌面)
先來看一下效果:
介面元素很簡單,就一個Button按鈕,然後寫個定時器,定時更新Button按鈕中的內容為當前時間,下面來介紹下原理,和介面組成。
窗口介紹
Windows作業系統所有的地方都是窗口,可能這也是系統名字的由來吧,包括你看到的文件夾,桌面,右鍵菜單,這些都是由介面組成的, 這麼多窗口需要有一個合理的顯示,就需要用到我們的層級關係,比如兩個窗體誰顯示在前,誰顯示在後。
VS給我們提供了一個查找和查看窗口資訊的工具,叫做Spy++,在工具裡面:
打開之後了,這裡給我們展示了當前系統所有的窗口資訊,你也可以點擊紅色框中的查找工具,來查看你想知道的窗口資訊:
來演示一下如何查找窗口,點擊上方紅色框中的查找窗口按鈕,兩個隨便選一個,會彈出如下窗口:
然後你點擊紅色區域中的這個控制項拖動到你想獲取的資訊窗口,就能看到當前窗口的詳細資訊了,包括窗口的句柄、標題、類。
比如我直接將圖標拖到桌面上,可以看到這是他顯示桌面的資訊:
這裡我們關掉這個窗口, 回到Spy++的主介面,拖到最底部:
可以看到, Progman Manager是桌面窗口的父窗口,前面小窗口圖標是灰色的表示的是此窗口是隱藏的(子窗口擁有和父窗口一致的顯示層級)。
原理操作
現在,我們只需要把我們的介面,也就是放到 Program Manager下面,然後再適當調整它的顯示順序,就可以了,但是這一塊我們不好操作。有一個其他路子就是給窗口發送一個特殊的消息,來讓我們有操作的空間。
只需要給 Program Manager窗口發送一個消息0x52C,就可以將Program Manager拆分為多個窗口,分別是Program Manager窗口和兩個WorkerW窗口。
下面是已經發送過此消息後的樣子:
可以看到Program Manager下面已經什麼都沒有了,內容全都轉移到第一個WokerW窗口下,這時候我們只需要將我們的窗口掛在到Program Manager窗口的下方就能又有和它一樣的顯示層級了(窗口從下到上依次顯示,所以這裡Program Manager顯示在最底層),不過需要注意的是,在Program Manager和第一個WorkerW窗口之間,還存在另外一個WorkerW窗口,在我的系統中,它默認隱藏了,為了確保效果一致,我們需要手動將它隱藏起來。
具體操作
窗體的部分很簡單,就創建個窗體,帶一個按鈕,設置一些窗口基本的資訊,截個程式碼:
程式碼部分也貼一下吧(複製程式碼注意你的命名空間):
<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按鈕中內容的定時器,這裡就不貼了,隨意發揮自己的想像吧!
簡單心得
既然可以把當前的介面放置到桌面圖標的下方, 那麼在介面中可以設置一些更加好玩的效果,比如播放一個影片,這就是我們的動態桌面了,要是再有點奇思妙想,弄個科技桌面,或者簡單粗暴做個俄羅斯方塊,也不是不可以! 自由發揮!
又水一篇!
哈哈!