最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Automatically copying graphs and tables into an outlook mail using Excel VBA - Stack Overflow

programmeradmin6浏览0评论

As part of my internship at a bank i've been tasked to automate a weekly hedging proposal. The idea is as follows: I gathered data, graphs and tables from different excel workbooks and brought them all together into one seperate worksheet. I've put the data, graphs and tables in chronological order. Now, I've managed to create a button using VBA's to export the data which automatically makes an outlook mail, i just need to get the graphs and the tables in there.

The first graph (Chart 15) comes after data range A14:A17. After the graph comes the first table, which is from range A36:I46. After data range A48:A50 comes the second table, which is from range A52:E57. We then get the second graph (Chart 2), which comes after data (text) A62:A65. Right after we've got our final table we need to export, which is in cells A89:K99. Unfortunately, I haven't been able to get the graphs and the tables in there. I've tried different methods on Google and even asked ChatGPT to write the code for me, but I can't seem to get the graphs and tables into the outlook mail.

**I initially used this code to export just the data without the graphs and tables as a first step, which worked just fine: **

Sub ExportToOutlook()
    Dim olApp As Object
    Dim olMail As Object
    Dim bodyText As String
    Dim cell As Range
      
    On Error Resume Next
    Set olApp = GetObject(, "Outlook.Application")
    If Err.Number <> 0 Then
        Set olApp = CreateObject("Outlook.Application")
    End If
    On Error GoTo 0

    Set olMail = olApp.CreateItem(0) ' 0 = olMailItem
  
    bodyText = ""
  
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Sheets("MyWorksheet")
  
    ' Add text from A1:A4
    For Each cell In ws.Range("A1:A4")
        If Trim(cell.Value) <> "" Then ' Only add non-empty cells
            bodyText = bodyText & cell.Value & vbCrLf
        End If
    Next cell
    If bodyText <> "" Then bodyText = bodyText & vbCrLf ' Add space after the first range
  
    ' Add text from A6:A12
    For Each cell In ws.Range("A6:A12")
        If Trim(cell.Value) <> "" Then
            bodyText = bodyText & cell.Value & vbCrLf
        End If
    Next cell
    If bodyText <> "" Then bodyText = bodyText & vbCrLf ' Add space after the second range
  
    ' Add text from A14:A17
    For Each cell In ws.Range("A14:A17")
        If Trim(cell.Value) <> "" Then
            bodyText = bodyText & cell.Value & vbCrLf
        End If
    Next cell
    If bodyText <> "" Then bodyText = bodyText & vbCrLf ' Add space after the third range
  
    ' Add text from A36
    If Trim(ws.Range("A36").Value) <> "" Then
        bodyText = bodyText & ws.Range("A36").Value & vbCrLf
    End If
    If bodyText <> "" Then bodyText = bodyText & vbCrLf ' Add space after the fourth range
  
    ' Add text from A48:A50
    For Each cell In ws.Range("A48:A50")
        If Trim(cell.Value) <> "" Then
            bodyText = bodyText & cell.Value & vbCrLf
        End If
    Next cell
    If bodyText <> "" Then bodyText = bodyText & vbCrLf ' Add space after the fifth range
  
    ' Add text from A59:A60
    For Each cell In ws.Range("A59:A60")
        If Trim(cell.Value) <> "" Then
            bodyText = bodyText & cell.Value & vbCrLf
        End If
    Next cell
    If bodyText <> "" Then bodyText = bodyText & vbCrLf ' Add space after the sixth range
  
    ' Add text from A62:A65
    For Each cell In ws.Range("A62:A65")
        If Trim(cell.Value) <> "" Then
            bodyText = bodyText & cell.Value & vbCrLf
        End If
    Next cell
    If bodyText <> "" Then bodyText = bodyText & vbCrLf ' Add space after the seventh range
  
    ' Add text from A101:A105
    For Each cell In ws.Range("A101:A105")
        If Trim(cell.Value) <> "" Then
            bodyText = bodyText & cell.Value & vbCrLf
        End If
    Next cell
  
    With olMail
        .Subject = "Exported Data from Excel"
        .Body = Trim(bodyText) ' Use Trim to remove any trailing new line characters
        .Display ' Use .Send to send without displaying
    End With
  
    Set olMail = Nothing
    Set olApp = Nothing
End Sub

**After that I tried to get the graphs and tables in there, but it didn't work, so I asked ChatGPT. It changed things around, and gave me the following, and this is what im currently stuck with: **


      
  
    rangesToInclude = Array("A1:A4", "A6:A12", "A14:A17", "A36", "A48:A50", "A59:A60", "A62:A65", "A101:A105")
      
    For Each rng In rangesToInclude
        For Each cell In ws.Range(rng)
            If Trim(cell.Value) <> "" Then
                bodyText = bodyText & cell.Value & vbCrLf
            End If
        Next cell
        bodyText = bodyText & vbCrLf ' Add space after each range
    Next rng
      
    ' Set the email properties
    With olMail
        .Subject = "Exported Data from Excel"
        .Body = Trim(bodyText) ' Use Trim to remove any trailing new line characters
          
        ' Display the email to paste the content
        .Display
          
        ' Use the Word Editor of the email to paste images
        On Error Resume Next
        Set wdDoc = .GetInspector.WordEditor
          
        ' Check if wdDoc is valid
        If Not wdDoc Is Nothing Then
            ' Copy Chart 15 as a picture
            Set chartObj = ws.ChartObjects("Chart 15")
            If Not chartObj Is Nothing Then
                chartFileName = Environ$("TEMP") & "\Chart15.png"
                chartObj.Chart.Export Filename:=chartFileName, FilterName:="PNG"
                wdDoc.Application.Selection.InlineShapes.AddPicture chartFileName
                wdDoc.Application.Selection.TypeParagraph ' Add a line break
            End If
              
            ' Copy Table from A36:I46
            ws.Range("A36:I46").Copy
            wdDoc.Application.Selection.Paste
            wdDoc.Application.Selection.TypeParagraph ' Add a line break
              
            ' Copy Table from A52:E57
            ws.Range("A52:E57").Copy
            wdDoc.Application.Selection.Paste
            wdDoc.Application.Selection.TypeParagraph ' Add a line break
              
            ' Copy Chart 2 as a picture
            Set chartObj = ws.ChartObjects("Chart 2")
            If Not chartObj Is Nothing Then
                chartFileName = Environ$("TEMP") & "\Chart2.png"
                                chartObj.Chart.Export Filename:=chartFileName, FilterName:="PNG"
                wdDoc.Application.Selection.InlineShapes.AddPicture chartFileName
                wdDoc.Application.Selection.TypeParagraph ' Add a line break
            End If
              
            ' Copy Table from A89:K99
            ws.Range("A89:K99").Copy
            wdDoc.Application.Selection.Paste
            wdDoc.Application.Selection.TypeParagraph ' Add a line break
              

As part of my internship at a bank i've been tasked to automate a weekly hedging proposal. The idea is as follows: I gathered data, graphs and tables from different excel workbooks and brought them all together into one seperate worksheet. I've put the data, graphs and tables in chronological order. Now, I've managed to create a button using VBA's to export the data which automatically makes an outlook mail, i just need to get the graphs and the tables in there.

The first graph (Chart 15) comes after data range A14:A17. After the graph comes the first table, which is from range A36:I46. After data range A48:A50 comes the second table, which is from range A52:E57. We then get the second graph (Chart 2), which comes after data (text) A62:A65. Right after we've got our final table we need to export, which is in cells A89:K99. Unfortunately, I haven't been able to get the graphs and the tables in there. I've tried different methods on Google and even asked ChatGPT to write the code for me, but I can't seem to get the graphs and tables into the outlook mail.

**I initially used this code to export just the data without the graphs and tables as a first step, which worked just fine: **

Sub ExportToOutlook()
    Dim olApp As Object
    Dim olMail As Object
    Dim bodyText As String
    Dim cell As Range
      
    On Error Resume Next
    Set olApp = GetObject(, "Outlook.Application")
    If Err.Number <> 0 Then
        Set olApp = CreateObject("Outlook.Application")
    End If
    On Error GoTo 0

    Set olMail = olApp.CreateItem(0) ' 0 = olMailItem
  
    bodyText = ""
  
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Sheets("MyWorksheet")
  
    ' Add text from A1:A4
    For Each cell In ws.Range("A1:A4")
        If Trim(cell.Value) <> "" Then ' Only add non-empty cells
            bodyText = bodyText & cell.Value & vbCrLf
        End If
    Next cell
    If bodyText <> "" Then bodyText = bodyText & vbCrLf ' Add space after the first range
  
    ' Add text from A6:A12
    For Each cell In ws.Range("A6:A12")
        If Trim(cell.Value) <> "" Then
            bodyText = bodyText & cell.Value & vbCrLf
        End If
    Next cell
    If bodyText <> "" Then bodyText = bodyText & vbCrLf ' Add space after the second range
  
    ' Add text from A14:A17
    For Each cell In ws.Range("A14:A17")
        If Trim(cell.Value) <> "" Then
            bodyText = bodyText & cell.Value & vbCrLf
        End If
    Next cell
    If bodyText <> "" Then bodyText = bodyText & vbCrLf ' Add space after the third range
  
    ' Add text from A36
    If Trim(ws.Range("A36").Value) <> "" Then
        bodyText = bodyText & ws.Range("A36").Value & vbCrLf
    End If
    If bodyText <> "" Then bodyText = bodyText & vbCrLf ' Add space after the fourth range
  
    ' Add text from A48:A50
    For Each cell In ws.Range("A48:A50")
        If Trim(cell.Value) <> "" Then
            bodyText = bodyText & cell.Value & vbCrLf
        End If
    Next cell
    If bodyText <> "" Then bodyText = bodyText & vbCrLf ' Add space after the fifth range
  
    ' Add text from A59:A60
    For Each cell In ws.Range("A59:A60")
        If Trim(cell.Value) <> "" Then
            bodyText = bodyText & cell.Value & vbCrLf
        End If
    Next cell
    If bodyText <> "" Then bodyText = bodyText & vbCrLf ' Add space after the sixth range
  
    ' Add text from A62:A65
    For Each cell In ws.Range("A62:A65")
        If Trim(cell.Value) <> "" Then
            bodyText = bodyText & cell.Value & vbCrLf
        End If
    Next cell
    If bodyText <> "" Then bodyText = bodyText & vbCrLf ' Add space after the seventh range
  
    ' Add text from A101:A105
    For Each cell In ws.Range("A101:A105")
        If Trim(cell.Value) <> "" Then
            bodyText = bodyText & cell.Value & vbCrLf
        End If
    Next cell
  
    With olMail
        .Subject = "Exported Data from Excel"
        .Body = Trim(bodyText) ' Use Trim to remove any trailing new line characters
        .Display ' Use .Send to send without displaying
    End With
  
    Set olMail = Nothing
    Set olApp = Nothing
End Sub

**After that I tried to get the graphs and tables in there, but it didn't work, so I asked ChatGPT. It changed things around, and gave me the following, and this is what im currently stuck with: **


      
  
    rangesToInclude = Array("A1:A4", "A6:A12", "A14:A17", "A36", "A48:A50", "A59:A60", "A62:A65", "A101:A105")
      
    For Each rng In rangesToInclude
        For Each cell In ws.Range(rng)
            If Trim(cell.Value) <> "" Then
                bodyText = bodyText & cell.Value & vbCrLf
            End If
        Next cell
        bodyText = bodyText & vbCrLf ' Add space after each range
    Next rng
      
    ' Set the email properties
    With olMail
        .Subject = "Exported Data from Excel"
        .Body = Trim(bodyText) ' Use Trim to remove any trailing new line characters
          
        ' Display the email to paste the content
        .Display
          
        ' Use the Word Editor of the email to paste images
        On Error Resume Next
        Set wdDoc = .GetInspector.WordEditor
          
        ' Check if wdDoc is valid
        If Not wdDoc Is Nothing Then
            ' Copy Chart 15 as a picture
            Set chartObj = ws.ChartObjects("Chart 15")
            If Not chartObj Is Nothing Then
                chartFileName = Environ$("TEMP") & "\Chart15.png"
                chartObj.Chart.Export Filename:=chartFileName, FilterName:="PNG"
                wdDoc.Application.Selection.InlineShapes.AddPicture chartFileName
                wdDoc.Application.Selection.TypeParagraph ' Add a line break
            End If
              
            ' Copy Table from A36:I46
            ws.Range("A36:I46").Copy
            wdDoc.Application.Selection.Paste
            wdDoc.Application.Selection.TypeParagraph ' Add a line break
              
            ' Copy Table from A52:E57
            ws.Range("A52:E57").Copy
            wdDoc.Application.Selection.Paste
            wdDoc.Application.Selection.TypeParagraph ' Add a line break
              
            ' Copy Chart 2 as a picture
            Set chartObj = ws.ChartObjects("Chart 2")
            If Not chartObj Is Nothing Then
                chartFileName = Environ$("TEMP") & "\Chart2.png"
                                chartObj.Chart.Export Filename:=chartFileName, FilterName:="PNG"
                wdDoc.Application.Selection.InlineShapes.AddPicture chartFileName
                wdDoc.Application.Selection.TypeParagraph ' Add a line break
            End If
              
            ' Copy Table from A89:K99
            ws.Range("A89:K99").Copy
            wdDoc.Application.Selection.Paste
            wdDoc.Application.Selection.TypeParagraph ' Add a line break
              

Share Improve this question asked Mar 24 at 13:40 KnowhledgeKnowhledge 11 bronze badge 4
  • And what's not working as expected? – Shrotter Commented Mar 24 at 15:17
  • 1 "im currently stuck with" - what exactly is your code doing (or not doing) and how is that different from what you want/expect? If you get errors, then exactly what errors, and on which line(s) of your code? We can't really help unless you provide the details. – Tim Williams Commented Mar 24 at 16:52
  • 1 If you are using the Chart.Export method, then you will need to add the file as an attachment (optionally, making it invisible) and then reference that to make it display. If you are using Copy/Paste instead, then you might want to look at the Chart.CopyPicture method. Finally: you can create a "manual" version of the email, and use VBA to look at the MailItem.HTMLBody – Chronocidal Commented Mar 24 at 23:11
  • Whenever i ran the code, it wouldn't give me any errors. It would do its thing: create an outlook mail, including the text, however the tables and graphs would be completely missing. I want the code to be in chronological order, since i've got a template to follow. So there's some parts where you would have pieces of text followed by text, and parts where there should be text, followed by a graph and/or a table. Im currently only able to export the text. – Knowhledge Commented Mar 26 at 10:00
Add a comment  | 

1 Answer 1

Reset to default 0

Okay, first things first: stop using MailItem.Body (which only support Plain Text), and start using MailItem.HTMLBody (which allows you to edit the HTML directly)

With olMail
    .Subject = "Exported Data from Excel"
    .HTMLBody = Trim(bodyText) ' Use Trim to remove any trailing new line characters
    .Display ' Use .Send to send without displaying
End With

Of course, part of that means that certain characters (such as New Lines, Greater-Than or Less-Than symbols, or Ampersands) will not display correctly unless you change them to their HTML code equivalents. So, let's create a quick and basic function for that, which you can expand as needed:

Function TextToHTML(Value As String) As String
    Dim strTEMP As String
    strTEMP = Trim(Value)
    
    'Replace Symbols with HTML Codes
    strTEMP = Replace(strTEMP, "&", "&amp;")
    strTEMP = Replace(strTEMP, ">", "&gt;")
    strTEMP = Replace(strTEMP, "<", "&lt;")
    
    'Fix New-Lines
    strTEMP = Replace(strTEMP, vbCrLf, vbLf)
    strTEMP = Replace(strTEMP, vbCr, vbLf)
    strTEMP = Replace(strTEMP, vbLf, "<br/>")
    
    TextToHTML = strTEMP
End Function

Next, you'll want to convert your Tables into HTML Tables, with the <table>, <tr>, <td> and <th> tags. Again, we can build a function that will loop through the cells in the range and output the HTML as a string:

Function RangeToHTMLTable(Target As Range, Optional HeaderRow As Boolean = False) As String
    If Target.Areas.Count > 1 Then Exit Function
    Dim strTable As String, strRow As String, lRow As Long, lColumn As Long, strType As String
    Dim rCell As Range, rMerge As Range, strText As String
    
    'Using a proper Style Header and/or Classes would be more effective
    Const strStyle As String = " style=""border:1px solid black; border-collapse:collapse;"""
    
    'Start of the Table
    strTable = "<table" & strStyle & ">"
    'Loop through each row of the table
    For lRow = 1 To Target.Rows.Count
        'Start a New Row
        strRow = "<tr>"
        If HeaderRow And lRow = 1 Then
            strType = "th" 'Header
        Else
            strType = "td" 'Data
        End If
        
        'Loop through each Cell in the Row
        For lColumn = 1 To Target.Columns.Count
            Set rCell = Target.Cells(lRow, lColumn)
            Set rMerge = rCell.MergeArea
            'Change text to HTML formatting
            'Currently just does the raw text directly, and ignores Cell formatting such as Bold/Italic/Colours/etc
            strText = TextToHTML(rCell.Text)
            
            'Add the Cell to the Row when necessary
            If rCell.MergeCells Then
                'If Merged, only process the top-left cell
                If Not Intersect(rCell.Cells(1, 1), rMerge.Cells(1, 1)) Is Nothing Then
                    strRow = strRow & "<" & strType
                    If rMerge.Rows.Count > 1 Then strRow = strRow & " rowspan=""" & rMerge.Rows.Count & """"
                    If rMerge.Columns.Count > 1 Then strRow = strRow & " colspan=""" & rMerge.Columns.Count & """"
                    strRow = strRow & strStyle & ">" & strText & "</" & strType & ">"
                End If
            Else
                'Not Merged
                strRow = strRow & "<" & strType & strStyle & ">" & strText & "</" & strType & ">"
            End If
        Next lColumn
        'End of the Row
        strRow = strRow & "</tr>"
        'Add the Row to the Table
        strTable = strTable & strRow
    Next lRow
    'End of the Table
    strTable = strTable & "</table>"
    'Return the completed table
    RangeToHTMLTable = strTable
End Function

This means you can quickly add a Table to an Email with something like

bodyText = bodyText & RangeToHTMLTable(ws.Range("A52:E57")) & "<br/><br/>"

or

With ws.Range("A52:E57")
    If Application.CountA(.Resize()) > 0 Then
        'Table is not Empty
        bodyText = bodyText & RangeToHTMLTable(.Resize()) & "<br/><br/>"
    End If
End With

For Charts, there are a couple of ways to go about it. The method I will use here is to convert the Chart to an Image, and then add the Image to the Email.

The advantage here is, of course, that you can easily adapt the code to include other images, by just excluding the first step.

So, to export the Image, I threw together a quick Function that will put it in your TEMP folder, and return the FileName:

Function ExportChartAsImage(Target As ChartObject) As String
    ExportChartAsImage = "" 'Default to blank
    Dim lAttempt As Long, sFolder As String, sName As String, ForbiddenChars As Variant, element As Variant
    ForbiddenChars = Split("* "" / \ < > : | ?", " ")
    sFolder = Environ("TEMP") & "\" '{USER}\AppData\Local\Temp\
    sName = Target.Name
    'Remove any characters that are Forbidden in Filenames
    For Each element In ForbiddenChars
        sName = Replace(sName, CStr(element), "_")
    Next element
    
    'Find a Filename that is not already in use...
    For lAttempt = 0 To 99 'Only try 100 times
        If Len(Dir(sFolder & sName & IIf(lAttempt = 0, "", " (" & lAttempt & ")") & ".png")) < 3 Then
            sName = sName & IIf(lAttempt = 0, "", " (" & lAttempt & ")") & ".png"
            Exit For
        End If
    Next lAttempt
    
    'If unable to find a free Filename, return the default value and exit
    If Right(sName, 4) <> ".png" Then Exit Function
    
    'Export the Image to your TEMP folder, with the Filename
    Target.Chart.Export sFolder & sName
    DoEvents
    
    'If the file was successfully exported, then return the Filepath
    If Len(Dir(sFolder & sName)) > 3 Then ExportChartAsImage = sFolder & sName
End Function

To hold the returned FileName, I would recommend adding a new Variable, such as Dim strChartFile AS String to hold it while you check if the export worked and attach it, then display it as an HTML image:

'Try to export the Chart
strChartFile = ExportChartAsImage(ws.ChartObjects("Chart 15"))
If strChartFile <> "" Then 'If the chart was successfully exported
    olMail.Attachments.Add strChartFile, 1, 0 'Add as a Hidden attachment
    DoEvents
    Kill strChartFile 'Delete the temporary image, since it is no longer required
    DoEvents
    'Get just the FileName, without the folder
    strChartFile = Mid(strChartFile, InStrRev(strChartFile, "\") + 1)
    'Add the Image to the HTML Body, using the FileName
    bodyText = bodyText & "<img src=""cid:" & strChartFile & """><br/><br/>"
End If

(If you are adapting this to add other images, you'll probably want to get rid of the line that deletes the temporary chart image)

发布评论

评论列表(0)

  1. 暂无评论