【问题标题】:Take screenshot of multiple desktops of all visible applications and forms截取所有可见应用程序和表单的多个桌面的屏幕截图
【发布时间】:2013-03-28 16:15:57
【问题描述】:

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

我试过GetDesktopWindow() (MSDN),但它不能正常工作。某些表格未显示在捕获的图片上。

【问题讨论】:

    标签: c# screenshot desktop-application multiple-monitors multiscreen


    【解决方案1】:

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

    当然不是。

    GetDesktopWindow 函数向桌面窗口返回一个句柄。它与捕获该窗口的图像没有任何关系。

    此外,桌面窗口与“整个屏幕”不是一回事。它特指桌面窗口。有关更多信息以及滥用此函数返回的句柄时可能出现的问题,请参阅this article

    我正在使用具有 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 应用程序在不是我的主线程的线程中运行此功能。
    • 请在不是您的主线程的线程中使用 wpf 应用程序检查您的解决方案。我尝试过这个。它不起作用!
    • @user2251498 我做到了,我不能让它崩溃。我保存了一张包含我所有 4 台显示器的图片。 (是的,我恰好在我的开发机器上也有 4 个。)我已经发布了我使用的示例代码。这对你有用吗?
    • 当然它的工作,重点是使用 thread.SetApartmentState(ApartmentState.STA),但是当我将此行添加到我的程序时,我不知道为什么我的代码会抛出异常。我认为如果你不使用那条线,它将无法正常工作。
    • 为了完整性,我建议您使用:g.CopyFromScreen(SystemInformation.VirtualScreen.Left, SystemInformation.VirtualScreen.Top, 0, 0, bmp.Size);这样,如果您的显示器堆叠起来,左上角并不总是 (0,0),它可能类似于 (-768,0)。这应该说明这一点。
    【解决方案2】:

    我创建了一个小助手,因为我今天需要这个案例并尝试了许多不同的功能。与监视器的数量无关,您可以将其保存为磁盘上的文件或使用以下代码块将其存储在 db 中的二进制字段中。

    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);
    • @AdelKhayata 我之前用 3 个屏幕测试过它,但老实说,没有想到中间那个可能是主屏幕。感谢您的建议。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-07-12
    • 2021-06-28
    • 1970-01-01
    • 2017-07-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多