Skip to content

Instantly share code, notes, and snippets.

@averykhoo
Last active June 30, 2020 09:15
Show Gist options
  • Save averykhoo/819a49f05a663b9c6ec282ebfadd71f9 to your computer and use it in GitHub Desktop.
Save averykhoo/819a49f05a663b9c6ec282ebfadd71f9 to your computer and use it in GitHub Desktop.
capture a screenshot using an excel macro
Option Explicit
'#######################################################################################
' Module code for capturing a screen image (Print Screen) and pasting to a new workbook
' Created on November 14th, 2009, compiled by Zack Barresse
' Compiled utilizing the following resources:
' http://www.ac6la.com/makegif.html
' http://www.andreavb.com/tip090001.html
'#######################################################################################
' Updated by Avery on 2020-06-23 to make the code work with 64-bit Excel
' Updated by Avery on 2020-06-26 to hide the Excel window for 0.1 seconds in order to screenshot what's behind
' Disclaimer: this is my first time seeing VBA, so the edits may not be the best, but it seems to work
' Rewritten utilizing some of the following resources:
' http://www.vbaexpress.com/kb/getarticle.php?kb_id=1075
' https://www.exceltip.com/general-topics-in-vba/determine-the-screen-size-using-vba-in-microsoft-excel.html
' https://jkp-ads.com/Articles/apideclarations.asp
' https://www.cadsharp.com/docs/Win32API_PtrSafe.txt
' https://codekabinett.com/rdumps.php?Lang=2&targetDoc=windows-api-declaration-vba-64-bit
'#######################################################################################
Private Const CCHDEVICENAME = 32
Private Const CCHFORMNAME = 32
Private Const CCFORMNAME As Integer = 32
Private Const SRCCOPY = &HCC0020 ' (DWORD) destination = source
Public Const SM_CXSCREEN = 0
Public Const SM_CYSCREEN = 1
Dim APPLICATION_HWND As LongPtr
Private Type DEVMODE
dmDeviceName As String * CCHDEVICENAME
dmSpecVersion As Integer
dmDriverVersion As Integer
dmSize As Integer
dmDriverExtra As Integer
dmFields As Integer
dmOrientation As Integer
dmPaperSize As Integer
dmPaperLength As Integer
dmPaperWidth As Integer
dmScale As Integer
dmCopies As Integer
dmDefaultSource As Integer
dmPrintQuality As Integer
dmColor As Integer
dmDuplex As Integer
dmYResolution As Integer
dmTTOption As Integer
dmCollate As Integer
dmFormName As String * CCFORMNAME
dmUnusedPadding As Integer
dmBitsPerPel As Long
dmPelsWidth As Long
dmPelsHeight As Long
dmDisplayFlags As Long
dmDisplayFrequency As Long
dmICMMethod As Long
dmICMIntent As Long
dmMediaType As Long
dmDitherType As Long
dmReserved1 As Long
dmReserved2 As Long
End Type
'API
Declare PtrSafe Function ReleaseDC Lib "user32" (ByVal hwnd As LongPtr, ByVal hdc As LongPtr) As Long
Declare PtrSafe Function OpenClipboard Lib "user32" (ByVal hwnd As LongPtr) As LongPtr
Declare PtrSafe Function EmptyClipboard Lib "user32" () As LongPtr
Declare PtrSafe Function SetClipboardData Lib "user32" (ByVal wFormat As LongPtr, ByVal hMem As LongPtr) As LongPtr
Declare PtrSafe Function CloseClipboard Lib "user32" () As LongPtr
Declare PtrSafe Function SelectObject Lib "gdi32" (ByVal hdc As LongPtr, ByVal hObject As LongPtr) As LongPtr
Declare PtrSafe Function DeleteDC Lib "gdi32" (ByVal hdc As LongPtr) As Long
Declare PtrSafe Function BitBlt Lib "gdi32" (ByVal hDestDC As LongPtr, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As LongPtr, ByVal xSrc As Long, ByVal ySrc As Long, ByVal dwRop As Long) As Long
Declare PtrSafe Function CreateDC Lib "gdi32" Alias "CreateDCA" (ByVal lpDriverName As String, ByVal lpDeviceName As String, ByVal lpOutput As String, lpInitData As DEVMODE) As LongPtr
Declare PtrSafe Function CreateCompatibleBitmap Lib "gdi32" (ByVal hdc As LongPtr, ByVal nWidth As Long, ByVal nHeight As Long) As LongPtr
Declare PtrSafe Function CreateCompatibleDC Lib "gdi32" (ByVal hdc As LongPtr) As LongPtr
Declare PtrSafe Function CountClipboardFormats Lib "user32" () As Long
Declare PtrSafe Function IsClipboardFormatAvailable Lib "user32" (ByVal wFormat As Long) As Long
Declare PtrSafe Function CreateIC Lib "gdi32" Alias "CreateICA" (ByVal lpDriverName As String, ByVal lpDeviceName As String, ByVal lpOutput As String, lpInitData As DEVMODE) As LongPtr
Declare PtrSafe Function GetDeviceCaps Lib "gdi32" (ByVal hdc As LongPtr, ByVal nIndex As Long) As Long
Declare PtrSafe Function EnumClipboardFormats Lib "user32" (ByVal wFormat As Long) As Long
Declare PtrSafe Function GetSystemMetrics Lib "user32" (ByVal nIndex As Long) As Long
Declare PtrSafe Function EnumWindows Lib "user32" (ByVal lpEnumFunc As LongPtr, ByVal lParam As LongPtr) As Long
Declare PtrSafe Function IsWindowVisible Lib "user32" (ByVal hwnd As LongPtr) As Boolean
Declare PtrSafe Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As LongPtr, ByVal lpString As String, ByVal cch As LongPtr) As Long
Declare PtrSafe Function SetForegroundWindow Lib "user32" (ByVal hwnd As LongPtr) As LongPtr
Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal Milliseconds As LongPtr)
'Find current open window (should be Excel)
Private Function EnumWindowsProc(ByVal hwnd As LongPtr, lParam As LongPtr) As LongPtr
Dim visible As Boolean: visible = IsWindowVisible(hwnd)
If visible And hwnd <> APPLICATION_HWND Then
Dim windowTitle As String: windowTitle = Space(260)
Call GetWindowText(hwnd, windowTitle, 260)
windowTitle = Trim(windowTitle)
windowTitle = Left(windowTitle, Len(windowTitle) - 1) 'remove null terminator
If Len(windowTitle) > 0 And windowTitle <> "Start" Then
Debug.Print hwnd
Debug.Print windowTitle
lParam = hwnd
EnumWindowsProc = 0
Else
EnumWindowsProc = 1
End If
Else
EnumWindowsProc = 1
End If
End Function
'Capture screenshot, hiding Excel first so it doesn't occlude the window of interest
Sub GetPrintScreen()
Application.ScreenUpdating = False 'not sure how much this optimization helps
APPLICATION_HWND = Application.hwnd
Dim hwnd As LongPtr
'Hide Excel in the background
Call EnumWindows(AddressOf EnumWindowsProc, VarPtr(hwnd))
SetForegroundWindow hwnd
DoEvents
Sleep 100
'Screenshot
Call CaptureScreen(0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN))
'Restore Excel to foreground
SetForegroundWindow APPLICATION_HWND
Application.ScreenUpdating = True
End Sub
'Capture screenshot with Excel in the picture
Sub JustPrintScreen()
Call CaptureScreen(0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN))
End Sub
'This seems like a generic function to paste any copied image into a new workbook
Public Sub ScreenToGIF_NewWorkbook()
Dim wbDest As Workbook, wsDest As Worksheet
Dim FromType As String, PicHigh As Single
Dim PicWide As Single, PicWideInch As Single
Dim PicHighInch As Single, DPI As Long
Dim PixelsWide As Integer, PixelsHigh As Integer
Call TOGGLEEVENTS(False)
Call GetPrintScreen
If CountClipboardFormats = 0 Then
MsgBox "Clipboard is currently empty.", vbExclamation, "Nothing to Paste"
GoTo EndOfSub
End If
'Determine the format of the current clipboard contents. There may be multiple
'formats available but the Paste methods below will always (?) give priority
'to enhanced metafile (picture) if available so look for that first.
If IsClipboardFormatAvailable(14) <> 0 Then
FromType = "pic"
ElseIf IsClipboardFormatAvailable(2) <> 0 Then
FromType = "bmp"
Else
MsgBox "Clipboard does not contain a picture or bitmap to paste.", _
vbExclamation, "No Picture"
Exit Sub
End If
Application.StatusBar = "Pasting from clipboard ..."
Set wbDest = Workbooks.Add(xlWBATWorksheet)
Set wsDest = wbDest.Sheets(1)
wbDest.Activate
wsDest.Activate
wsDest.Range("B3").Activate
'Paste a picture/bitmap from the clipboard (if possible) and select it.
'The clipboard may contain both text and picture/bitmap format items. If so,
'using just ActiveSheet.Paste will paste the text. Using Pictures.Paste will
'paste a picture if a picture/bitmap format is available, and the Typename
'will return "Picture" (or perhaps "OLEObject"). If *only* text is available,
'Pictures.Paste will create a new TextBox (not a picture) on the sheet and
'the Typename will return "TextBox". (This condition now checked above.)
On Error Resume Next 'just in case
wsDest.Pictures.Paste.Select
On Error GoTo 0
'If the pasted item is an "OLEObject" then must convert to a bitmap
'to get the correct size, including the added border and matting.
'Do this via a CopyPicture-Bitmap and then a second Pictures.Paste.
If TypeName(Selection) = "OLEObject" Then
With Selection
.CopyPicture Appearance:=xlScreen, Format:=xlBitmap
.Delete
ActiveSheet.Pictures.Paste.Select
'Modify the FromType (used below in the suggested file name)
'to signal that the original clipboard image is not being used.
FromType = "ole object"
End With
End If
'Make sure that what was pasted and selected is as expected.
'Note this is the Excel TypeName, not the clipboard format.
If TypeName(Selection) = "Picture" Then
With Selection
PicWide = .Width
PicHigh = .Height
.Delete
End With
Else
'Can get to here if a chart is selected and "Copy"ed instead of "Copy Picture"ed.
'Otherwise, ???.
If TypeName(Selection) = "ChartObject" Then
MsgBox "Use Shift > Edit > Copy Picture on charts, not just Copy.", _
vbExclamation, "Got a Chart Copy, not a Chart Picture"
Else
MsgBox "Excel pasted a '" & TypeName(Selection) & "' instead of a Picture.", _
vbExclamation, "Not a Picture"
End If
'Clean up and quit.
ActiveWorkbook.Close SaveChanges:=False
GoTo EndOfSub
End If
'Add an empty embedded chart, sized as above, and activate it.
'Positioned at cell B3 just for convenient debugging and final viewing.
'Tip from Jon Peltier: Just add the embedded chart directly, don't use the
'macro recorder method of adding a new separate chart sheet and then relocating
'the chart back to a worksheet.
With Sheets(1)
.ChartObjects.Add(.Range("B3").Left, .Range("B3").Top, PicWide, PicHigh).Activate
End With
'Paste the [resized] bitmap into the ChartArea, which creates ActiveChart.Shapes(1).
On Error Resume Next
ActiveChart.Pictures.Paste.Select
On Error GoTo 0
If TypeName(Selection) = "Picture" Then
With ActiveChart
'Adjust the position of the pasted picture, aka ActiveChart.Shapes(1).
'Adjustment is slightly greater than the .ChartArea.Left/Top offset, why ???
'''' .Shapes(1).IncrementLeft -1
'''' .Shapes(1).IncrementTop -4
'Remove chart border. This must be done *after* all positioning and sizing.
' .ChartArea.Border.LineStyle = 0
End With
'Show pixel size info above the picture-in-chart-soon-to-be-GIF/JPEG/PNG.
PicWideInch = PicWide / 72 'points to inches ("logical", not necessarily physical)
PicHighInch = PicHigh / 72
DPI = PixelsPerInch() 'typically 96 or 120 dpi for displays
PixelsWide = PicWideInch * DPI
PixelsHigh = PicHighInch * DPI
Else
'Something other than a Picture was pasted into the chart.
'This is very unlikely.
MsgBox "Clipboard corrupted, possibly by another task."
End If
EndOfSub:
Call TOGGLEEVENTS(True)
End Sub
'Originally written by Zack Barresse
Public Sub TOGGLEEVENTS(blnState As Boolean)
With Application
.DisplayAlerts = blnState
.EnableEvents = blnState
.ScreenUpdating = blnState
If blnState Then .CutCopyMode = False
If blnState Then .StatusBar = False
End With
End Sub
'Get the screen resolution in pixels per inch
Public Function PixelsPerInch() As Long
PixelsPerInch = Application.DefaultWebOptions.PixelsPerInch
'Before Microsoft Office 2000, there was no Application.DefaultWebOptions.PixelsPerInch
'Dim hdc As LongPtr
'hdc = CreateIC("DISPLAY", vbNullString, vbNullString, 0)
'PixelsPerInch = GetDeviceCaps(hdc, 88) 'LOGPIXELSX = 88 = Logical pixels/inch in X
'DeleteDC (hdc)
End Function
'Screen Capture Procedure, coordinates are expressed in pixels
Public Sub CaptureScreen(Left As Long, Top As Long, Width As Long, Height As Long)
Dim srcDC As LongPtr, trgDC As LongPtr, BMPHandle As LongPtr, dm As DEVMODE
srcDC = CreateDC("DISPLAY", "", "", dm)
trgDC = CreateCompatibleDC(srcDC)
BMPHandle = CreateCompatibleBitmap(srcDC, Width, Height)
SelectObject trgDC, BMPHandle
BitBlt trgDC, 0, 0, Width, Height, srcDC, Left, Top, SRCCOPY
OpenClipboard 0&
EmptyClipboard
SetClipboardData 2, BMPHandle
CloseClipboard
DeleteDC trgDC
ReleaseDC BMPHandle, srcDC
End Sub
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment