wallpaper是一款優(yōu)秀的動態(tài)壁紙軟件,除了播放動畫以外,還可以執(zhí)行程序,甚至可以實時響應(yīng)鼠標移動。 原理分析windows的桌面是由不同的二窗體構(gòu)成,包括圖標層,背景層,背景層顯示桌面壁紙,圖標層放置圖標,且圖標層背景透明,因此可以直接看到后面的背景層,鼠標右鍵彈出菜單也是在圖標層完成。wallpaper在圖標層和背景層之間插入了自己的窗口,因此可以顯示動畫,執(zhí)行代碼。前面已經(jīng)提到圖標層是一個透明的覆蓋全屏的大窗口,因此鼠標事件只會在圖標層響應(yīng),而wallpaper可以實時響應(yīng)鼠標可能是利用了Hook攔截了鼠標事件,并加入自己代碼。 既然知道了原理就可以自己實現(xiàn)。 C#實現(xiàn)界面繪制首先創(chuàng)建兩個窗體,一個用來播放視頻,一個用來控制 上圖是控制窗口,也是主窗口。 另一個視頻窗口較為簡單,直接用MediaPlayer覆蓋全屏就行,注意需要設(shè)置WindowState為Maximized,即啟動時立即最大化,同時播放器要隱藏ui,即設(shè)置uiMode為none。 在主窗體的load事件里新建VideoForm。為了讓VideoForm能夠夾在圖標層和背景層中間,需要將VideoForm的父窗體設(shè)置為背景窗體。 查找句柄現(xiàn)在需要查找背景窗體的句柄,使用窗口查看器發(fā)現(xiàn)背景窗體沒有窗體名稱,因此無法直接定位,但是我們知道它的類名是WorkW,它的父窗體是Program Manager,所以我們可以遍歷所有WorkW窗體,如果其中一個窗體的父窗體是Program Manager,那么這個窗體就是背景窗體。 C#不支持直接這種接近底層的操作,因此需要調(diào)用user32.dll實現(xiàn) [DllImport("user32.dll", EntryPoint = "SetParent")]
private static extern int SetParent(int hWndChild,int hWndNewParent);
[DllImport("user32.dll", EntryPoint = "FindWindowA")]
private static extern IntPtr FindWindowA(string lpClassName, string lpWindowName);
[DllImport("user32.dll", EntryPoint = "FindWindowExA")]
private static extern IntPtr FindWindowExA(IntPtr hWndParent, IntPtr hWndChildAfter, string lpszClass, string lpszWindow);
[DllImport("user32.dll", EntryPoint = "GetClassNameA")]
private static extern IntPtr GetClassNameA(IntPtr hWnd, IntPtr lpClassName, int nMaxCount);
[DllImport("user32.dll", EntryPoint = "GetParent")]
private static extern IntPtr GetParent(IntPtr hWnd);
public static void SetFather(Form form)
{
SetParent((int)form.Handle, GetBackground());
}
private static int GetBackground()
{
unsafe
{
IntPtr background = IntPtr.Zero;
IntPtr father = FindWindowA("progman", "Program Manager");
IntPtr workerW = IntPtr.Zero;
do
{
workerW = FindWindowExA(IntPtr.Zero, workerW, "workerW", null);
if (workerW != IntPtr.Zero)
{
char[] buff = new char[200];
IntPtr b = Marshal.UnsafeAddrOfPinnedArrayElement(buff, 0);
int ret = (int)GetClassNameA(workerW, b, 400);
if (ret == 0) throw new Exception("出錯");
}
if (GetParent(workerW) == father)
{
background = workerW;
}
} while (workerW != IntPtr.Zero);
return (int)background;
}
}
其中GetBackground函數(shù)負責查找背景層窗體,SetFather負責把一個窗體設(shè)置成另一個窗體的子窗體。為了使用指針功能,需要先開啟不安全的代碼功能 :項目—??屬性(??是你的項目名稱)—允許不安全代碼。 這個方法在Windows 10 21H1 19043.1110上測試有效,但是不保證在其他系統(tǒng)有效,例如,在vista系統(tǒng)上就會返回空指針,這可能是因為vista系統(tǒng)上的背景窗體不滿足上面所講的關(guān)系。一旦返回空指針,會導致設(shè)置父窗體失敗,最后視頻會在圖標層上方播放,此時的動態(tài)壁紙軟件就徹底變成了一個全屏播放器。 如果遇到上面這種情況,可以使用MicrosoftSpy來查找背景窗體,并根據(jù)具體情況改寫上面的代碼。 這里利用了windows窗口的一個特性:如果A窗體在B窗體上面,那么A窗體也會在B窗體的子窗體上面。 按鈕事件給控制窗體的四個按鈕寫上事件 private void Form1_Load(object sender, EventArgs e)
{
main = new VideoForm();
player = main.player;
Window.SetFather(main);
main.Show();
}
private void button1_Click(object sender, EventArgs e)//打開
{
OpenFileDialog open = new OpenFileDialog();
open.Filter = "媒體文件(所有類型)|*.mp4;*.mpeg;*.wma;*.wmv;*.wav;*.avi|所有文件|*.*";
if (open.ShowDialog() == DialogResult.OK)
{
player.URL = open.FileName;
}
}
private void button2_Click(object sender, EventArgs e)//播放
{
player.Ctlcontrols.play();
}
private void button3_Click(object sender, EventArgs e)//暫停
{
player.Ctlcontrols.pause();
}
private void button4_Click(object sender, EventArgs e)//退出
{
main.Dispose();
System.Environment.Exit(0);
}
其中main是視頻播放窗體,player是播放器 運行 點擊退出 刷新背景雖然程序退出了,但是桌面變成了一張白紙,極其難看,目前暫不知道為什么會發(fā)生這種情況,個人猜測是windows考慮到背景是一張靜態(tài)圖,所以不會實時刷新,而剛剛被覆蓋掉的地方就會保持最后一次刷新的顏色,剛才點擊“退出”時,由于先dispose了視頻播放窗體,導致背景變成白板,如果不點擊“退出”,直接結(jié)束進程,那么背景就會變成黑板,因為MediaPlayer就是黑色的 既然如此,我們只需要讓背景刷新一下就可以,顯然在切換壁紙的時候,windows不得不刷新背景,所以我們可以先獲取當前壁紙,然后把壁紙切換成當前壁紙,這樣實際效果看起來沒有任何變化,但是讓windows為我們刷新了一次背景。 [DllImport("user32.dll", EntryPoint = "SystemParametersInfo")]
public static extern int SystemParametersInfo(int uAction, int uParam, StringBuilder lpvParam, int fuWinIni);
public static bool Refresh()
{
StringBuilder wallpaper = new StringBuilder(200);
SystemParametersInfo(0x73, 200, wallpaper, 0);
int ret = SystemParametersInfo(20, 1, wallpaper, 3);
if(ret != 0)
{
RegistryKey hk = Registry.CurrentUser;
RegistryKey run = hk.CreateSubKey(@"Control Panel\Desktop\");
run.SetValue("Wallpaper", wallpaper.ToString());
return true;
}
return false;
}
改寫“退出”按鈕事件 private void button4_Click(object sender, EventArgs e)//退出
{
main.Hide();
this.Hide();
Window.Refresh();
main.Dispose();
System.Environment.Exit(0);
}
之所以先隱藏,是因為在dispose和refresh執(zhí)行的空隙里會有一瞬間的白屏,如果先隱藏就可以避免這種情況。 因為視頻壁紙需要常駐后臺,而控制窗口不可能常駐桌面,所以我們需要改寫它的Formclosing,取消窗體關(guān)閉事件,并隱藏窗體 private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
e.Cancel = true;
this.Hide();
}
給窗體加上NotifyIcon控件,該控件可以顯示任務(wù)欄角標,改寫雙擊事件,雙擊角標時顯示控制窗體 private void notifyIcon1_MouseDoubleClick(object sender, MouseEventArgs e)
{
this.Show();
}
到現(xiàn)在完整的Wallpaper已經(jīng)制作完成,但是目前僅能播放視頻。當然也包括圖片,但是你需要設(shè)置MediaPlayer的循環(huán)播放,否則圖片顯示幾秒后就會變成純黑壁紙。 資源占用看看GPU占用情況 以上數(shù)據(jù)是我在播放電影《龍之谷精靈王座》時的資源占用情況,該電影共1.83GB,可以看到內(nèi)存占用不到100MB,GPU0是核顯,核顯占用也才2%,比起wallpaper已經(jīng)非常優(yōu)秀了,但同時功能也非常單一,不過如果僅僅用來播放視頻,完全可以用來替代wallpaper。 如果你想要實現(xiàn)更多好玩的功能,也可以往視頻播放窗體里加別的東西,但是需要注意一點,所有需要交互的事件都不會響應(yīng),比如鼠標點擊,你只能通過控制窗體來修改視頻播放窗體的內(nèi)容。 源代碼 https://dearx./iiP4frxcm4d EXE文件 https://dearx./iIPmWrxcn6b EXE文件鏈接打開后是一個壓縮包,里面包含兩個dll和一個exe,這三個文件需要放在同一目錄下才可以運行
|