【问题标题】:Screen capture of a particular instance of multiple application windows多个应用程序窗口的特定实例的屏幕截图
【发布时间】:2018-08-31 15:38:01
【问题描述】:

我需要截屏多窗口应用程序的特定窗口。 一个很好的例子:我用两个窗口运行 OUTLOOK - 邮件和日历。 当我使用 Process.GetProcessesByName(ApplicationToWatch).FirstOrDefault() 我当然会得到“第一个”窗口。如何访问“第二个”或后续窗口? (有趣的是,有一个 FirstOrDefault 但没有另一种方法来获得其他东西 - 我显然错过了一些东西)。

【问题讨论】:

  • 如果您只使用 DotNet,这将非常困难。我建议使用 win32 api。您可以获取窗口的句柄并尝试将其绘制为位图。如果您确定窗口位于前台并且您知道窗口的位置,则可以使用 CopyFromScreen 函数。
  • 是的,显然它在 DotNet 中困难的。 :-) 。我的问题是首先获得窗口的位置。哦,好吧 - 在我的实际应用程序中,我现在得到了正确的窗口 - 但预期会遇到 OUTLOOK 示例让我彻夜难眠。

标签: vb.net


【解决方案1】:

您已经了解了如何使用EnumChildWindows 获取子窗口句柄以与GetClassName 关联。

所以我会建议你另一种方法,使用UI Automation
它可能不太为人所知,但在这种情况下,它非常简单,并且可以简化此任务。

您只需要知道要枚举其子窗口的主窗口的句柄,并使用 LINQ 的 .Where().FirstOrDefault() 方法过滤子窗口的集合 (AutomationElementCollection)。

请注意,UI 自动化枚举不如 EnumChildWindows 彻底。此外,在某些情况下,返回的类名称可能与特定 UI 元素的实际类名称不同。
但这些可能是您不感兴趣的 UI 元素。

要获取Outlook 主窗口的句柄,我们照常使用Process.GetProcessesByName()
然后使用AutomationElement.FromHandle() 方法获取自动化元素。

要查找特定的子元素,我们可以使用此主自动化元素(源代码参考).FindAll() 方法,过滤返回的集合,如果需要,使用.Where() - 定义子集合 - 或 .FirstOrDefault(),获取特定元素类或句柄(或其他已知细节)的引用。

这些方法展示了如何截取Outlook 侧日历面板和主日历窗口的屏幕截图。

使用code already discussed in your previous question 获取所选屏幕边界的实际屏幕截图。

此代码需要添加对:
UIAutomationClient
UIAutomationTypes
WindowsBase的引用>

Imports System.Diagnostics
Imports System.Drawing
Imports System.Windows.Automation

Dim OutLookProc As Process = Process.GetProcessesByName("OUTLOOK").FirstOrDefault()
Dim MainElement As AutomationElement = AutomationElement.FromHandle(OutLookProc.MainWindowHandle)

Dim SmallCalendar As AutomationElement =
        MainElement.FindAll(TreeScope.Subtree, Automation.RawViewCondition).
                    OfType(Of AutomationElement)().
                    FirstOrDefault(Function(elm) elm.Current.Name.Contains("NUIDocument") AndAlso
                                   (Not String.IsNullOrEmpty(elm.Current.AutomationId)))

Dim CalendarNavigator As AutomationElement =
        MainElement.FindAll(TreeScope.Subtree, Automation.RawViewCondition).
                    OfType(Of AutomationElement)().
                    FirstOrDefault(Function(elm) elm.Current.ClassName.Contains("TreeDisplayNode"))

If SmallCalendar IsNot Nothing Then
    Dim SmallCalendarHeight As Integer = CInt(CalendarNavigator.Current.BoundingRectangle.Y -
                                              SmallCalendar.Current.BoundingRectangle.Y)
    Dim CalLocation As Point = New Point(CInt(SmallCalendar.Current.BoundingRectangle.Location.X),
                                         CInt(SmallCalendar.Current.BoundingRectangle.Location.Y))
    Dim CalSize As Size = New Size(CInt(SmallCalendar.Current.BoundingRectangle.Width), SmallCalendarHeight)

    Dim SmallCalendarBounds As Rectangle = New Rectangle(CalLocation, CalSize)
    SmallCalendarBounds.Inflate(-20, 0)

    'CopyFormScreen() the SmallCalendarBounds rectangle. Inflate as needed
End If

这部分代码计算 Outlook 侧日历对象的屏幕边界。
它可能没有那么有用,但它是一种展示如何使用这些类来解析/检查进程主窗口的 UI 元素的方法。

这是结果:


主日历窗口更易于识别和捕获。
所有日历类名称都以ViewWnd 结尾,因此,无论选择/使用哪一个,您都可以轻松识别它。

Dim LargeCalendar As AutomationElement =
        MainElement.FindAll(TreeScope.Descendants, Automation.RawViewCondition).
                    OfType(Of AutomationElement)().
                    FirstOrDefault(Function(elm) elm.Current.ClassName.Contains("ViewWnd"))

If LargeCalendar IsNot Nothing Then
    Dim LCalLocation As Point = New Point(CInt(LargeCalendar.Current.BoundingRectangle.Location.X),
                                          CInt(LargeCalendar.Current.BoundingRectangle.Location.Y))
    Dim LCalSize As Size = New Size(CInt(LargeCalendar.Current.BoundingRectangle.Width),
                                    CInt(LargeCalendar.Current.BoundingRectangle.Height))
    Dim LargeCalendarBounds As Rectangle = New Rectangle(LCalLocation, LCalSize)
End If

这是结果:


【讨论】:

  • 这将我推向了新的领域。如何添加对以下内容的引用:UIAutomationClient UIAutomationTypes WindowsBase
  • @RC Ellis 在解决方案资源管理器窗口中,找到 References ➝ 右键单击​​ ➝ Add Reference... ➝(在程序集 ➝ 框架中)➝ 按照字母顺序 :) 或者,在项目中 ➝ 属性 ➝参考 ➝ 按钮 Add....
  • @RC Ellis 所以,你不能让它工作吗?我可以使用EnumChildWindows 发布一个示例,但是为其他应用程序重现可能有点复杂。例如,小日历部分与主应用程序类没有区别。您必须通过它的位置而不是它的类名来识别它。类名为rctrl_renwnd32,与Outlook 主窗口和其他一些组件相同。尽管如此,如果你需要一个例子......
  • 我没有时间解决这个问题——这不是现在的优先事项——但我确实需要了解这一点。我遇到的是我需要监控的应用程序生成多个离散窗口 - 有时我需要获取特定窗口的屏幕截图(不是窗口的组件)。感谢您的帮助,当我有时间时,我会回去研究这个。
  • @RC Ellis 是的,当然。如果您需要监视应用程序的弹出(或非弹出)窗口,我已经发布了一些相关代码,使用UIAutomation,以检测系统中任何位置何时出现新窗口。发生这种情况时,您会被告知所有最有用的细节,从句柄(和窗口标题)开始。你可以在这里看到它:Add an event to all Forms in a Project。它的实现是出于其他原因(正如您从倾斜中看到的那样),但这就是它的作用。
【解决方案2】:

这应该会有所帮助:

获取窗口位置:

How to get and set the window position of another application in C#

获取窗口句柄:

Get Application's Window Handles

将另一个应用程序放在前面:

https://social.msdn.microsoft.com/Forums/en-US/b5a91ac4-4894-45a1-aa66-b4d548ca8163/bring-another-application-to-front?forum=winforms

您可以尝试这些技巧,然后从屏幕上复制。

【讨论】:

  • 但正如我所说,它的味道并不容易^^
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-07-02
  • 2017-10-11
  • 1970-01-01
  • 1970-01-01
  • 2020-12-30
  • 2011-06-12
  • 2010-10-27
相关资源
最近更新 更多