前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Wallpaper的原理和C#实现(含源文件)

Wallpaper的原理和C#实现(含源文件)

作者头像
DearXuan
发布2022-01-19 17:50:24
2.2K0
发布2022-01-19 17:50:24
举报
文章被收录于专栏:DearXuan的博客文章

wallpaper是一款优秀的动态壁纸软件,除了播放动画以外,还可以执行程序,甚至可以实时响应鼠标移动。

原理分析

windows的桌面是由不同的二窗体构成,包括图标层,背景层,背景层显示桌面壁纸,图标层放置图标,且图标层背景透明,因此可以直接看到后面的背景层,鼠标右键弹出菜单也是在图标层完成。wallpaper在图标层和背景层之间插入了自己的窗口,因此可以显示动画,执行代码。前面已经提到图标层是一个透明的覆盖全屏的大窗口,因此鼠标事件只会在图标层响应,而wallpaper可以实时响应鼠标可能是利用了Hook拦截了鼠标事件,并加入自己代码。

既然知道了原理就可以自己实现。

C#实现

界面绘制

首先创建两个窗体,一个用来播放视频,一个用来控制

上图是控制窗口,也是主窗口。

另一个视频窗口较为简单,直接用MediaPlayer覆盖全屏就行,注意需要设置WindowState为Maximized,即启动时立即最大化,同时播放器要隐藏ui,即设置uiMode为none。

在主窗体的load事件里新建VideoForm。为了让VideoForm能够夹在图标层和背景层中间,需要将VideoForm的父窗体设置为背景窗体。

查找句柄

现在需要查找背景窗体的句柄,使用窗口查看器发现背景窗体没有窗体名称,因此无法直接定位,但是我们知道它的类名是WorkW,它的父窗体是Program Manager,所以我们可以遍历所有WorkW窗体,如果其中一个窗体的父窗体是Program Manager,那么这个窗体就是背景窗体。

C#不支持直接这种接近底层的操作,因此需要调用user32.dll实现

代码语言:javascript
复制
[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函数负责查找背景层窗体,SetFather负责把一个窗体设置成另一个窗体的子窗体。为了使用指针功能,需要先开启不安全的代码功能 :项目—??属性(??是你的项目名称)—允许不安全代码。

这个方法在Windows 10 21H1 19043.1110上测试有效,但是不保证在其他系统有效,例如,在vista系统上就会返回空指针,这可能是因为vista系统上的背景窗体不满足上面所讲的关系。一旦返回空指针,会导致设置父窗体失败,最后视频会在图标层上方播放,此时的动态壁纸软件就彻底变成了一个全屏播放器。

如果遇到上面这种情况,可以使用MicrosoftSpy来查找背景窗体,并根据具体情况改写上面的代码。

这里利用了windows窗口的一个特性:如果A窗体在B窗体上面,那么A窗体也会在B窗体的子窗体上面。

按钮事件

给控制窗体的四个按钮写上事件

代码语言:javascript
复制
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是播放器

运行

点击退出

刷新背景

虽然程序退出了,但是桌面变成了一张白纸,极其难看,目前暂不知道为什么会发生这种情况,个人猜测是windows考虑到背景是一张静态图,所以不会实时刷新,而刚刚被覆盖掉的地方就会保持最后一次刷新的颜色,刚才点击“退出”时,由于先dispose了视频播放窗体,导致背景变成白板,如果不点击“退出”,直接结束进程,那么背景就会变成黑板,因为MediaPlayer就是黑色的

既然如此,我们只需要让背景刷新一下就可以,显然在切换壁纸的时候,windows不得不刷新背景,所以我们可以先获取当前壁纸,然后把壁纸切换成当前壁纸,这样实际效果看起来没有任何变化,但是让windows为我们刷新了一次背景。

代码语言:javascript
复制
[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;
}

改写“退出”按钮事件

代码语言:javascript
复制
private void button4_Click(object sender, EventArgs e)//退出
{
    main.Hide();
    this.Hide();
    Window.Refresh();
    main.Dispose();
    System.Environment.Exit(0);
}

之所以先隐藏,是因为在dispose和refresh执行的空隙里会有一瞬间的白屏,如果先隐藏就可以避免这种情况。

因为视频壁纸需要常驻后台,而控制窗口不可能常驻桌面,所以我们需要改写它的Formclosing,取消窗体关闭事件,并隐藏窗体

代码语言:javascript
复制
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    e.Cancel = true;
    this.Hide();
}

给窗体加上NotifyIcon控件,该控件可以显示任务栏角标,改写双击事件,双击角标时显示控制窗体

代码语言:javascript
复制
private void notifyIcon1_MouseDoubleClick(object sender, MouseEventArgs e)
{
    this.Show();
}

到现在完整的Wallpaper已经制作完成,但是目前仅能播放视频。当然也包括图片,但是你需要设置MediaPlayer的循环播放,否则图片显示几秒后就会变成纯黑壁纸。

资源占用

看看GPU占用情况

以上数据是我在播放电影《龙之谷精灵王座》时的资源占用情况,该电影共1.83GB,可以看到内存占用不到100MB,GPU0是核显,核显占用也才2%,比起wallpaper已经非常优秀了,但同时功能也非常单一,不过如果仅仅用来播放视频,完全可以用来替代wallpaper。

如果你想要实现更多好玩的功能,也可以往视频播放窗体里加别的东西,但是需要注意一点,所有需要交互的事件都不会响应,比如鼠标点击,你只能通过控制窗体来修改视频播放窗体的内容。

源代码

https://dearx.lanzoui.com/iiP4frxcm4d

EXE文件

https://dearx.lanzoui.com/iIPmWrxcn6b

EXE文件链接打开后是一个压缩包,里面包含两个dll和一个exe,这三个文件需要放在同一目录下才可以运行

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021年7月27日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 原理分析
  • C#实现
    • 界面绘制
      • 查找句柄
        • 按钮事件
          • 刷新背景
          • 资源占用
          相关产品与服务
          云点播
          面向音视频、图片等媒体,提供制作上传、存储、转码、媒体处理、媒体 AI、加速分发播放、版权保护等一体化的高品质媒体服务。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档