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

问题描述:

我想要一种相对无黑客的方式来做到这一点,有什么想法吗?例如,下面的截图不包括半透明窗口:

I would like a relatively hack-free way to do this, any ideas? For example, the following takes a screenshot that doesn't include the semi-transparent window:

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:	empscn.gif")
        End Using
        Process.Start(New Diagnostics.ProcessStartInfo("c:	empscn.gif") With {.UseShellExecute = True})
    End Sub
End Class

要么我的 google-fu 真的很烂,要么这并不像听起来那么容易.我很确定为什么会发生这种情况,因为视频驱动程序必须将内存分开才能使其工作,但我不在乎为什么它不起作用,我只想在没有...
* 打印屏幕关键技巧
* 3rd 方软件
* SDK 功能还可以,但我会赞成用户拥有的每个可以在纯框架中向我展示的对象(开玩笑,但会很好).

Either my google-fu really sucks or this is not as easy as it sounds. I'm pretty sure why this is happening because of the way the video driver would have to separate the memory to make this work, but I don't care why it doesn't work, I just want to do it without...
* print-screen key hacks
* 3rd party software
* SDK functions are OK but I'll upvote every object owned by the user that can show me it in pure framework (Just kidding but it would be nice).

如果 这是唯一的方法,我如何在 VB 中做到这一点?
100 万谢谢.

If This is the only way to do it, how to I do that in VB?
1M thanks.

设置了 TransparencyKey 或 Opacity 属性的表单是所谓的分层窗口.它们使用视频适配器的覆盖"功能显示.这使它们能够拥有透明效果.

Forms that have the TransparencyKey or Opacity property set are so-called layered windows. They are shown using the "overlay" feature of the video adapter. Which make them being able to have their transparency effects.

捕获它们需要在接受 CopyPixelOperation 参数的 CopyFromScreen 重载中打开 CopyPixelOperation.CaptureBlt 选项.

Capturing them requires turning on the CopyPixelOperation.CaptureBlt option in the CopyFromScreen overload that accepts the CopyPixelOperation argument.

不幸的是,这个重载有一个严重的错误,阻止了它的工作.它没有正确验证该值.在 .NET 4.0 中仍未修复.没有其他好的修复方法,只能使用 P/Invoke 来制作屏幕截图.举个例子:

Unfortunately, this overload has a critical bug that prevents this from working. It doesn't validate the value properly. Still not fixed in .NET 4.0. There is no other good fix but fall back to using P/Invoke to make the screen shot. Here's an example:

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:	emp	est.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);
  }
}

Fwiw,更高的 Windows 版本提供了解决此错误的方法.不确定是哪个,我认为是 Win7 SP1.如果您传递 CopyPixelOperation.CaptureBlt 选项,BitBlt() 函数现在将执行您想要的操作.但当然,该解决方法并未追溯应用于早期的 Windows 版本,因此您不能真正依赖它.

Fwiw, a later Windows version provided a workaround for this bug. Not exactly sure which, I think it was Win7 SP1. The BitBlt() function will now do what you want if you pass only the CopyPixelOperation.CaptureBlt option. But of course that workaround wasn't applied retro-actively to earlier Windows versions so you can't really depend on it.