Results 1 to 3 of 3

Thread: [RESOLVED] Taking screenshots of forms

  1. #1

    Thread Starter
    Member
    Join Date
    May 2018
    Posts
    54

    Resolved [RESOLVED] Taking screenshots of forms

    I need an app to be able to take a screenshot of itself and save that out to a file to use for a journalling features which will track what was on the screen at the time a given control was clicked on. Ideally I just wanted to implement this as a function which can be called with a one liner whenever this is used.

    The journalling feature is all done, and for screenshots I tried using this technique I found in an old post:

    https://www.developerfusion.com/thre...form-as-image/

    The example captures to the clipboard when fired but uses a button as a separate event to save the file. I just added the code for the button onto the end to make it capture and save in one step.

    The problem I'm having is that there seems to need to be a separate process to execute with user intervention for capturing to the clipboard to complete. As it stands, if I run it the first time it gives a runtime error as the clipboard data is invalid. Then on rerunning it will save the screenshot from the first run. Then on a third rerun the screenshot from the second etc etc.

    No way of doing this (including having the capture/save as separate functions) seems to work unless the execution stops. Eg trying

    CALL Capture_Screenshot
    CALL Save_Screenshot

    won't work, but if I plonk a messagebox in between the two calls to break the execution it then will. I've even tried insanely messy attempts to workaround like pasting the screenshot into an imagebox on a hidden form and save the contents of the imagebox or using a timer to call the save, still doesn't work. There needs to be a total break in execution somewhere for the clipboard to update and I can't have this as it needs to happen silently.

    Any ideas? Or an alternative approach to doing the capture?
    Last edited by chris223b; May 20th, 2026 at 05:29 AM.

  2. #2
    PowerPoster
    Join Date
    Aug 2010
    Location
    Canada
    Posts
    2,891

    Re: Taking screenshots of forms

    Mostly ChatGPTs work, but I tweaked it a bit to support capturing the whole desktop (hwnd = 0) and to use a BitBlt/PrintWindow fallback, and raise errors for failures.

    To use it, add a cScreenshot class to your project with this code:

    Code:
    Option Explicit
    
    Private Declare Function GetWindowRect Lib "user32" ( _
                                           ByVal hwnd As Long, _
                                           ByRef lpRect As RECT) As Long
    
    Private Declare Function GetWindowDC Lib "user32" ( _
                                         ByVal hwnd As Long) As Long
    
    Private Declare Function ReleaseDC Lib "user32" ( _
                                       ByVal hwnd As Long, _
                                       ByVal hdc As Long) As Long
    
    Private Declare Function CreateCompatibleDC Lib "gdi32" ( _
                                                ByVal hdc As Long) As Long
    
    Private Declare Function DeleteDC Lib "gdi32" ( _
                                      ByVal hdc As Long) As Long
    
    Private Declare Function CreateCompatibleBitmap Lib "gdi32" ( _
                                                    ByVal hdc As Long, _
                                                    ByVal nWidth As Long, _
                                                    ByVal nHeight As Long) As Long
    
    Private Declare Function SelectObject Lib "gdi32" ( _
                                          ByVal hdc As Long, _
                                          ByVal hObject As Long) As Long
    
    Private Declare Function DeleteObject Lib "gdi32" ( _
                                          ByVal hObject As Long) As Long
    
    Private Declare Function BitBlt Lib "gdi32" ( _
                                    ByVal hDestDC As Long, _
                                    ByVal x As Long, _
                                    ByVal y As Long, _
                                    ByVal nWidth As Long, _
                                    ByVal nHeight As Long, _
                                    ByVal hSrcDC As Long, _
                                    ByVal xSrc As Long, _
                                    ByVal ySrc As Long, _
                                    ByVal dwRop As Long) As Long
    
    Private Declare Function OleCreatePictureIndirect Lib "olepro32.dll" ( _
                                                      ByRef PicDesc As PICTDESC, _
                                                      ByRef RefIID As GUID, _
                                                      ByVal fPictureOwnsHandle As Long, _
                                                      ByRef IPic As IPicture) As Long
    
    Private Declare Function PrintWindow Lib "user32" ( _
                                         ByVal hwnd As Long, _
                                         ByVal hdcBlt As Long, _
                                         ByVal nFlags As Long) As Long
    
    Private Type RECT
       Left As Long
       Top As Long
       Right As Long
       Bottom As Long
    End Type
    
    Private Type GUID
       Data1 As Long
       Data2 As Integer
       Data3 As Integer
       Data4(0 To 7) As Byte
    End Type
    
    Private Type PICTDESC
       cbSizeofStruct As Long
       picType As Long
       hImage As Long
       xExt As Long
       yExt As Long
    End Type
    
    Private Const SRCCOPY As Long = &HCC0020
    Private Const PICTYPE_BITMAP As Long = 1
    
    Private Const PW_CLIENTONLY As Long = &H1
    Private Const PW_RENDERFULLCONTENT As Long = &H2
    
    Public Function CaptureWindowByHwnd(ByVal hwnd As Long) As StdPicture
       Dim rc As RECT
       Dim width As Long
       Dim height As Long
    
       Dim hWndDC As Long
       Dim hMemDC As Long
       Dim hBmp As Long
       Dim hOldBmp As Long
    
       If hwnd = 0 Then hwnd = GetDesktopWindow  ' Use 0 for Hwnd to capture entire desktop
    
       If GetWindowRect(hwnd, rc) = 0 Then Err.Raise vbObjectError, , "Could not get Window rectangle!"
    
       width = rc.Right - rc.Left
       height = rc.Bottom - rc.Top
    
       If width <= 0 Or height <= 0 Then Err.Raise vbObjectError, , "Bad Window rectangle!"
    
       hWndDC = GetWindowDC(hwnd)
       If hWndDC = 0 Then Err.Raise vbObjectError, , "Could not get Window DC!"
       
       hMemDC = CreateCompatibleDC(hWndDC)
       If hMemDC = 0 Then
          ReleaseDC hwnd, hWndDC
          Err.Raise vbObjectError, , "Could not create memory DC!"
       End If
    
       hBmp = CreateCompatibleBitmap(hWndDC, width, height)
       If hBmp = 0 Then
          DeleteDC hMemDC
          ReleaseDC hwnd, hWndDC
          Err.Raise vbObjectError, , "Could not create compatible bitmap!"
       End If
    
       hOldBmp = SelectObject(hMemDC, hBmp)
    
       ' Try PrintWindow first to capture windows that are covered, minimized, off-screen, hardware-rendered, or using something like WebView2/DirectComposition
       ' https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-printwindow
       ' If PrintWindow fails, we'll try BitBlt (but that might capture incomplete data in the above scenarios...maybe better than nothing though)
       If PrintWindow(hwnd, hMemDC, PW_RENDERFULLCONTENT) <> 0 Then
          ' OK!
          Set CaptureWindowByHwnd = BitmapHandleToPicture(hBmp)
          
          ' Ownership of hBmp has been transferred to StdPicture.
          hBmp = 0
       Else
          Debug.Print Err.LastDllError
          Debug.Assert False   ' Failed!! Maybe old version of Windows??? Will try BitBlt method
       End If
       
       If hBmp <> 0 Then
          ' PrintWindow failed, try BitBlt
          If BitBlt(hMemDC, 0, 0, width, height, hWndDC, 0, 0, SRCCOPY) <> 0 Then
             Set CaptureWindowByHwnd = BitmapHandleToPicture(hBmp)
       
             ' Ownership of hBmp has been transferred to StdPicture.
             hBmp = 0
          End If
       End If
       
       If hOldBmp <> 0 Then SelectObject hMemDC, hOldBmp
       If hBmp <> 0 Then DeleteObject hBmp
    
       DeleteDC hMemDC
       ReleaseDC hwnd, hWndDC
    End Function
    
    Private Function BitmapHandleToPicture(ByVal hBmp As Long) As StdPicture
       Dim Pic As PICTDESC
       Dim IID_IDispatch As GUID
       Dim oPic As IPicture
    
       ' OleCreatePictureIndirect needs to know what interface we want back.
       '
       ' In this case we ask for IDispatch:
       '
       '   IID_IDispatch = {00020400-0000-0000-C000-000000000046}
       '
       ' This is the standard COM interface ID for IDispatch.
       '
       ' Why IDispatch instead of IPicture?
       '   VB6's StdPicture is automation-friendly, so requesting IDispatch
       '   is a common/reliable way to get back an object VB can assign to
       '   StdPicture/IPicture.
       '
       ' GUID memory layout:
       '
       '   Data1    = 00020400
       '   Data2    = 0000
       '   Data3    = 0000
       '   Data4(0) = C0
       '   Data4(1) = 00
       '   Data4(2) = 00
       '   Data4(3) = 00
       '   Data4(4) = 00
       '   Data4(5) = 00
       '   Data4(6) = 00
       '   Data4(7) = 46
       '
       ' Unassigned numeric fields default to zero in VB6, so we only need
       ' to set Data1, Data4(0), and Data4(7).
       With IID_IDispatch
          .Data1 = &H20400
          .Data4(0) = &HC0
          .Data4(7) = &H46
       End With
    
       ' Fill a PICTDESC structure describing the bitmap handle.
       '
       ' cbSizeofStruct:
       '   Required by OleCreatePictureIndirect so OLE knows which version/
       '   size of the structure we are passing.
       '
       ' picType:
       '   PICTYPE_BITMAP = 1
       '   This tells OLE that hImage is an HBITMAP.
       '
       ' hImage:
       '   The actual bitmap handle that will be wrapped in a StdPicture.
       With Pic
          .cbSizeofStruct = Len(Pic)
          .picType = PICTYPE_BITMAP
          .hImage = hBmp
       End With
    
       ' Third argument:
       '
       '   fPictureOwnsHandle = 1
       '
       ' This is important. It tells the created StdPicture object that it
       ' owns hBmp and should delete the bitmap when the picture is destroyed.
       '
       ' Because of that, the caller must NOT call DeleteObject(hBmp) after
       ' this function succeeds. Set your local hBmp variable to 0 after a
       ' successful call to prevent double-free / invalid-handle problems.
       '
       ' Return value:
       '   S_OK = 0
       If OleCreatePictureIndirect(Pic, IID_IDispatch, 1, oPic) = 0 Then
          Set BitmapHandleToPicture = oPic
       End If
    End Function
    Then take a screenshot of your app window and save it like so:

    Code:
    Private Sub Command1_Click()
       Dim lo_Screenshot As New CScreenshot
       Dim lo_Pic As StdPicture
       
       Set lo_Pic = lo_Screenshot.CaptureWindowByHwnd(Me.hwnd)  ' Take a screenshot of this app's window and stuff it in a StdPicture object
       
       SavePicture lo_Pic, "<SOMEFOLDERPATH>\screenshot.bmp" ' Save the BMP somewhere
    End Sub

  3. #3

    Thread Starter
    Member
    Join Date
    May 2018
    Posts
    54

    Re: Taking screenshots of forms

    Sorry for delay, this seems to work perfectly so far! Thank you!

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width