65.9K
CodeProject 正在变化。 阅读更多。
Home

捕获最小化窗口:一个孩子的把戏

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.72/5 (42投票s)

2007 年 9 月 27 日

CPOL

5分钟阅读

viewsIcon

236575

downloadIcon

12616

即使窗口最小化或完全被其他窗口隐藏,也能捕获窗口。

Screenshot - minimizedCapture.jpg

背景

在我一个项目中,我需要从最小化窗口捕获。在几次尝试使用普通方法失败后;我查了很多网络(好吧,不是很多),但找不到解决方案,所以我决定研究一个新的方法。

开篇

首先,感谢上帝保佑我的脑袋没有炸开,因为我确实需要做到这一点。

也许这种方法不是很可靠,但它解决了我的问题,并且可能对其他人有用,所以我认为将其发布在 Code Project 上比不发布要好。

顺便说一句,这是我第一篇 CodeProject 文章,如果您发现任何瑕疵,请原谅我,并请您提出建议,帮助我进一步改进。

问题

好的,这种方法的核心是 `PrintWindow` API,它将窗口的界面绘制到指定的设备上下文中。

此方法可用于捕获窗口,无论其顺序和透明度如何(可以捕获部分或完全隐藏的窗口),但主要问题是,它无法捕获最小化窗口,实际上当窗口最小化时,它将不再绘制,所以如果我们使用 `PrintWindow`,它只会返回窗口的标题。

我尝试了几种捕获最小化窗口的方法,但都失败了,除了这个。

如何捕获

我假设无法从最小化窗口捕获,因此这个假设改变了方法。

我尝试将窗口恢复为正常状态然后捕获,之后再将其最小化;为了实现隐藏捕获,在我们从最小化状态弹出窗口之前,它必须是透明的,这可以通过 `SetLayeredWindowAttributes` API 来实现,但还有另一个问题。再次,窗口弹出动画仍然显示,即使窗口是完全透明的。

通过使用 SPY++,我监控了 explorer 的消息,然后取消勾选“最小化和最大化窗口时进行动画”,并发现了这个消息 `SPI_SETANIMATION`,然后,经过一些搜索,我发现可以通过 `SystemParametersInfo` API 来更改它。

可以通过注册表禁用此效果,但不是实时的。

HKEY_CURRENT_USER\Control Panel\Desktop\WindowMetrics\MinAnimate

如果值为 `0`,则禁用该效果,值为 `1` 则启用。

因此,以下是我为捕获最小化窗口所遵循的步骤

  1. 禁用 `MinAnimate` 效果
  2. 使窗口透明(`PrintWindow` 可以从此类窗口截屏)
  3. 恢复窗口
  4. 捕捉
  5. 再次将其最小化
  6. 移除透明度
  7. 启用 `MinAnimate` 效果

现在让我们检查代码。

1 & 7 - 禁用/启用 MinAnimate 效果

我忘了提,我说 `MinAnimate` 是因为它在注册表中的名称。

如前所述,可以通过使用 `SystemParametersInfo` 来实现,代码如下

(此代码片段在 _XPAppearance.cs_ 中)

[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SystemParametersInfo(SPI uiAction, uint uiParam,
 ref ANIMATIONINFO pvParam, SPIF fWinIni);

`SPI` 是 `SPI` 消息的枚举,我们想要禁用/启用 `MinAnimate`,所以我们必须将 `uiAction` 设置为 `SPI_SETANIMATION`。

我们可以在 `ANIMATIONINFO` 结构中 `获取`/`设置` 该效果的状态。

请注意,`ANIMATIONINFO` 的 `cbsize` 成员必须设置为结构的大小,并且 `pvParam` 也必须等于 `cbSize`。

以下是结构

(此代码片段在 _XPAppearance.cs_ 中)

[StructLayout(LayoutKind.Sequential)]
private struct ANIMATIONINFO
{

   public ANIMATIONINFO(bool iMinAnimate)
   {
      this.cbSize = GetSize();
      if (iMinAnimate) this.iMinAnimate = 1;
      else this.iMinAnimate = 0;
   }

   public uint cbSize;
   private int iMinAnimate;

   public bool IMinAnimate
   {
      get
      {
         if (this.iMinAnimate == 0) return false;
         else return true;
      }
      set
      {
         if (value == true) this.iMinAnimate = 1;
         else this.iMinAnimate = 0;
      }
   }

   public static uint GetSize()
   {
      return (uint)Marshal.SizeOf(typeof(ANIMATIONINFO));
   }
}

以下是我用于禁用/启用 `MinAnimate` 的方法

(此代码片段在 _XPAppearance.cs_ 中)

public static void SetMinimizeMaximizeAnimation(bool status)
{
   ANIMATIONINFO animationInfo=new ANIMATIONINFO(status);
   SystemParametersInfo(SPI.SPI_GETANIMATION, ANIMATIONINFO.GetSize(),
    ref animationInfo, SPIF.None);

   if (animationInfo.IMinAnimate != status)
   {
      animationInfo.IMinAnimate = status;
      SystemParametersInfo(SPI.SPI_SETANIMATION, ANIMATIONINFO.GetSize(),
       ref animationInfo, SPIF.SPIF_SENDCHANGE);
   }
}

我认为上面的代码没什么需要解释的。

2 & 6 - 添加/移除透明度

为了使窗口不可见但仍可捕获,我们可以使其透明。

可以这样做

(此代码片段在 _WindowSnap.cs_ 中)

[DllImport("user32")]
private static extern int GetWindowLong(IntPtr hWnd, int index);

[DllImport("user32")]
private static extern int SetWindowLong(IntPtr hWnd, int index, int dwNewLong);

[DllImport("user32")]
private static extern int SetLayeredWindowAttributes(IntPtr hWnd, byte crey,
 byte alpha, int flags);

(此代码片段在 _WindowSnap.cs_ 的 `EnterSpecialCapturing` 方法中)

winLong = GetWindowLong(hWnd, GWL_EXSTYLE);
SetWindowLong(hWnd, GWL_EXSTYLE, winLong | WS_EX_LAYERED);
SetLayeredWindowAttributes(hWnd, 0, 1, LWA_ALPHA);

要移除已应用的透明度,可以通过重新设置旧的 `winLong` 来实现。

(此代码片段在 _WindowSnap.cs_ 的 `ExitSpecialCapturing` 方法中)

SetWindowLong(hWnd, GWL_EXSTYLE, winLong);

3 & 5 - 恢复/最小化窗口

这部分很容易,可以通过 `ShowWindow` API 来实现。

(此代码片段在 _WindowSnap.cs_ 中)

[DllImport("user32")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool ShowWindow(IntPtr hWnd, ShowWindowEnum flags);

private enum ShowWindowEnum{Hide = 0,
ShowNormal = 1,ShowMinimized = 2,ShowMaximized = 3,
Maximize = 3,ShowNormalNoActivate = 4,Show = 5,
Minimize = 6,ShowMinNoActivate = 7,ShowNoActivate = 8,
Restore = 9,ShowDefault = 10,ForceMinimized = 11};

(此代码片段在 _WindowSnap.cs_ 的 `EnterSpecialCapturing` 方法中)

ShowWindow(hWnd, ShowWindowEnum.Restore);

再次最小化可以使用

(此代码片段在 _WindowSnap.cs_ 的 `ExitSpecialCapturing` 方法中)

ShowWindow(hWnd, ShowWindowEnum.Minimize);

4 - 捕获

没什么可说的,有很多关于此的文章;代码如下

(此代码片段在 _WindowSnap.cs_ 的 `GetWindowImage` 方法中)

Bitmap bmp = new Bitmap(size.Width, size.Height);
Graphics g = Graphics.FromImage(bmp);
IntPtr dc = g.GetHdc();

PrintWindow(hWnd, dc, 0);

g.ReleaseHdc();
g.Dispose();

捕获子窗口

从 MDI 子窗口捕获与从普通窗口捕获非常相似,但有一个例外,如果子窗口被父窗口(而不是另一个子窗口)部分隐藏,则该部分会显示为空白。为了解决这个问题,我们可以使用 `GetParent` 和 `SetParent` API 将其作为普通窗口处理,然后再恢复。

[DllImport("user32")]
private static extern IntPtr GetParent(IntPtr hWnd);
[DllImport("user32")]
private static extern IntPtr SetParent(IntPtr child, IntPtr newParent);  

我们可以将 `IntPtr.Zero` 传递给 `GetParent` 方法的 `newParent` 参数,使其恢复正常(非子窗口)。

最后的话

就是这样,最后,您可以使用 `WindowSnap` 类从 Windows 捕获屏幕。它有两个方法,`GetAllWindows` 和 `GetWindowSnap`。第一个返回所有可用窗口的屏幕截图集合,第二个返回特定窗口的屏幕截图。

最后,我感谢您花费时间阅读本文。希望它能有所帮助。请您提出建议和想法,使本文更好。

更新

  • 2007 年 10 月 6 日
    • 捕获子窗口:添加了对子窗口的支持。特别感谢 mmjc23 的提及。更多详情,请参阅 **捕获子窗口** 部分。
    • MinAnimate Bug 修复:在上一版本中,如果 `MinAnimate` 效果被禁用,`WindowSnap` 会在 `SpecialCapturing`(从最小化窗口捕获)之后启用它。特别感谢 Kalabuli 通知我此 bug。
  • 2007 年 10 月 10 日
    • 子对话框捕获修复:修复了从模态子对话框捕获的问题。特别感谢 Hitesh 通知我此 bug。
© . All rights reserved.