2

My app runs in front of a Google Chrome Window. The right hand quarter of the chrome window is always visible as I may need to interact with it. The left hand side of the chrome window is completely covered as this is where my controls are.

The trouble is, there are a couple of things on the left side of the Chrome Window which might change from time to time, and I need to know when this happens. It would look really messy to make transparent areas on my form, so what I need to do is shoot a graphic of the chrome window behind my form every few seconds so I can check for any differences.

I have found numerous examples of code using bitblt etc but so far all I'm getting is solid black. Nothing is returning any image. So how can I shoot a graphic of the Chrome Window?

If anyone could provide a function which returns a bitmap from a hwnd, that would be brilliant!

Jimi
  • 29,621
  • 8
  • 43
  • 61
Trickie
  • 35
  • 2

1 Answers1

2

Well, give PrintWindow a try.

This function issues a WM_PRINT or WM_PRINTCLIENT message (depending on the flags specified), so the Window that receives and processes the message prints itself to the hDC passed in the function call.
► The Window can be completely obscured by another, it doesn't matter, it's not a screenshot. The Window has to process the message, though.


The static (Shared) RenderWindow method shown here, returns a Bitmap object generated by the PrintWindow function.

You should try to pass True as the clientAreaOnly argument, to set PW_CLIENTONLY as the nFlags option, because many WPF apps are designed to hide the default Window Frame, while WinForms Windows usually don't, so try to pass False in this case.

The optional tryGetFullContent argument sets the PW_RENDERFULLCONTENT flag, which is available from Windows 8.1+. This flag is not documented.
UPDATE: in this case, it's what gets the job done.

To get the Handle of the Window you care about, you have many options.

Process.GetProcessesByName("processName").FirstOrDefault(...) or Process.GetProcessById(id), if you know the Process Id.

Or FindWindowEx, or EnumWindows, or EnumDesktopWindows, depending on the context.
See here: How to obtain task bar Notification Area width for a simple use of the FindWindowEx function.

Assuming you have a WinForms app, you can call the method like this, to show a Bitmap of the rendered Window in a PictureBox:

Dim proc = Process.GetProcessesByName("WINWORD").FirstOrDefault(Function(p) p.MainWindowHandle <> IntPtr.Zero)
If proc IsNot Nothing Then
    PictureBox1.Image = RenderWindow(proc.MainWindowHandle, True)
End If

Win32 Declarations and implementation of the RenderWindow method:

► Here, I'm using DwmGetWindowAttribute, specifying DWMWA_EXTENDED_FRAME_BOUNDS as the attribute, to retrieve the Window rectangle.
You could also use GetWindowRect, but this function is not DpiAware (and a bit old :), it may return a wrong size in specific conditions.

Public Shared Function RenderWindow(hWnd As IntPtr, clientAreaOnly As Boolean, Optional tryGetFullContent As Boolean = False) As Bitmap
    Dim printOption = If(clientAreaOnly, PrintWindowOptions.PW_CLIENTONLY, PrintWindowOptions.PW_DEFAULT)
    printOption = If(tryGetFullContent, PrintWindowOptions.PW_RENDERFULLCONTENT, printOption)

    Dim dwmRect = New Rectangle()
    Dim hResult = DwmGetWindowAttribute(hWnd, DWMWINDOWATTRIBUTE.DWMWA_EXTENDED_FRAME_BOUNDS, dwmRect, Marshal.SizeOf(Of Rectangle)())
    If hResult < 0 Then
        Marshal.ThrowExceptionForHR(hResult)
        Return Nothing
    End If

    Dim bmp = New Bitmap(dwmRect.Width, dwmRect.Height)
    Using g = Graphics.FromImage(bmp)
        Dim hDC = g.GetHdc()

        Try
            Dim success = PrintWindow(hWnd, hDC, printOption)
            ' success result not fully handled here
            If Not success Then
                Dim win32Error = Marshal.GetLastWin32Error()
                Return Nothing
            End If
            Return bmp
        Finally
            g.ReleaseHdc(hDC)
        End Try

    End Using
End Function

Native methods:

<DllImport("user32.dll", SetLastError:=True)>
Friend Shared Function PrintWindow(hwnd As IntPtr, hDC As IntPtr, nFlags As UInteger) As Boolean
End Function

<DllImport("dwmapi.dll", SetLastError:=True)>
Friend Shared Function DwmGetWindowAttribute(hwnd As IntPtr, dwAttribute As DWMWINDOWATTRIBUTE, ByRef pvAttribute As Rectangle, cbAttribute As Integer) As Integer
End Function

Public Enum DWMWINDOWATTRIBUTE As UInteger
    DWMWA_NCRENDERING_ENABLED = 1      ' [get] Is non-client rendering enabled/disabled
    DWMWA_NCRENDERING_POLICY           ' [set] DWMNCRENDERINGPOLICY - Non-client rendering policy
    DWMWA_TRANSITIONS_FORCEDISABLED    ' [set] Potentially enable/forcibly disable transitions
    DWMWA_ALLOW_NCPAINT                ' [set] Allow contents rendered In the non-client area To be visible On the DWM-drawn frame.
    DWMWA_CAPTION_BUTTON_BOUNDS        ' [get] Bounds Of the caption button area In window-relative space.
    DWMWA_NONCLIENT_RTL_LAYOUT         ' [set] Is non-client content RTL mirrored
    DWMWA_FORCE_ICONIC_REPRESENTATION  ' [set] Force this window To display iconic thumbnails.
    DWMWA_FLIP3D_POLICY                ' [set] Designates how Flip3D will treat the window.
    DWMWA_EXTENDED_FRAME_BOUNDS        ' [get] Gets the extended frame bounds rectangle In screen space
    DWMWA_HAS_ICONIC_BITMAP            ' [set] Indicates an available bitmap When there Is no better thumbnail representation.
    DWMWA_DISALLOW_PEEK                ' [set] Don't invoke Peek on the window.
    DWMWA_EXCLUDED_FROM_PEEK           ' [set] LivePreview exclusion information
    DWMWA_CLOAK                        ' [set] Cloak Or uncloak the window
    DWMWA_CLOAKED                      ' [get] Gets the cloaked state Of the window
    DWMWA_FREEZE_REPRESENTATION        ' [set] BOOL, Force this window To freeze the thumbnail without live update
    DWMWA_LAST
End Enum

Public Enum PrintWindowOptions As UInteger
    PW_DEFAULT = 0
    PW_CLIENTONLY = 1
    PW_RENDERFULLCONTENT = 2
End Enum
Jimi
  • 29,621
  • 8
  • 43
  • 61
  • I had really high hopes for your function when I saw it Jimi - unfortunately it just returns a solid black bmp just like all the other methods I have tried! Any idea what's wrong? – Trickie Dec 16 '20 at 09:30
  • Actually this seems to be a problem with Google Chrome. It works with other windows, actually so does bitblt, but nothing seems to work with a chrome window, just returns a black area each time. – Trickie Dec 16 '20 at 10:00
  • UPDATE: I found a way to make this work with the chrome window! It is necessary to use tryGetFullContent, i.e. Dim bmp = RenderWindow(hwnd, False, True). Using clientAreaOnly doesn't work with chrome windows! Thank you for you help Jimi, got there in the end! – Trickie Dec 16 '20 at 10:55