【问题标题】:Get a screenshot of a specific application获取特定应用程序的屏幕截图
【发布时间】:2010-10-27 20:11:07
【问题描述】:

我知道我可以使用 Graphics.CopyFromScreen() 获取整个屏幕的屏幕截图。但是,如果我只想要特定应用程序的屏幕截图怎么办?

【问题讨论】:

    标签: c# winapi screenshot


    【解决方案1】:

    PrintWindow win32 api 将捕获窗口位图,即使窗口被其他窗口覆盖或不在屏幕上:

    [DllImport("user32.dll")]
    public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
    [DllImport("user32.dll")]
    public static extern bool PrintWindow(IntPtr hWnd, IntPtr hdcBlt, int nFlags);
    
    public static Bitmap PrintWindow(IntPtr hwnd)    
    {       
        RECT rc;        
        GetWindowRect(hwnd, out rc);
    
        Bitmap bmp = new Bitmap(rc.Width, rc.Height, PixelFormat.Format32bppArgb);        
        Graphics gfxBmp = Graphics.FromImage(bmp);        
        IntPtr hdcBitmap = gfxBmp.GetHdc();        
    
        PrintWindow(hwnd, hdcBitmap, 0);  
    
        gfxBmp.ReleaseHdc(hdcBitmap);               
        gfxBmp.Dispose(); 
    
        return bmp;   
    }
    

    上面对 RECT 的引用可以用下面的类来解决:

    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
        private int _Left;
        private int _Top;
        private int _Right;
        private int _Bottom;
    
        public RECT(RECT Rectangle) : this(Rectangle.Left, Rectangle.Top, Rectangle.Right, Rectangle.Bottom)
        {
        }
        public RECT(int Left, int Top, int Right, int Bottom)
        {
            _Left = Left;
            _Top = Top;
            _Right = Right;
            _Bottom = Bottom;
        }
    
        public int X {
            get { return _Left; }
            set { _Left = value; }
        }
        public int Y {
            get { return _Top; }
            set { _Top = value; }
        }
        public int Left {
            get { return _Left; }
            set { _Left = value; }
        }
        public int Top {
            get { return _Top; }
            set { _Top = value; }
        }
        public int Right {
            get { return _Right; }
            set { _Right = value; }
        }
        public int Bottom {
            get { return _Bottom; }
            set { _Bottom = value; }
        }
        public int Height {
            get { return _Bottom - _Top; }
            set { _Bottom = value + _Top; }
        }
        public int Width {
            get { return _Right - _Left; }
            set { _Right = value + _Left; }
        }
        public Point Location {
            get { return new Point(Left, Top); }
            set {
                _Left = value.X;
                _Top = value.Y;
            }
        }
        public Size Size {
            get { return new Size(Width, Height); }
            set {
                _Right = value.Width + _Left;
                _Bottom = value.Height + _Top;
            }
        }
    
        public static implicit operator Rectangle(RECT Rectangle)
        {
            return new Rectangle(Rectangle.Left, Rectangle.Top, Rectangle.Width, Rectangle.Height);
        }
        public static implicit operator RECT(Rectangle Rectangle)
        {
            return new RECT(Rectangle.Left, Rectangle.Top, Rectangle.Right, Rectangle.Bottom);
        }
        public static bool operator ==(RECT Rectangle1, RECT Rectangle2)
        {
            return Rectangle1.Equals(Rectangle2);
        }
        public static bool operator !=(RECT Rectangle1, RECT Rectangle2)
        {
            return !Rectangle1.Equals(Rectangle2);
        }
    
        public override string ToString()
        {
            return "{Left: " + _Left + "; " + "Top: " + _Top + "; Right: " + _Right + "; Bottom: " + _Bottom + "}";
        }
    
        public override int GetHashCode()
        {
            return ToString().GetHashCode();
        }
    
        public bool Equals(RECT Rectangle)
        {
            return Rectangle.Left == _Left && Rectangle.Top == _Top && Rectangle.Right == _Right && Rectangle.Bottom == _Bottom;
        }
    
        public override bool Equals(object Object)
        {
            if (Object is RECT) {
                return Equals((RECT)Object);
            } else if (Object is Rectangle) {
                return Equals(new RECT((Rectangle)Object));
            }
    
            return false;
        }
    }
    

    【讨论】:

    • 很好的解决方案。我只想指出,有时 PixelFormat.Format32bppArgb 会产生白色伪影。在这种情况下,只需尝试使用其他格式,例如 PixelFormat.Format24bppRgb
    • 我该如何使用这个方法?打印窗口(what to pass here)
    • @MauriceFlanagan 我得到全黑图像?为什么??
    • 此解决方案中是否添加了黑色图像解决方案?我在 Windows 10 上遇到了同样的事情
    • UWP 应用程序始终为黑色位图。当我与 UWP 应用程序在同一个桌面上时,我什至无法获得 UWP 应用程序的 DWM 缩略图。但是,当我不在同一个桌面上时,我可以获得 UWP 应用程序的 DWM 缩略图。快把我逼疯了。
    【解决方案2】:

    这里有一些代码可以帮助您入门:

    public void CaptureApplication(string procName)
    {
        var proc = Process.GetProcessesByName(procName)[0];
        var rect = new User32.Rect();
        User32.GetWindowRect(proc.MainWindowHandle, ref rect);
    
        int width = rect.right - rect.left;
        int height = rect.bottom - rect.top;
    
        var bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb);
        using (Graphics graphics = Graphics.FromImage(bmp))
        {
            graphics.CopyFromScreen(rect.left, rect.top, 0, 0, new Size(width, height), CopyPixelOperation.SourceCopy);
        }
    
        bmp.Save("c:\\tmp\\test.png", ImageFormat.Png);
    }
    
    private class User32
    {
        [StructLayout(LayoutKind.Sequential)]
        public struct Rect
        {
            public int left;
            public int top;
            public int right;
            public int bottom;
        }
    
        [DllImport("user32.dll")]
        public static extern IntPtr GetWindowRect(IntPtr hWnd, ref Rect rect);
    }
    

    有效,但需要改进:

    • 您可能希望使用不同的机制来获取进程句柄(或至少进行一些防御性编码)
    • 如果您的目标窗口不在前景中,您最终会得到一个大小/位置正确的屏幕截图,但只会填充前景中的任何内容(您可能希望将给定的窗口拉入前景优先)
    • 您可能想要做的不仅仅是将 bmp 保存到临时目录

    【讨论】:

    • 这不会在win7中将所选进程的窗口带到前台,因此您将获得活动窗口的屏幕截图
    • @alconja 我用你的代码拍摄了记事本的快照。但是它拍摄了作为活动窗口的 Visual Studio 的快照。我们可以用它来拍摄没有活动窗口的照片吗?跨度>
    • @FastSnail - 我打算建议你尝试另一个答案,但我从你的 cmets 看到这也不起作用......另一个选择可能是尝试找到一种 pinvoke 方法首先将目标应用程序/窗口拉到前台。比如SwitchToThisWindow,也许……
    • 隐藏或最小化窗口是否有效?如果屏幕被锁定,CopyScreen 会创建异常吗?
    • @daisy - 已经有一段时间了,但不,我认为这个只是截取屏幕区域的屏幕截图,所以如果窗口不可见,它将无法工作。下面的答案声称在这种情况下有效(但不确定锁定屏幕,抱歉)​​。
    【解决方案3】:

    根据 Alconja 的回答,我做了一些改进:

    [StructLayout(LayoutKind.Sequential)]
    public struct Rect
    {
        public int left;
        public int top;
        public int right;
        public int bottom;
    }
    
    [DllImport("user32.dll")]
    private static extern int SetForegroundWindow(IntPtr hWnd);
    
    private const int SW_RESTORE = 9;
    
    [DllImport("user32.dll")]
    private static extern IntPtr ShowWindow(IntPtr hWnd, int nCmdShow);
    
    [DllImport("user32.dll")]
    public static extern IntPtr GetWindowRect(IntPtr hWnd, ref Rect rect);
    
    public Bitmap CaptureApplication(string procName)
    {
        Process proc;
    
        // Cater for cases when the process can't be located.
        try
        {
            proc = Process.GetProcessesByName(procName)[0];
        }
        catch (IndexOutOfRangeException e)
        {
            return null;
        }
    
        // You need to focus on the application
        SetForegroundWindow(proc.MainWindowHandle);
        ShowWindow(proc.MainWindowHandle, SW_RESTORE);
    
        // You need some amount of delay, but 1 second may be overkill
        Thread.Sleep(1000);
    
        Rect rect = new Rect();
        IntPtr error = GetWindowRect(proc.MainWindowHandle, ref rect);
    
        // sometimes it gives error.
        while (error == (IntPtr)0)
        {
            error = GetWindowRect(proc.MainWindowHandle, ref rect);
        }
    
        int width = rect.right - rect.left;
        int height = rect.bottom - rect.top;
    
        Bitmap bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb);
        Graphics.FromImage(bmp).CopyFromScreen(rect.left,
                                               rect.top,
                                               0,
                                               0,
                                               new Size(width, height),
                                               CopyPixelOperation.SourceCopy);
    
        return bmp;
    }
    

    【讨论】:

    • 浏览完后我注意到,如果进程在Thread.Sleep(1000); 期间关闭,您将有一个无限循环。
    • @NicolasTyler 是对的。澄清一下,问题是使用不再有效的 HWND 调用 GetWindowRect 将始终返回零,这意味着此答案中的 while 循环将永远不会退出,只会永远烧掉 CPU,这是一个非常严重的错误。但除此之外,我认为这个答案是一个优雅的解决方案。也许只是限制在固定的重试次数,也许在两者之间睡一会儿。或者不要在这个方法中重试。
    • 这个答案也不会处理 Graphics 对象。
    【解决方案4】:

    您可以查看P/Invoking win32 的执行方式,an article to this effect... 之类的。

    基本上,通过将 DC 设置为位图并将 WM_PRINT 发送到相关应用程序窗口的麻烦。总而言之,它非常讨厌,但可能对你有用。

    您可能需要的函数:SendMessageGetDCCreateCompatibleBitmpSelectObject

    我不能说我以前曾经这样做过,但这就是我解决问题的方式。 (好吧,我可能会用纯 C 语言来做,但仍然;大致是我攻击它的方式)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-03
      • 2018-05-05
      • 2013-11-26
      • 2015-03-23
      • 1970-01-01
      相关资源
      最近更新 更多