在.NET中捕获包含半透明窗口的屏幕截图

32

我希望有一个相对无hack的方法来实现这个,有什么想法吗?例如,以下代码会截取不包含半透明窗口的屏幕截图:

Public Class Form1
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Shown
        Text = "Opaque Window"
        Dim win2 As New Form
        win2.Opacity = 0.5
        win2.Text = "Tranparent Window"
        win2.Show()
        win2.Top = Top + 50
        win2.Left = Left() + 50
        Dim bounds As Rectangle = System.Windows.Forms.Screen.GetBounds(Point.Empty)
        Using bmp As Bitmap = New Bitmap(bounds.Width, bounds.Height)
            Using g As Graphics = Graphics.FromImage(bmp)
                g.CopyFromScreen(Point.Empty, Point.Empty, bounds.Size)
            End Using
            bmp.Save("c:\temp\scn.gif")
        End Using
        Process.Start(New Diagnostics.ProcessStartInfo("c:\temp\scn.gif") With {.UseShellExecute = True})
    End Sub
End Class

我的谷歌搜索能力很差,或者说这并不像听起来那么简单。我相当确定是由于视频驱动程序必须分离内存才能使这个工作,但我并不关心它为什么不起作用,我只想在不使用以下方法的情况下完成它:
* Print-Screen键快捷键
* 第三方软件
* SDK函数可行,但如果用户拥有的对象可以在纯框架中显示给我看,我会点赞的(开玩笑,但这很好)。

如果是唯一的方法,那么如何在VB中实现?
非常感谢。


如果这是唯一的方法...一些研究表明那也不会起作用。 - FastAl
1个回答

74

设置了 TransparencyKey 或 Opacity 属性的窗体被称为分层窗口。它们使用视频适配器的“覆盖”功能进行显示,从而使其能够具有透明效果。

捕获这些窗口需要在接受 CopyPixelOperation 参数的 CopyFromScreen 重载中启用 CopyPixelOperation.CaptureBlt 选项。

不幸的是,这个重载存在一个关键错误,阻止了此功能的正常工作。它没有正确验证该值。即便在 .NET 4.0 中仍未修复。唯一可靠的解决方案是通过 P/Invoke 来实现截屏。以下是一个示例:

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace WindowsApplication1 {
  public partial class Form1 : Form {
    public Form1() {
      InitializeComponent();
    }
    private void button1_Click(object sender, EventArgs e) {
      Size sz = Screen.PrimaryScreen.Bounds.Size;
      IntPtr hDesk = GetDesktopWindow();
      IntPtr hSrce = GetWindowDC(hDesk);
      IntPtr hDest = CreateCompatibleDC(hSrce);
      IntPtr hBmp = CreateCompatibleBitmap(hSrce, sz.Width, sz.Height);
      IntPtr hOldBmp = SelectObject(hDest, hBmp);
      bool b = BitBlt(hDest, 0, 0, sz.Width, sz.Height, hSrce, 0, 0, CopyPixelOperation.SourceCopy | CopyPixelOperation.CaptureBlt);
      Bitmap bmp = Bitmap.FromHbitmap(hBmp);
      SelectObject(hDest, hOldBmp);
      DeleteObject(hBmp);
      DeleteDC(hDest);
      ReleaseDC(hDesk, hSrce);
      bmp.Save(@"c:\temp\test.png");
      bmp.Dispose();
    }

    // P/Invoke declarations
    [DllImport("gdi32.dll")]
    static extern bool BitBlt(IntPtr hdcDest, int xDest, int yDest, int
    wDest, int hDest, IntPtr hdcSource, int xSrc, int ySrc, CopyPixelOperation rop);
    [DllImport("user32.dll")]
    static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDc);
    [DllImport("gdi32.dll")]
    static extern IntPtr DeleteDC(IntPtr hDc);
    [DllImport("gdi32.dll")]
    static extern IntPtr DeleteObject(IntPtr hDc);
    [DllImport("gdi32.dll")]
    static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);
    [DllImport("gdi32.dll")]
    static extern IntPtr CreateCompatibleDC(IntPtr hdc);
    [DllImport("gdi32.dll")]
    static extern IntPtr SelectObject(IntPtr hdc, IntPtr bmp);
    [DllImport("user32.dll")]
    public static extern IntPtr GetDesktopWindow();
    [DllImport("user32.dll")]
    public static extern IntPtr GetWindowDC(IntPtr ptr);
  }
}

顺带一提,后来的 Windows 版本为此错误提供了一种解决方法。不太确定具体是哪个版本,我想应该是 Win7 SP1。如果你只传递 CopyPixelOperation.CaptureBlt 选项,BitBlt() 函数现在会按照你的预期工作。但当然,这个解决方法并没有追溯到早期的 Windows 版本,所以你不能真正依赖它。


8
这个方案太棒了!老实说,汉斯,如果我不是基督徒,我会***地向你下拜!这个方案确实非常有效! - FastAl
7
通过使用Screen.AllScreens,创建一个具有总宽度和最大高度的新的Size sz,可以捕获多个屏幕。 - Jon
这个也适用于以 SystemInformation.VirtualScreen 作为输入吗? - Kobor42
1
请确保您的应用程序明确声明了 dpiAware,否则如果计算机配置了非标准 DPIScreen.PrimaryScreen.Bounds.Size 将返回一个虚假(较小)的区域,而使用 GDI 调用拍摄的屏幕截图将使用真实像素,从而导致上述代码中的截图被裁剪。 - dnet

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