C#获取Windows下的桌面壁纸

  咸鱼了好久,上来晒一下。。。最近写WPF程序时想要实现在Windows下获取桌面壁纸的功能,用的是C#,在网上搜了一些资料,然而并没有非常完整的资料,所以在这里记录整理一下。

方法一

  首先,在网上最容易找到的一个方法是使用user32的一个api函数SystemParametersInfo(),查MSDN可以知道这个函数可以获取或者设置系统级别的参数,uiAction参数传入SPI_GETDESKWALLPAPER(0x0073)就可以获得桌面壁纸的路径了,然而事情并没有那么简单_(:3 」∠)_ 。

  顺便吐槽一下,用MSDN搜东西的时候最好把语言设置为英文或者在搜的时候记得勾选显示英文结果,不然没有翻译的文档就不会显示在你的搜索结果里面。

  代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public const int SPI_GETDESKWALLPAPER = 0x0073;

// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724947(v=vs.85).aspx
[DllImport("user32.dll", EntryPoint = "SystemParametersInfo", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int SystemParametersInfo(int uAction, int uiParam, StringBuilder pvParam, int fWinIni);

public static string GetWallpaperSrcPath()
{
StringBuilder builder = new StringBuilder(256);
SystemParametersInfo(SPI_GETDESKWALLPAPER, builder.Capacity, builder, 0);
string wallpaperPath = builder.ToString();
return wallpaperPath;
}

  然后你就能得到设置这个壁纸的时候,壁纸所在的路径了。

方法二

  还有一种方法就是读注册表,获得的结果和上面的方法是一样的,读取HKEY_CURRENT_USER\Control Panel\Desktop\WallPaper的值就能获得一模一样的路径了。

然而

  不过我之前说过,事情并没有那么简单,我之前的壁纸是用一个UWP的壁纸应用设置的,可是后来我卸载了那个应用,壁纸的原文件也被删除了,而系统给你的路径是你设置壁纸的时候壁纸文件所在的路径,如果壁纸路径发生了变化,比如重命名、移动到别的地方或者被删除,那么用这个路径你是找不到这个壁纸的。而且如果你用其他图片改成相同的名字替换了原来的文件,那么你获取到的图片也是错的。

方法三

  所以这时候就要找别的方法了,网上有很多关于这些的讨论,有一个api非常值得关注,那就是PaintDesktop,用法十分简单,传入一个设备上下文的句柄(hDc),然后他就会帮你把桌面画上去了。然而WPF要去哪里弄dc呢。。。

  作为一名初学的菜鸡,只会面向Google编程,然后找到了一篇关于WPF截图的文章,从里面得到了启发,对里面的代码稍作了些修改,说实话我也不知道这样子用会不会有问题,但是代码确实是能做到我想要的结果的,如果有问题还有各位菊苣指出。

  需要引入的api方法有以下这些:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd162758(v=vs.85).aspx
[DllImport("user32.dll", EntryPoint = "PaintDesktop")]
public static extern int PaintDesktop(IntPtr hdc);

// https://msdn.microsoft.com/en-us/library/windows/desktop/ms633504(v=vs.85).aspx
[DllImport("user32.dll", EntryPoint = "GetDesktopWindow", SetLastError = true)]
public static extern IntPtr GetDesktopWindow();

// http://msdn.microsoft.com/en-us/library/dd144871(VS.85).aspx
[DllImport("user32.dll")]
public static extern IntPtr GetDC(IntPtr hwnd);

// http://msdn.microsoft.com/en-us/library/dd162920(VS.85).aspx
[DllImport("user32.dll")]
public static extern int ReleaseDC(IntPtr hwnd, IntPtr dc);

#region gdi32
// http://msdn.microsoft.com/en-us/library/dd183370(VS.85).aspx
[DllImport("gdi32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool BitBlt(IntPtr hDestDC, int x, int y, int nWidth, int nHeight, IntPtr hSrcDC, int xSrc, int ySrc, Int32 dwRop);

// http://msdn.microsoft.com/en-us/library/dd183488(VS.85).aspx
[DllImport("gdi32.dll")]
public static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);

// http://msdn.microsoft.com/en-us/library/dd183489(VS.85).aspx
[DllImport("gdi32.dll", SetLastError = true)]
public static extern IntPtr CreateCompatibleDC(IntPtr hdc);

// http://msdn.microsoft.com/en-us/library/dd162957(VS.85).aspx
[DllImport("gdi32.dll", ExactSpelling = true, PreserveSig = true, SetLastError = true)]
public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);

// http://msdn.microsoft.com/en-us/library/dd183539(VS.85).aspx
[DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);

public const int SRCCOPY = 0xCC0020;
#endregion

  然后代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public static BitmapSource CaptureWallpaper()
{
BitmapSource bitmapSource = null;
IntPtr sourceDC = IntPtr.Zero;
IntPtr targetDC = IntPtr.Zero;
IntPtr compatibleBitmapHandle = IntPtr.Zero;
BitmapImage bitmapImage = null;

try
{
// 拿到桌面窗口的dc,只在Win10静态壁纸下测试过,其他情况肯定是要改的,找桌面窗口句柄的方法还要再封装一下,不过我比较咸鱼先这样写了。。。
sourceDC = GetDC(FindWindowEx(
FindWindowEx(
FindWindow("Progman", "Program Manager"),
IntPtr.Zero, "SHELLDLL_DefView", ""
),
IntPtr.Zero, "SysListView32", "FolderView"
));
// 创建一个兼容sourceDC的内存DC
targetDC = CreateCompatibleDC(sourceDC);
// 创建一个兼容targetDC的位图
compatibleBitmapHandle = CreateCompatibleBitmap(sourceDC, (int)SystemParameters.PrimaryScreenWidth, (int)SystemParameters.PrimaryScreenHeight);
// 把位图指定到targetDC环境中
SelectObject(targetDC, compatibleBitmapHandle);
// 把桌面画到sourceDC上
PaintDesktop(sourceDC);
// 然后从sourceDC复制到targetDC
BitBlt(targetDC, 0, 0, (int)SystemParameters.PrimaryScreenWidth, (int)SystemParameters.PrimaryScreenHeight, sourceDC, 0, 0, SRCCOPY);
// 把hBitmap转换成BitmapSource
bitmapSource = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
compatibleBitmapHandle, IntPtr.Zero, Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
}
catch (Exception e)
{
Console.WriteLine(e.StackTrace);
}
finally
{
DeleteObject(compatibleBitmapHandle);
ReleaseDC(IntPtr.Zero, sourceDC);
ReleaseDC(IntPtr.Zero, targetDC);
}
return bitmapSource;
}

  因为发现Windows会马上重绘桌面,所以就拿桌面窗口的dc让桌面壁纸直接就画在桌面窗口了,不知道有没有更好的方法,菜鸡初学还不是很熟悉C#,WPF和Win32,还请各位菊苣多指教。

  然后我们就可以拿到桌面的位图了,之后的事情就好办了,大功告成!!!o(*≧▽≦)ツ

1
// 感觉博客的代码高亮有点吃藕,迟一点再继续折腾。。。(叹气

#EOF