0

I need to screen capture a particular window of a multi-window application. A good example: I run OUTLOOK with two windows - Mail and Calendar. When I use Process.GetProcessesByName(ApplicationToWatch).FirstOrDefault() I of course get the "first" window. How do I get access to the "second" or subsequent windows? (Interesting that there is a FirstOrDefault but not another method to get something else - I'm clearly missing something).

RC Ellis
  • 3
  • 4
  • this is quite difficult if you are only using DotNet. I suggest using the win32 api. You could get the handle of the window and try to paint it into a bitmap. If you are shure the window is in the foreground and you know the position of the window you could use the CopyFromScreen function. – Phillip Aug 31 '18 at 15:42
  • Yes, apparently it *is* difficult in DotNet. :-) . My issue is to get the position of the window in the first place. Oh well - in my actual application, I'm getting the right window now - but anticipating running into the OUTLOOK example keeps me up at night. – RC Ellis Aug 31 '18 at 16:00

2 Answers2

2

You already have some indications on how to get a child window Handle using EnumChildWindows, to be associated with GetClassName.

So I'll propose you another method, using UI Automation.
It's probably not so well known, but in this case it's quite straightforward and it can simplify this task.

You just need to know the Handle of the Main Window whose child you want to enumerate and filter the collection (AutomationElementCollection) of child windows using LINQ's .Where() or .FirstOrDefault() methods.

As a note, the UI Automation enumeration is not as thorough as EnumChildWindows. Also, the returned Class Names can be different, in some cases, from the actual class name of specific UI Elements.
But those are, possibly, UI Elements your not interested in.

To get the Handle of the Outlook main window, we use Process.GetProcessesByName() as usual.
The Automation Element is then aquired using the AutomationElement.FromHandle() method.

To find specific child Elements, we can use this Main Automation Element (source reference) .FindAll() method, filtering the returned collection, if needed, with .Where() - to define a sub-collection - or .FirstOrDefault(), to get the reference of a specific Element Class or Handle (or other known details).

These methods show how to take a screenshot of Outlook's side Calendar panel and the Main Calendar window.

Use the code already discussed in your previous question to take the actual screenshot of the selected Screen bounds.

This code requires to add a reference to:
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

This part of the code calculates the Screen bounds of Outlook's side Calendar object.
It may not be that useful, but it's a way to show how you can use these classes to parse/inspect the UI Elements of a process Main Window.

This is the result:

Outlook Small calendar

The Main Calendar window is simpler to identify and capture.
All Calendars classes names end with ViewWnd, thus, no matter which one is selected/in use, you will always be able to identify it quite easily.

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

This is the result:

Outlook Main Calendar

Jimi
  • 29,621
  • 8
  • 43
  • 61
  • This is pushing me into new territory. How do I add a reference to: UIAutomationClient UIAutomationTypes WindowsBase – RC Ellis Sep 04 '18 at 15:23
  • @RC Ellis In your Solution Explorer window, locate `References` ➝ Right Click ➝ `Add Reference...` ➝ (in Assemblies ➝ Framework) ➝ Follow the alphabetic order :) Or, in the Project ➝ Properties ➝ References ➝ Button `Add...`. – Jimi Sep 04 '18 at 15:34
  • @RC Ellis So, you couldn't get it to work? I can post an example using `EnumChildWindows`, but it could be a bit complicated to reproduce for other application. For example, the small calendar section is indistinguishable from the Main application class. You have to identify it by its position, rather than its class name. The class name is `rctrl_renwnd32`, same as the Outlook main window and some other components. Nonetheless, if you need an example... – Jimi Sep 05 '18 at 16:21
  • I haven't had time to work through this - it's not a priority right now - but I do need to understand this. What I'm running into is that the application I need to monitor generates multiple discrete windows - and there will be a time when I need to get a screen shot of a particular window (not a component of a window). I appreciate your help, and when I get moments I go back to study this. – RC Ellis Sep 05 '18 at 16:26
  • 1
    @RC Ellis Yes, sure. If you need to monitor an application's popup (or non-popup) windows, I've already posted some related code, using `UIAutomation`, to detect when a new Window appears, from anywhere in the System. When this happens, you are informed about all it's most useful details, starting from it Handle (and Window Title). You can see it here: [Add an event to all Forms in a Project](https://stackoverflow.com/questions/51491566/add-an-event-to-all-forms-in-a-project?answertab=active#tab-top). It's implemented for other reasons (as you see from the tilte), but this is what it does. – Jimi Sep 05 '18 at 16:36
0

This should help:

getting the window position:

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

getting the window handle:

Get Application's Window Handles

bringing another application into the front:

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

you could try those tricks and just copy from screen.

Phillip
  • 789
  • 4
  • 22