Using Excel VBA Macro To Capture + Save Screenshot of Specific Area In Same File

前端 未结 3 1711
北荒
北荒 2020-12-08 12:46

I\'m trying to create a macro which uses an ActiveX control button (click) to take a screenshot of my desktop screen and save it within the same excel sheet as the button. H

相关标签:
3条回答
  • 2020-12-08 13:04
    Sub SavePicToFile(namefile)
     Selection.CopyPicture xlScreen, xlBitmap
     Application.DisplayAlerts = False
     Set tmp = Charts.Add
     On Error Resume Next
     With tmp
        .SeriesCollection(1).Delete
        .Width = Selection.Width
        .Height = Selection.Height
        .Paste
        .Export filename:=namefile, Filtername:="jpeg"
        .Delete
     End With
    End Sub
    foto = Application.ActiveWorkbook.Path & "\Foto" & ".jpeg"
    ActiveWorkbook.Sheets(1).Range("A1:Z30").Select
    SavePicToFile (foto)
    
    0 讨论(0)
  • 2020-12-08 13:13

    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.

    1. You could try Application.Wait. This method seems to work when I test it, but I'd caution that it's also unreliable.
    2. 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
    
    0 讨论(0)
  • 2020-12-08 13:13

    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
    
    0 讨论(0)
提交回复
热议问题