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="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按鈕中內容的定時器,這裡就不貼了,隨意發揮自己的想象吧!
簡單心得
既然可以把當前的介面放置到桌面圖示的下方, 那麼在介面中可以設定一些更加好玩的效果,比如播放一個視訊,這就是我們的動態桌面了,要是再有點奇思妙想,弄個科技桌面,或者簡單粗暴做個俄羅斯方塊,也不是不可以! 自由發揮!
又水一篇!
哈哈!