Using Excel VBA Macro To Capture + Save Screenshot of Specific Area In Same File
Without using SendKeys
Option Explicit
Private Declare Sub keybd_event Lib "user32" (ByVal bVk As Byte, ByVal _
bScan As Byte, ByVal dwFlags As Long, ByVal dwExtraInfo As Long)
Private Const VK_SNAPSHOT = &H2C
Sub PrintScreen()
keybd_event VK_SNAPSHOT, 1, 0, 0
ActiveSheet.Paste
End Sub
However, with this approach if you are using multiple monitors, it will only capture the active monitor, so further effort needs to be made if you need to capture the other monitor (this can probably be done with API calls but I haven't gotten that far).
NB: The AppActivate
statement can be used to activate another (non-Excel) application and if you do this, then the keybd_event
function will only capture that application, e.g;
AppActivate "Windows Command Processor" 'Modify as needed
keybd_event VK_SNAPSHOT, 1, 0, 0
ActiveSheet.Paste
Using SendKeys
, Problem Solved:
While SendKeys
is notoriously flaky, if you need to use this method due to limiations of the API method described above, you might have some problems. As we both observed, the call to ActiveSheet.Paste
was not actually pasting the Print Screen, but rather it was pasting whatever was previously in the Clipboard queue, to the effect that you needed to click your button to call the macro twice, before it would actually paste the screenshot.
I tried a few different things to no avail, but overlooked the obvious: While debugging, if I put a breakpoint on ActiveSheet.Paste
, I was no longer seeing the problem described above!
This tells me that the SendKeys
is not processed fast enough to put the data in the Clipboard before the next line of code executes, to solve that problem there are two possible solutions.
- You could try
Application.Wait
. This method seems to work when I test it, but I'd caution that it's also unreliable. - A better option would be
DoEvents
, because it's explicitly designed to handle this sort of thing:
DoEvents passes control to the operating system. Control is returned after the operating system has finished processing the events in its queue and all keys in the SendKeys queue have been sent.
This works for me whether I run the macro manually from the IDE, from the Macros ribbon, or from a button Click
event procedure:
Option Explicit
Sub CopyScreen()
Application.SendKeys "({1068})", True
DoEvents
ActiveSheet.Paste
Dim shp As Shape
With ActiveSheet
Set shp = .Shapes(.Shapes.Count)
End With
End Sub
How To Position, Resize & Crop the Image:
Regardless of which method you use, once the picture has been pasted using ActiveSheet.Paste
it will be a Shape which you can manipulate.
To Resize: once you have a handle on the shape, just assign its Height
and Width
properties as needed:
Dim shp As Shape
With ActiveSheet
Set shp = .Shapes(.Shapes.Count)
End With
shp.Height = 600
shp.Width = 800
To Position It: use the shape's TopLeftCell
property.
To Crop It: use the shp.PictureFormat.Crop
(and/or CropLeft
, CropTop
, CropBottom
, CropRight
if you need to fine-tune what part of the screenshot is needed. For instance, this crops the pasted screenshot to 800x600:
Dim h As Single, w As Single
h = -(600 - shp.Height)
w = -(800 - shp.Width)
shp.LockAspectRatio = False
shp.PictureFormat.CropRight = w
shp.PictureFormat.CropBottom = h
You can try this code in a standard Module in Excel 32 Bit.
- Screenshots can be captured immediately by calling Sub prcSave_Picture_Screen and it will capture your whole screen and save to the same path as your workbook (You can change the path and file name if you want)
- Screenshots of an active window can also be captured after calling Sub prcSave_Picture_Active_Window 3 seconds (which is adjustable)
Source: ms-office-forum.de
Option Explicit
Private Declare Sub Sleep Lib "kernel32.dll" ( _
ByVal dwMilliseconds As Long)
Private Declare Function OleCreatePictureIndirect Lib "olepro32.dll" ( _
ByRef PicDesc As PicBmp, _
ByRef RefIID As GUID, _
ByVal fPictureOwnsHandle As Long, _
ByRef IPic As IPicture) As Long
Private Declare Function CreateCompatibleDC Lib "gdi32.dll" ( _
ByVal hdc As Long) As Long
Private Declare Function CreateCompatibleBitmap Lib "gdi32.dll" ( _
ByVal hdc As Long, _
ByVal nWidth As Long, _
ByVal nHeight As Long) As Long
Private Declare Function SelectObject Lib "gdi32.dll" ( _
ByVal hdc As Long, _
ByVal hObject As Long) As Long
Private Declare Function GetDeviceCaps Lib "gdi32.dll" ( _
ByVal hdc As Long, _
ByVal iCapabilitiy As Long) As Long
Private Declare Function GetSystemPaletteEntries Lib "gdi32.dll" ( _
ByVal hdc As Long, _
ByVal wStartIndex As Long, _
ByVal wNumEntries As Long, _
ByRef lpPaletteEntries As PALETTEENTRY) As Long
Private Declare Function CreatePalette Lib "gdi32.dll" ( _
ByRef lpLogPalette As LOGPALETTE) As Long
Private Declare Function SelectPalette Lib "gdi32.dll" ( _
ByVal hdc As Long, _
ByVal hPalette As Long, _
ByVal bForceBackground As Long) As Long
Private Declare Function RealizePalette Lib "gdi32.dll" ( _
ByVal hdc As Long) As Long
Private Declare Function BitBlt Lib "gdi32.dll" ( _
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 DeleteDC Lib "gdi32.dll" ( _
ByVal hdc As Long) As Long
Private Declare Function GetDC Lib "user32.dll" ( _
ByVal hWnd As Long) As Long
Private Declare Function GetWindowRect Lib "user32.dll" ( _
ByVal hWnd As Long, _
ByRef lpRect As RECT) As Long
Private Declare Function GetSystemMetrics Lib "user32.dll" ( _
ByVal nIndex As Long) As Long
Private Declare Function GetForegroundWindow Lib "user32.dll" () As Long
Private Const SM_CXSCREEN = 0&
Private Const SM_CYSCREEN = 1&
Private Const RC_PALETTE As Long = &H100
Private Const SIZEPALETTE As Long = 104
Private Const RASTERCAPS As Long = 38
Private Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
Private Type PALETTEENTRY
peRed As Byte
peGreen As Byte
peBlue As Byte
peFlags As Byte
End Type
Private Type LOGPALETTE
palVersion As Integer
palNumEntries As Integer
palPalEntry(255) As PALETTEENTRY
End Type
Private Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(7) As Byte
End Type
Private Type PicBmp
Size As Long
Type As Long
hBmp As Long
hPal As Long
Reserved As Long
End Type
Public Sub prcSave_Picture_Screen() 'ganzer bildschirm
stdole.SavePicture hDCToPicture(GetDC(0&), 0&, 0&, _
GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN)), _
ThisWorkbook.Path & "\Screenshot.bmp" 'anpassen !!!
End Sub
Public Sub prcSave_Picture_Active_Window() 'aktives Fenster
Dim hWnd As Long
Dim udtRect As RECT
Sleep 3000 '3 sekunden pause um ein anderes Fenster zu aktivieren
hWnd = GetForegroundWindow
GetWindowRect hWnd, udtRect
stdole.SavePicture hDCToPicture(GetDC(0&), udtRect.Left, udtRect.Top, _
udtRect.Right - udtRect.Left, udtRect.Bottom - udtRect.Top), _
ThisWorkbook.Path & "\Screenshot.bmp" 'anpassen !!!
End Sub
Private Function CreateBitmapPicture(ByVal hBmp As Long, ByVal hPal As Long) As Object
Dim Pic As PicBmp, IPic As IPicture, IID_IDispatch As GUID
With IID_IDispatch
.Data1 = &H20400
.Data4(0) = &HC0
.Data4(7) = &H46
End With
With Pic
.Size = Len(Pic)
.Type = 1
.hBmp = hBmp
.hPal = hPal
End With
Call OleCreatePictureIndirect(Pic, IID_IDispatch, 1, IPic)
Set CreateBitmapPicture = IPic
End Function
Private Function hDCToPicture(ByVal hDCSrc As Long, ByVal LeftSrc As Long, _
ByVal TopSrc As Long, ByVal WidthSrc As Long, ByVal HeightSrc As Long) As Object
Dim hDCMemory As Long, hBmp As Long, hBmpPrev As Long
Dim hPal As Long, hPalPrev As Long, RasterCapsScrn As Long, HasPaletteScrn As Long
Dim PaletteSizeScrn As Long, LogPal As LOGPALETTE
hDCMemory = CreateCompatibleDC(hDCSrc)
hBmp = CreateCompatibleBitmap(hDCSrc, WidthSrc, HeightSrc)
hBmpPrev = SelectObject(hDCMemory, hBmp)
RasterCapsScrn = GetDeviceCaps(hDCSrc, RASTERCAPS)
HasPaletteScrn = RasterCapsScrn And RC_PALETTE
PaletteSizeScrn = GetDeviceCaps(hDCSrc, SIZEPALETTE)
If HasPaletteScrn And (PaletteSizeScrn = 256) Then
LogPal.palVersion = &H300
LogPal.palNumEntries = 256
Call GetSystemPaletteEntries(hDCSrc, 0, 256, LogPal.palPalEntry(0))
hPal = CreatePalette(LogPal)
hPalPrev = SelectPalette(hDCMemory, hPal, 0)
Call RealizePalette(hDCMemory)
End If
Call BitBlt(hDCMemory, 0, 0, WidthSrc, HeightSrc, hDCSrc, LeftSrc, TopSrc, 13369376)
hBmp = SelectObject(hDCMemory, hBmpPrev)
If HasPaletteScrn And (PaletteSizeScrn = 256) Then
hPal = SelectPalette(hDCMemory, hPalPrev, 0)
End If
Call DeleteDC(hDCMemory)
Set hDCToPicture = CreateBitmapPicture(hBmp, hPal)
End Function