获取所有可见应用程序和窗体的多个桌面的截图。

11

我正在使用一个具有4个输出(监视器)的系统,例如每个输出具有1280x1024像素。 我需要整个桌面以及所有打开的应用程序的截图。

我尝试了GetDesktopWindow()(MSDN) ,但它不能正常工作。 捕获到的图片上有一些表单没有显示出来。

2个回答

40

我尝试了GetDesktopWindow()函数,但它无法正常工作。

当然不能。

GetDesktopWindow 函数返回一个句柄(handle)用于表示桌面窗口, 它与捕获屏幕图像没有关系。

此外,桌面窗口并不等同于“整个屏幕”。它特指桌面窗口。有关更多信息以及滥用此函数返回的句柄时可能出现的问题,请参见此文章

我正在使用一个拥有4个1280x1024显示器的系统,并且需要从整个桌面和其中所有打开的应用程序中获取屏幕截图。

在.NET Framework中使用Graphics.CopyFromScreen方法相对简单。你甚至不需要进行任何P/Invoke调用!

在这种情况下唯一的诀窍是确保传递正确的尺寸。由于你有4个监视器,仅传递主屏幕的尺寸是行不通的。你需要传递整个虚拟屏幕的尺寸,其中包含所有监视器。通过查询SystemInformation.VirtualScreen属性来检索这个尺寸,该属性返回虚拟屏幕的边界。根据文档所示,这是多监视器系统上整个桌面的边界。

示例代码:

// Determine the size of the "virtual screen", which includes all monitors.
int screenLeft   = SystemInformation.VirtualScreen.Left;
int screenTop    = SystemInformation.VirtualScreen.Top;
int screenWidth  = SystemInformation.VirtualScreen.Width;
int screenHeight = SystemInformation.VirtualScreen.Height;

// Create a bitmap of the appropriate size to receive the screenshot.
using (Bitmap bmp = new Bitmap(screenWidth, screenHeight))
{
    // Draw the screenshot into our bitmap.
    using (Graphics g = Graphics.FromImage(bmp))
    {
        g.CopyFromScreen(screenLeft, screenTop, 0, 0, bmp.Size);
    }

    // Do something with the Bitmap here, like save it to a file:
    bmp.Save(savePath, ImageFormat.Jpeg);
}

编辑:

请在不是主线程的wpf应用程序中检查您的解决方案。我尝试了它,它不起作用!

哦,我没有在问题或正文中看到WPF标签。

无论如何,我发布的代码可以在WPF应用程序中很好地工作,只要添加适当的引用和使用声明即可。System.Windows.FormsSystem.Drawing 应该是必需的。可能有一种更符合WPF风格的方式来完成这项任务,而不需要依赖这些WinForms程序集,但我不知道是什么。

它甚至可以在另一个线程上工作。 这里没有任何需要UI线程的内容。

是的,我测试过了。以下是我的完整测试代码:

using System.Windows;
using System.Windows.Forms;   // also requires a reference to this assembly
using System.Drawing;         // also requires a reference to this assembly
using System.Drawing.Imaging;
using System.Threading;

public partial class MainWindow : Window
{
   public MainWindow()
   {
      InitializeComponent();
   }

   private void button1_Click(object sender, RoutedEventArgs e)
   {
      // Create a new thread for demonstration purposes.
      Thread thread = new Thread(() =>
      {
         // Determine the size of the "virtual screen", which includes all monitors.
         int screenLeft   = SystemInformation.VirtualScreen.Left;
    int screenTop    = SystemInformation.VirtualScreen.Top;
    int screenWidth  = SystemInformation.VirtualScreen.Width;
    int screenHeight = SystemInformation.VirtualScreen.Height;

         // Create a bitmap of the appropriate size to receive the screenshot.
         using (Bitmap bmp = new Bitmap(screenWidth, screenHeight))
         {
            // Draw the screenshot into our bitmap.
            using (Graphics g = Graphics.FromImage(bmp))
            {
               g.CopyFromScreen(screenLeft, screenTop, 0, 0, bmp.Size);
            }

            // Do something with the Bitmap here, like save it to a file:
            bmp.Save("G:\\TestImage.jpg", ImageFormat.Jpeg);
         }
      });
      thread.SetApartmentState(ApartmentState.STA);
      thread.Start();
   }
}

我尝试了这个函数以及在搜索中找到的许多类似解决方案。但是它们都不能拍摄我的窗体,只能拍摄桌面上的其他窗体!这可能会导致我的WPF应用程序出现问题,并在非主线程中运行此函数。 - mat
@user2251498 我试过了,但无法让它崩溃。我保存了一张包含我所有4个显示器的图片。(是的,我恰好在我的开发机上也有4个显示器。)我已经发布了我使用的示例代码。这对你有用吗? - Cody Gray
当然可以工作,关键是使用thread.SetApartmentState(ApartmentState.STA),但是当我将这行代码添加到我的程序中时,我不知道为什么会抛出异常。我认为如果你不使用这行代码,它将无法正常工作。 - mat
5
我建议您使用以下代码以保证完整性: g.CopyFromScreen(SystemInformation.VirtualScreen.Left, SystemInformation.VirtualScreen.Top, 0, 0, bmp.Size);这样,如果您的显示器堆叠在一起,左上角不一定总是(0,0),而可能是类似(-768,0)这样的值。这将考虑到此情况。 - TaRDy
@TaRDy 好主意。我在编写代码时没有想到这一点。为了让其他人受益,我已经更新了我的答案。个人而言,如果你编辑以进行更改,我会很满意,但我知道并不是每个人都喜欢别人修改他们的代码。所以感谢你提醒我! - Cody Gray
显示剩余5条评论

5

我创建了一个小助手,因为今天我需要它并尝试了许多不同的函数。无论有多少个监视器,你都可以使用以下代码块将其保存为文件或将其存储在数据库的二进制字段中。

ScreenShotHelper.cs

using System.ComponentModel;//This namespace is required for only Win32Exception. You can remove it if you are catching exceptions from another layer.
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;

namespace Company.Core.Helpers.Win32 {

    public static class ScreenShotHelper {

        private static Bitmap CopyFromScreen(Rectangle bounds) {
            try {
                var image = new Bitmap(bounds.Width, bounds.Height);
                using var graphics = Graphics.FromImage(image);
                graphics.CopyFromScreen(Point.Empty, Point.Empty, bounds.Size);
                return image;
            }
            catch(Win32Exception) {//When screen saver is active
                return null;
            }
        }

        public static Image Take(Rectangle bounds) {
            return CopyFromScreen(bounds);

        }

        public static byte[] TakeAsByteArray(Rectangle bounds) {
            using var image = CopyFromScreen(bounds);
            using var ms = new MemoryStream();
            image.Save(ms, ImageFormat.Png);
            return ms.ToArray();
        }   

        public static void TakeAndSave(string path, Rectangle bounds, ImageFormat imageFormat) {
            using var image = CopyFromScreen(bounds);
            image.Save(path, imageFormat);
        }
    }
}

用法 - 二进制字段

var bounds = new Rectangle();
bounds = Screen.AllScreens.Aggregate(bounds, (current, screen) 
                           => Rectangle.Union(current, screen.Bounds));
_card.ScreenShot = Convert.ToBase64String(ScreenShotHelper.TakeAsByteArray(bounds));

使用 - 磁盘文件

var bounds = new Rectangle();
bounds = Screen.AllScreens.Aggregate(bounds, (current, screen) 
                           => Rectangle.Union(current, screen.Bounds));
ScreenShotHelper.TakeAndSave(@"d:\screenshot.png", bounds, ImageFormat.Png);           

你的代码对我来说没有正常工作,因为我有3个屏幕,并将中间屏幕设置为主屏幕,所以我通过在CopyFromScreen调用中指定Left和Top来修复它,如下所示:graphics.CopyFromScreen(bounds.Left, bounds.Top, 0, 0, bounds.Size); - Adel Khayata
@AdelKhayata 我之前测试过三个屏幕,但老实说,中间那个屏幕成为主屏幕的可能性没有想到。感谢您的建议。 - gurkan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接