Object Required: Excel & Word VBA, Dictionaries, and storing ranges - excel

I'm in need of help in understanding why I keep getting an "object required" error at Ln82. I thought you could store anything in a dictionary?
The workflow is:
Start the program
Create the dictionary of temporary items to loop through the input box
Use this dictionary later to store all the user input as ranges
Call a sub routine to open destination document(Mysupes)
Call a sub routine to open source excel wb(Alert)
Prompt user 12 times(via loop) to select ranges in source excel
Paste into destination word doc(at this point I don't care where, I just need to paste the damn thing)
Also please ignore any of the comments, it is just my scratch work where I've tried different avenues.
Sub AlertToSupes()
'Declarations
Dim MyAlert As String
Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")
'Dim key As Variant
Dim v As Long
Dim r As Variant
Dim Mysupes As Document
'Mysupes.Visible = True
'Dim AlertToSupeData As Object
Application.ScreenUpdating = True
'Collection of objects to get from Alert doc and paste into Supes
'Dim colSupesData As Collection
'Set colSupesData = New Collection
' colSupesData.Add "Project team names"
' colSupesData.Add "Programming"
' colSupesData.Add "Date(today)"
' colSupesData.Add "Subject(Blind study name in Alert)"
' colSupesData.Add "LRW job#"
' colSupesData.Add "LOI"
' colSupesData.Add "Incidence"
' colSupesData.Add "Sample size"
' colSupesData.Add "Dates(select from Alert)"
' colSupesData.Add "Devices allowed"
' colSupesData.Add "Respondent qualifications(from Alert)"
' colSupesData.Add "Quotas"
'Dictionary of attributes(alternative to list)
dict.Add "Project team names", ""
dict.Add "Programming", ""
dict.Add "Date(today)", ""
dict.Add "Subject(Blind study name in Alert)", ""
dict.Add "LRW job#", ""
dict.Add "LOI", ""
dict.Add "Incidence", ""
dict.Add "Sample size", ""
dict.Add "Dates(select from Alert)", ""
dict.Add "Devices allowed", ""
dict.Add "Respondent qualifications(from Alert)", ""
dict.Add "Quotas", ""
'Open up the Supes
Call OpenSupes
'Open up the Alert file
MyAlert = Application.GetOpenFilename()
Workbooks.Open (MyAlert)
'Loop for subroutine
For Each key In dict.keys
Debug.Print (key)
Call Cpy(key)
dict.item = r.Value
Next key
End Sub
Sub Cpy(key As Variant)
'Loop that asks for user-defined input for every field of Supes
Dim r As Range, LR As Long
Dim Mysupes As Object
On Error Resume Next
Set r = Application.InputBox("Select the cell that contains " & key, Type:=8)
On Error GoTo 0
If r Is Nothing Then Exit Sub
'LR = Cells(Rows.Count, r.Column).End(xlUp).Row
'Range(Cells(5, r.Column), Cells(LR, r.Column)).Copy Destination:=Cells(5, r.Column + 1)
r.Copy
With Mysupes
'AppWord.Documents.Add
AppWord.Selection.PasteExcelTable
Application.CutCopyMode = False
'Set MySupes = Nothing
End With
End Sub
Sub OpenSupes()
'Dim Mysupes As Object
Dim wordapp As Object
Dim Mysupes As FileDialog
Set wordapp = CreateObject("word.Application")
Set Mysupes = Application.FileDialog( _
FileDialogType:=msoFileDialogOpen)
Mysupes.Show
'Set Mysupes = wordapp.Documents.Open("\\10.1.11.169\LRW\Field (New)\01 Admin\02 Standard Documents\01 Supes\Supes Memo - Online Study.dotx")
wordapp.Visible = True
End Sub

There are numerous issues with the code.
1) Key one is that you are trying to use the Workbooks.Open method on a Word document. [Workbooks.Open][1] expects a workbook variable. So this:
Workbooks.Open (MyAlert)
isn't going to work with a Word doc.
You want Documents.Open but also need a Word application to use this with so you will need to create that application instance in the appropriate sub. You do it elsewhere with wordapp.Documents.Open
2) Use Option Explicit at the top of your code and declare all your variables. There are missing ones throughout.
3) Quit applications after opening them or eventually something will crash due to too many running instances.
4) Application.ScreenUpdating = True should be at the end of the sub to update the screen and only if you had Application.ScreenUpdating = False before that.
5) As #CindyMeister notes: You shouldn't need an On Error Resume Next around InputBox. You can test by setting the result to a variable and testing that.See Trouble with InputBoxes
6) And what #dbmitch said. A function conversion would be a logical choice.

The error you're reporting is generated inside your loop wen you try to assign r.Value to your dictionary
For Each key In dict.keys
Debug.Print (key)
Call Cpy(key)
dict.item = r.Value
Next key
You're assuming Cpy subroutine is sending the r cell back to your program,
but it's not - r is declared locally in your program as a variant and locally
inside Cpy as a Range.
You need to return r as a function value instead of a closed subroutine,
or you can make the r Range type variable a global so it can be seen by all your program

Related

VBA Date import files issue

The macro was given to me by my predecessor.
I have an issue with the ‘date’ when importing data using the macro. It works well when I import a data file into a macro and transform it into a report, this all works well.
The issue is that if I import a 2nd data file today again after the 1st round it won’t work. I get a prompt message from the macro saying "No new rows to import. If this is wrong check the 'LastImportDates' sheet". It will only work the next day. This is the issue I am struggling with as I need to import several files on the same day.
Please see the VBA codes below, It shows the section of the VBA macro. I hope this is the one that caused the issue. I am hoping that you can point me to where I need to change it, allowing me a import multiple data files on the same day.
I hope everything makes sense. I will endeavor my best to assist you further if needed.
Best regards
V
Sub MainCopyData()
Set rsheet = mbook.Sheets("RAW")
rsheet.Activate
rsheet.Rows("2:100000").EntireRow.Delete
Call FindFile
Call CopyData
rsheet.Activate
tempbook.Close SaveChanges:=False
End Sub
Sub FindFile()
Dim fso As Object 'FileSystemObject
Dim fld As Object 'Folder
Dim fl As Object 'File
Dim Mask As String
Set fso = CreateObject("scripting.FileSystemObject") ' late binding
Set fldStart = fso.GetFolder(ActiveWorkbook.Path) ' <-- use your FileDialog code here
For Each fld In fldStart.Files
If InStr(1, fld.Name, "data_Checkout_Starts_ALL_TIME.csv") > 0 Then
Set fl = fld
Exit For
End If
Next
If fld Is Nothing Then
With Application.FileDialog(msoFileDialogFilePicker)
'Makes sure the user can select only one file
.AllowMultiSelect = False
'Show the dialog box
.Show
'Store in fullpath variable
Set fl = fso.GetFile(.SelectedItems.Item(1))
End WithEnd If
Set tempbook = Workbooks.Open(fl.Path, Local:=True)
End Sub
Sub CopyData()
lastimport = mbook.Sheets("ImportDates").Cells(1, 1).End(xlDown).Value
Set tempsht = tempbook.Sheets(1)
FirstR = 0
LastR = 0
dateC = findCol("EventDate", tempsht)
For x = 2 To tempsht.Cells(1, 1).End(xlDown).Row
If FirstR = 0 And tempsht.Cells(x, dateC) > lastimport Then
FirstR = x
End If
If tempsht.Cells(x, dateC).Value < Date Then
LastR = x
End If
Next x
If FirstR > 0 Then
mbook.Sheets("ImportDates").Cells(1, 1).End(xlDown).Offset(1, 2).Value = LastR - FirstR - 1
mbook.Sheets("ImportDates").Cells(1, 1).End(xlDown).Offset(1, 1).Value = Date
mbook.Sheets("ImportDates").Cells(1, 1).End(xlDown).Offset(1, 0).Value = Date - 1
Else
MsgBox ("No new rows to import. If this is wrong check the 'LastImportDates' sheet")
tempbook.Close SaveChanges:=False
End
End If
rsheet.Activate
tempsht.Rows(FirstR & ":" & LastR).Copy rsheet.Cells(2, 1)
End Sub```

How to optimize a macro used to insert text from a dynamic range from Microsoft Excel into Word

I have an Excel macro written which:
Opens up a specific Word document
Checks if the Excel named range is empty
If not empty, checks if the Word bookmark range exists
Goes back to the Excel named range and checks if the cell is empty besides it
4.a. If the cell beside it is empty, the cell from from Step 2replaces the Word bookmark
range
4.b If the cell beside it is not empty, it stores the dynamic range of cells into a variable
A For loop is used to cycle through the dynamic range of cells and insert them one by one in separate bullet points like so:
Cell 1.Value
Cell 2.Value
Cell 3.Value
So my code works, but I'm unable to change it into a Subroutine to call it multiple times throughout the macro
Here is my code:
Sub GenerateWordDoc()
Dim theApp As Object
On Error Resume Next
MacScript "tell application id ""com.microsoft.Word"" to activate"
Err.Clear
Set theApp = GetObject(, "Word.Application")
theApp.Visible = True
Dim doc As Object
With theApp
.Visible = True
.Activate
End With
Dim docName As String
docName = 'test-doc.docx'
Set doc = theApp.Documents.Open(ThisWorkbook.Path & "/" & docName, ReadOnly:=True)
' Looping through cells and adding it to Bullet lists
If IsEmpty(Sheets("Sheet1").Range("excel_dynamic_range")) Then
Call ReplaceBookmarkWithSingleString(doc, "List is empty", "word_bookmark1")
Else
If doc.Bookmarks.Exists("word_bookmark1") Then
doc.Bookmarks("word_bookmark1").Range.Select
With theApp.Selection
.EndKey Unit:=wdLine ' go to end of line after selection
.TypeText "List:"
' If there is no value in adjacent cell to the right
If IsEmpty(Sheets("Sheet1").Range("excel_dynamic_range").Offset(, 1))
Then
.TypeParagraph
.Range.ListFormat.ListIndent
.TypeText Sheets("Sheet1").Range("excel_dynamic_range").Value
Else
Dim multiple_option_values3() As Variant
multiple_option_values3 = Sheets("Sheet1").Range("excel_dynamic_range", Range("excel_dynamic_range").End(xlToRight)).Value
Dim R3 As Long
Dim C3 As Long
For R3 = 1 To UBound(multiple_option_values3, 1)
For C3 = 1 To UBound(multiple_option_values3, 2)
.TypeParagraph
If C3 = 1 Then
.Range.ListFormat.ListIndent
End If
.TypeText multiple_option_values3(R3, C3)
Next C3
Next R3
End If
End With
End If
End If
End Sub
So when I change the Looping through cells and insert into bullet point above into a Subroutine, passing in the theApp variable as a param isn't executing the code at all:
Sub ReplaceBookmarkMultipleOptionsBulletList(ByVal theApp As Object, doc As Object, bookmarkName As String, excelRange As Range, initialBulletText As String, indentBulletList As Boolean)
Debug.Print theApp
Debug.Print doc
Debug.Print bookmarkName
If doc.Bookmarks.Exists(bookmarkName) Then
Debug.Print "bookmark exists in Word"
doc.Bookmarks(bookmarkName).Range.Select
theApp.Visible = True
With theApp.Selection
Debug.Print theApp.Selection
.EndKey Unit:=wdLine
.TypeParagraph
.TypeText "change the text again"
If excelRange.Count = 1 Then
.TypeParagraph
If indentBulletList = True Then
.Range.ListFormat.ListIndent
End If
.TypeText Sheets("Sheet 1").excelRange.Value
Else
Dim multiple_option_values() As Variant
multiple_option_values = excelRange.Value
Dim R As Long
Dim C As Long
For R = 1 To UBound(multiple_option_values, 1)
For C = 1 To UBound(multiple_option_values, 2)
.TypeParagraph
If C = 1 And indentBulletList = True Then
.Range.ListFormat.ListIndent
End If
.TypeText multiple_option_values(R, C)
Next C
Next R
End If
End With
End If
End Sub
How can I fix or optimize the macro above to insert the values from a dynamic range of cells into a Word doc?

save arrays in dictionary using for loop in VBA

I have a sheet with arrays with data that I want to save in a dictionary. The column space between each array is constant and the tables are similar size. I have names on top of each array (first one in cell J3) that should be the key and the data should be the item. How can I create a loop that saves all arrays and stops when the selected range is empty?
Sub Dictionary()
Dim dictionary() As Dictionary
Dim nCol As Integer, i As Integer
nCol = 13
Sheets("Sheet1").Activate
Range(Cells(27, 11), Cells(36, 21)).Activate
For i = 1 To nCol
' dictionary(i) = Selection.Value
Selection.Offset(RowOffset:=0, ColumnOffset:=nCol).Select
Next i
End Sub
Thank you
Please, test the next code. It needs a reference to 'Microsoft Scripting Runtime':
Sub RangesIndictionary()
Dim sh As Worksheet, dict As New scripting.dictionary, nCol As Long
Dim i As Long, rngInit As Range, rngKey As Range, keyName As String
Set sh = Sheets("Sheet1")
nCol = 13
Set rngKey = sh.Range("J3")
keyName = rngKey.Value
Set rngInit = sh.Range(sh.cells(27, 11), sh.cells(36, 21))
Do While keyName <> ""
dict.Add keyName, rngInit
Set rngKey = rngKey.Offset(0, nCol)
keyName = rngKey.Value
Set rngInit = rngInit.Offset(0, nCol)
Loop
Debug.Print dict.count & " ranges have been placed in the dictionary"
Debug.Print "The first range is " & dict(sh.Range("J3").Value).Address
End Sub
It may work using Late binding, but it is better to use the reference and benefit of the intellisense suggestions.
If it is possible that the name to be the same for two such ranges, the code should preliminary check if the key exists. And you must tell us how to be treated such a situation.
In order to automatically add the necessary reference, please firstly run the next code:
Sub addScrRunTimeRef()
'Add a reference to 'Microsoft Scripting Runtime':
'In case of error ('Programmatic access to Visual Basic Project not trusted'):
'Options->Trust Center->Trust Center Settings->Macro Settings->Developer Macro Settings->
' check "Trust access to the VBA project object model"
On Error Resume Next
Application.VBE.ActiveVBProject.References.AddFromFile "C:\Windows\SysWOW64\scrrun.dll"
If err.Number = 32813 Then
err.Clear: On Error GoTo 0
MsgBox "The reference already exists...": Exit Sub
Else
On Error GoTo 0
MsgBox """Microsoft Scripting Runtime"" reference added successfully..."
End If
End Sub
It will remain only if you save the working workbook...

How to copy Outlook mail message into excel using VBA or Macros

I'm a newbie in VBA and Macros. If someone helps me with VBA code and macros, it will be helpful.
Daily I'll receive around 50-60 mails with one standard subject: "Task Completed". I have created a rule to all those mail to move to a specific folder: "Task Completed".
Reading all 50-60 mails a day and updating all mails is very much time consuming.
All 50-60 mails coming to my inbox will have same subject but from different users.
Body of mail will vary.
I'm using Outlook 2010 and Excel 2010.
Since you have not mentioned what needs to be copied, I have left that section empty in the code below.
Also you don't need to move the email to the folder first and then run the macro in that folder. You can run the macro on the incoming mail and then move it to the folder at the same time.
This will get you started. I have commented the code so that you will not face any problem understanding it.
First paste the below mentioned code in the outlook module.
Then
Click on Tools~~>Rules and Alerts
Click on "New Rule"
Click on "start from a blank rule"
Select "Check messages When they arrive"
Under conditions, click on "with specific words in the subject"
Click on "specific words" under rules description.
Type the word that you want to check in the dialog box that pops up and click on "add".
Click "Ok" and click next
Select "move it to specified folder" and also select "run a script" in the same box
In the box below, specify the specific folder and also the script (the macro that you have in module) to run.
Click on finish and you are done.
When the new email arrives not only will the email move to the folder that you specify but data from it will be exported to Excel as well.
CODE
Const xlUp As Long = -4162
Sub ExportToExcel(MyMail As MailItem)
Dim strID As String, olNS As Outlook.Namespace
Dim olMail As Outlook.MailItem
Dim strFileName As String
'~~> Excel Variables
Dim oXLApp As Object, oXLwb As Object, oXLws As Object
Dim lRow As Long
strID = MyMail.EntryID
Set olNS = Application.GetNamespace("MAPI")
Set olMail = olNS.GetItemFromID(strID)
'~~> Establish an EXCEL application object
On Error Resume Next
Set oXLApp = GetObject(, "Excel.Application")
'~~> If not found then create new instance
If Err.Number <> 0 Then
Set oXLApp = CreateObject("Excel.Application")
End If
Err.Clear
On Error GoTo 0
'~~> Show Excel
oXLApp.Visible = True
'~~> Open the relevant file
Set oXLwb = oXLApp.Workbooks.Open("C:\Sample.xls")
'~~> Set the relevant output sheet. Change as applicable
Set oXLws = oXLwb.Sheets("Sheet1")
lRow = oXLws.Range("A" & oXLApp.Rows.Count).End(xlUp).Row + 1
'~~> Write to outlook
With oXLws
'
'~~> Code here to output data from email to Excel File
'~~> For example
'
.Range("A" & lRow).Value = olMail.Subject
.Range("B" & lRow).Value = olMail.SenderName
'
End With
'~~> Close and Clean up Excel
oXLwb.Close (True)
oXLApp.Quit
Set oXLws = Nothing
Set oXLwb = Nothing
Set oXLApp = Nothing
Set olMail = Nothing
Set olNS = Nothing
End Sub
FOLLOWUP
To extract the contents from your email body, you can split it using SPLIT() and then parsing out the relevant information from it. See this example
Dim MyAr() As String
MyAr = Split(olMail.body, vbCrLf)
For i = LBound(MyAr) To UBound(MyAr)
'~~> This will give you the contents of your email
'~~> on separate lines
Debug.Print MyAr(i)
Next i
New introduction 2
In the previous version of macro "SaveEmailDetails" I used this statement to find Inbox:
Set FolderTgt = CreateObject("Outlook.Application"). _
GetNamespace("MAPI").GetDefaultFolder(olFolderInbox)
I have since installed a newer version of Outlook and I have discovered that it does not use the default Inbox. For each of my email accounts, it created a separate store (named for the email address) each with its own Inbox. None of those Inboxes is the default.
This macro, outputs the name of the store holding the default Inbox to the Immediate Window:
Sub DsplUsernameOfDefaultStore()
Dim NS As Outlook.NameSpace
Dim DefaultInboxFldr As MAPIFolder
Set NS = CreateObject("Outlook.Application").GetNamespace("MAPI")
Set DefaultInboxFldr = NS.GetDefaultFolder(olFolderInbox)
Debug.Print DefaultInboxFldr.Parent.Name
End Sub
On my installation, this outputs: "Outlook Data File".
I have added an extra statement to macro "SaveEmailDetails" that shows how to access the Inbox of any store.
New introduction 1
A number of people have picked up the macro below, found it useful and have contacted me directly for further advice. Following these contacts I have made a few improvements to the macro so I have posted the revised version below. I have also added a pair of macros which together will return the MAPIFolder object for any folder with the Outlook hierarchy. These are useful if you wish to access other than a default folder.
The original text referenced one question by date which linked to an earlier question. The first question has been deleted so the link has been lost. That link was to Update excel sheet based on outlook mail (closed)
Original text
There are a surprising number of variations of the question: "How do I extract data from Outlook emails to Excel workbooks?" For example, two questions up on [outlook-vba] the same question was asked on 13 August. That question references a variation from December that I attempted to answer.
For the December question, I went overboard with a two part answer. The first part was a series of teaching macros that explored the Outlook folder structure and wrote data to text files or Excel workbooks. The second part discussed how to design the extraction process. For this question Siddarth has provided an excellent, succinct answer and then a follow-up to help with the next stage.
What the questioner of every variation appears unable to understand is that showing us what the data looks like on the screen does not tell us what the text or html body looks like. This answer is an attempt to get past that problem.
The macro below is more complicated than Siddarth’s but a lot simpler that those I included in my December answer. There is more that could be added but I think this is enough to start with.
The macro creates a new Excel workbook and outputs selected properties of every email in Inbox to create this worksheet:
Near the top of the macro there is a comment containing eight hashes (#). The statement below that comment must be changed because it identifies the folder in which the Excel workbook will be created.
All other comments containing hashes suggest amendments to adapt the macro to your requirements.
How are the emails from which data is to be extracted identified? Is it the sender, the subject, a string within the body or all of these? The comments provide some help in eliminating uninteresting emails. If I understand the question correctly, an interesting email will have Subject = "Task Completed".
The comments provide no help in extracting data from interesting emails but the worksheet shows both the text and html versions of the email body if they are present. My idea is that you can see what the macro will see and start designing the extraction process.
This is not shown in the screen image above but the macro outputs two versions on the text body. The first version is unchanged which means tab, carriage return, line feed are obeyed and any non-break spaces look like spaces. In the second version, I have replaced these codes with the strings [TB], [CR], [LF] and [NBSP] so they are visible. If my understanding is correct, I would expect to see the following within the second text body:
Activity[TAB]Count[CR][LF]Open[TAB]35[CR][LF]HCQA[TAB]42[CR][LF]HCQC[TAB]60[CR][LF]HAbst[TAB]50 45 5 2 2 1[CR][LF] and so on
Extracting the values from the original of this string should not be difficult.
I would try amending my macro to output the extracted values in addition to the email’s properties. Only when I have successfully achieved this change would I attempt to write the extracted data to an existing workbook. I would also move processed emails to a different folder. I have shown where these changes must be made but give no further help. I will respond to a supplementary question if you get to the point where you need this information.
Good luck.
Latest version of macro included within the original text
Option Explicit
Public Sub SaveEmailDetails()
' This macro creates a new Excel workbook and writes to it details
' of every email in the Inbox.
' Lines starting with hashes either MUST be changed before running the
' macro or suggest changes you might consider appropriate.
Dim AttachCount As Long
Dim AttachDtl() As String
Dim ExcelWkBk As Excel.Workbook
Dim FileName As String
Dim FolderTgt As MAPIFolder
Dim HtmlBody As String
Dim InterestingItem As Boolean
Dim InxAttach As Long
Dim InxItemCrnt As Long
Dim PathName As String
Dim ReceivedTime As Date
Dim RowCrnt As Long
Dim SenderEmailAddress As String
Dim SenderName As String
Dim Subject As String
Dim TextBody As String
Dim xlApp As Excel.Application
' The Excel workbook will be created in this folder.
' ######## Replace "C:\DataArea\SO" with the name of a folder on your disc.
PathName = "C:\DataArea\SO"
' This creates a unique filename.
' #### If you use a version of Excel 2003, change the extension to "xls".
FileName = Format(Now(), "yymmdd hhmmss") & ".xlsx"
' Open own copy of Excel
Set xlApp = Application.CreateObject("Excel.Application")
With xlApp
' .Visible = True ' This slows your macro but helps during debugging
.ScreenUpdating = False ' Reduces flash and increases speed
' Create a new workbook
' #### If updating an existing workbook, replace with an
' #### Open workbook statement.
Set ExcelWkBk = xlApp.Workbooks.Add
With ExcelWkBk
' #### None of this code will be useful if you are adding
' #### to an existing workbook. However, it demonstrates a
' #### variety of useful statements.
.Worksheets("Sheet1").Name = "Inbox" ' Rename first worksheet
With .Worksheets("Inbox")
' Create header line
With .Cells(1, "A")
.Value = "Field"
.Font.Bold = True
End With
With .Cells(1, "B")
.Value = "Value"
.Font.Bold = True
End With
.Columns("A").ColumnWidth = 18
.Columns("B").ColumnWidth = 150
End With
End With
RowCrnt = 2
End With
' FolderTgt is the folder I am going to search. This statement says
' I want to seach the Inbox. The value "olFolderInbox" can be replaced
' to allow any of the standard folders to be searched.
' See FindSelectedFolder() for a routine that will search for any folder.
Set FolderTgt = CreateObject("Outlook.Application"). _
GetNamespace("MAPI").GetDefaultFolder(olFolderInbox)
' #### Use the following the access a non-default Inbox.
' #### Change "Xxxx" to name of one of your store you want to access.
Set FolderTgt = Session.Folders("Xxxx").Folders("Inbox")
' This examines the emails in reverse order. I will explain why later.
For InxItemCrnt = FolderTgt.Items.Count To 1 Step -1
With FolderTgt.Items.Item(InxItemCrnt)
' A folder can contain several types of item: mail items, meeting items,
' contacts, etc. I am only interested in mail items.
If .Class = olMail Then
' Save selected properties to variables
ReceivedTime = .ReceivedTime
Subject = .Subject
SenderName = .SenderName
SenderEmailAddress = .SenderEmailAddress
TextBody = .Body
HtmlBody = .HtmlBody
AttachCount = .Attachments.Count
If AttachCount > 0 Then
ReDim AttachDtl(1 To 7, 1 To AttachCount)
For InxAttach = 1 To AttachCount
' There are four types of attachment:
' * olByValue 1
' * olByReference 4
' * olEmbeddedItem 5
' * olOLE 6
Select Case .Attachments(InxAttach).Type
Case olByValue
AttachDtl(1, InxAttach) = "Val"
Case olEmbeddeditem
AttachDtl(1, InxAttach) = "Ebd"
Case olByReference
AttachDtl(1, InxAttach) = "Ref"
Case olOLE
AttachDtl(1, InxAttach) = "OLE"
Case Else
AttachDtl(1, InxAttach) = "Unk"
End Select
' Not all types have all properties. This code handles
' those missing properties of which I am aware. However,
' I have never found an attachment of type Reference or OLE.
' Additional code may be required for them.
Select Case .Attachments(InxAttach).Type
Case olEmbeddeditem
AttachDtl(2, InxAttach) = ""
Case Else
AttachDtl(2, InxAttach) = .Attachments(InxAttach).PathName
End Select
AttachDtl(3, InxAttach) = .Attachments(InxAttach).FileName
AttachDtl(4, InxAttach) = .Attachments(InxAttach).DisplayName
AttachDtl(5, InxAttach) = "--"
' I suspect Attachment had a parent property in early versions
' of Outlook. It is missing from Outlook 2016.
On Error Resume Next
AttachDtl(5, InxAttach) = .Attachments(InxAttach).Parent
On Error GoTo 0
AttachDtl(6, InxAttach) = .Attachments(InxAttach).Position
' Class 5 is attachment. I have never seen an attachment with
' a different class and do not see the purpose of this property.
' The code will stop here if a different class is found.
Debug.Assert .Attachments(InxAttach).Class = 5
AttachDtl(7, InxAttach) = .Attachments(InxAttach).Class
Next
End If
InterestingItem = True
Else
InterestingItem = False
End If
End With
' The most used properties of the email have been loaded to variables but
' there are many more properies. Press F2. Scroll down classes until
' you find MailItem. Look through the members and note the name of
' any properties that look useful. Look them up using VB Help.
' #### You need to add code here to eliminate uninteresting items.
' #### For example:
'If SenderEmailAddress <> "JohnDoe#AcmeSoftware.co.zy" Then
' InterestingItem = False
'End If
'If InStr(Subject, "Accounts payable") = 0 Then
' InterestingItem = False
'End If
'If AttachCount = 0 Then
' InterestingItem = False
'End If
' #### If the item is still thought to be interesting I
' #### suggest extracting the required data to variables here.
' #### You should consider moving processed emails to another
' #### folder. The emails are being processed in reverse order
' #### to allow this removal of an email from the Inbox without
' #### effecting the index numbers of unprocessed emails.
If InterestingItem Then
With ExcelWkBk
With .Worksheets("Inbox")
' #### This code creates a dividing row and then
' #### outputs a property per row. Again it demonstrates
' #### statements that are likely to be useful in the final
' #### version
' Create dividing row between emails
.Rows(RowCrnt).RowHeight = 5
.Range(.Cells(RowCrnt, "A"), .Cells(RowCrnt, "B")) _
.Interior.Color = RGB(0, 255, 0)
RowCrnt = RowCrnt + 1
.Cells(RowCrnt, "A").Value = "Sender name"
.Cells(RowCrnt, "B").Value = SenderName
RowCrnt = RowCrnt + 1
.Cells(RowCrnt, "A").Value = "Sender email address"
.Cells(RowCrnt, "B").Value = SenderEmailAddress
RowCrnt = RowCrnt + 1
.Cells(RowCrnt, "A").Value = "Received time"
With .Cells(RowCrnt, "B")
.NumberFormat = "#"
.Value = Format(ReceivedTime, "mmmm d, yyyy h:mm")
End With
RowCrnt = RowCrnt + 1
.Cells(RowCrnt, "A").Value = "Subject"
.Cells(RowCrnt, "B").Value = Subject
RowCrnt = RowCrnt + 1
If AttachCount > 0 Then
.Cells(RowCrnt, "A").Value = "Attachments"
.Cells(RowCrnt, "B").Value = "Inx|Type|Path name|File name|Display name|Parent|Position|Class"
RowCrnt = RowCrnt + 1
For InxAttach = 1 To AttachCount
.Cells(RowCrnt, "B").Value = InxAttach & "|" & _
AttachDtl(1, InxAttach) & "|" & _
AttachDtl(2, InxAttach) & "|" & _
AttachDtl(3, InxAttach) & "|" & _
AttachDtl(4, InxAttach) & "|" & _
AttachDtl(5, InxAttach) & "|" & _
AttachDtl(6, InxAttach) & "|" & _
AttachDtl(7, InxAttach)
RowCrnt = RowCrnt + 1
Next
End If
If TextBody <> "" Then
' ##### This code was in the original version of the macro
' ##### but I did not find it as useful as the other version of
' ##### the text body. See below
' This outputs the text body with CR, LF and TB obeyed
'With .Cells(RowCrnt, "A")
' .Value = "text body"
' .VerticalAlignment = xlTop
'End With
'With .Cells(RowCrnt, "B")
' ' The maximum size of a cell 32,767
' .Value = Mid(TextBody, 1, 32700)
' .WrapText = True
'End With
'RowCrnt = RowCrnt + 1
' This outputs the text body with NBSP, CR, LF and TB
' replaced by strings.
With .Cells(RowCrnt, "A")
.Value = "text body"
.VerticalAlignment = xlTop
End With
TextBody = Replace(TextBody, Chr(160), "[NBSP]")
TextBody = Replace(TextBody, vbCr, "[CR]")
TextBody = Replace(TextBody, vbLf, "[LF]")
TextBody = Replace(TextBody, vbTab, "[TB]")
With .Cells(RowCrnt, "B")
' The maximum size of a cell 32,767
.Value = Mid(TextBody, 1, 32700)
.WrapText = True
End With
RowCrnt = RowCrnt + 1
End If
If HtmlBody <> "" Then
' ##### This code was in the original version of the macro
' ##### but I did not find it as useful as the other version of
' ##### the html body. See below
' This outputs the html body with CR, LF and TB obeyed
'With .Cells(RowCrnt, "A")
' .Value = "Html body"
' .VerticalAlignment = xlTop
'End With
'With .Cells(RowCrnt, "B")
' .Value = Mid(HtmlBody, 1, 32700)
' .WrapText = True
'End With
'RowCrnt = RowCrnt + 1
' This outputs the html body with NBSP, CR, LF and TB
' replaced by strings.
With .Cells(RowCrnt, "A")
.Value = "Html body"
.VerticalAlignment = xlTop
End With
HtmlBody = Replace(HtmlBody, Chr(160), "[NBSP]")
HtmlBody = Replace(HtmlBody, vbCr, "[CR]")
HtmlBody = Replace(HtmlBody, vbLf, "[LF]")
HtmlBody = Replace(HtmlBody, vbTab, "[TB]")
With .Cells(RowCrnt, "B")
.Value = Mid(HtmlBody, 1, 32700)
.WrapText = True
End With
RowCrnt = RowCrnt + 1
End If
End With
End With
End If
Next
With xlApp
With ExcelWkBk
' Write new workbook to disc
If Right(PathName, 1) <> "\" Then
PathName = PathName & "\"
End If
.SaveAs FileName:=PathName & FileName
.Close
End With
.Quit ' Close our copy of Excel
End With
Set xlApp = Nothing ' Clear reference to Excel
End Sub
Macros not included in original post but which some users of above macro have found useful.
Public Sub FindSelectedFolder(ByRef FolderTgt As MAPIFolder, _
ByVal NameTgt As String, ByVal NameSep As String)
' This routine (and its sub-routine) locate a folder within the hierarchy and
' returns it as an object of type MAPIFolder
' NameTgt The name of the required folder in the format:
' FolderName1 NameSep FolderName2 [ NameSep FolderName3 ] ...
' If NameSep is "|", an example value is "Personal Folders|Inbox"
' FolderName1 must be an outer folder name such as
' "Personal Folders". The outer folder names are typically the names
' of PST files. FolderName2 must be the name of a folder within
' Folder1; in the example "Inbox". FolderName2 is compulsory. This
' routine cannot return a PST file; only a folder within a PST file.
' FolderName3, FolderName4 and so on are optional and allow a folder
' at any depth with the hierarchy to be specified.
' NameSep A character or string used to separate the folder names within
' NameTgt.
' FolderTgt On exit, the required folder. Set to Nothing if not found.
' This routine initialises the search and finds the top level folder.
' FindSelectedSubFolder() is used to find the target folder within the
' top level folder.
Dim InxFolderCrnt As Long
Dim NameChild As String
Dim NameCrnt As String
Dim Pos As Long
Dim TopLvlFolderList As Folders
Set FolderTgt = Nothing ' Target folder not found
Set TopLvlFolderList = _
CreateObject("Outlook.Application").GetNamespace("MAPI").Folders
' Split NameTgt into the name of folder at current level
' and the name of its children
Pos = InStr(NameTgt, NameSep)
If Pos = 0 Then
' I need at least a level 2 name
Exit Sub
End If
NameCrnt = Mid(NameTgt, 1, Pos - 1)
NameChild = Mid(NameTgt, Pos + 1)
' Look for current name. Drop through and return nothing if name not found.
For InxFolderCrnt = 1 To TopLvlFolderList.Count
If NameCrnt = TopLvlFolderList(InxFolderCrnt).Name Then
' Have found current name. Call FindSelectedSubFolder() to
' look for its children
Call FindSelectedSubFolder(TopLvlFolderList.Item(InxFolderCrnt), _
FolderTgt, NameChild, NameSep)
Exit For
End If
Next
End Sub
Public Sub FindSelectedSubFolder(FolderCrnt As MAPIFolder, _
ByRef FolderTgt As MAPIFolder, _
ByVal NameTgt As String, ByVal NameSep As String)
' See FindSelectedFolder() for an introduction to the purpose of this routine.
' This routine finds all folders below the top level
' FolderCrnt The folder to be seached for the target folder.
' NameTgt The NameTgt passed to FindSelectedFolder will be of the form:
' A|B|C|D|E
' A is the name of outer folder which represents a PST file.
' FindSelectedFolder() removes "A|" from NameTgt and calls this
' routine with FolderCrnt set to folder A to search for B.
' When this routine finds B, it calls itself with FolderCrnt set to
' folder B to search for C. Calls are nested to whatever depth are
' necessary.
' NameSep As for FindSelectedSubFolder
' FolderTgt As for FindSelectedSubFolder
Dim InxFolderCrnt As Long
Dim NameChild As String
Dim NameCrnt As String
Dim Pos As Long
' Split NameTgt into the name of folder at current level
' and the name of its children
Pos = InStr(NameTgt, NameSep)
If Pos = 0 Then
NameCrnt = NameTgt
NameChild = ""
Else
NameCrnt = Mid(NameTgt, 1, Pos - 1)
NameChild = Mid(NameTgt, Pos + 1)
End If
' Look for current name. Drop through and return nothing if name not found.
For InxFolderCrnt = 1 To FolderCrnt.Folders.Count
If NameCrnt = FolderCrnt.Folders(InxFolderCrnt).Name Then
' Have found current name.
If NameChild = "" Then
' Have found target folder
Set FolderTgt = FolderCrnt.Folders(InxFolderCrnt)
Else
'Recurse to look for children
Call FindSelectedSubFolder(FolderCrnt.Folders(InxFolderCrnt), _
FolderTgt, NameChild, NameSep)
End If
Exit For
End If
Next
' If NameCrnt not found, FolderTgt will be returned unchanged. Since it is
' initialised to Nothing at the beginning, that will be the returned value.
End Sub

Macro in PowerPoint which links to data stored in an Excel Spreadsheet

I have an Excel Spreadsheet (let's say objectdata.xls) which is used to set the widths/lengths of different rectangles. The spreadsheet therefore has 3 columns:
Object Name
Object Width
Object Length
There are approx 100 rectangles defined in the Spreadsheet
What i am try to do is run a macro in a PowerPoint (PP) which will read the data from the Spreadsheet (ideally this info should be stored external to the PP file but if need be it could be a linked or embedded file within PP) and then update the size of the rectangle shapes that I have included in the PP file.
E.g. on slide one, the macro reads row 1 in the spreadhseet and sees that the object width is 5 and length is 10, and so updates the size of the rectangle shape in the PP.
Can anyone tell me if this can be done?
Thanks.
Use GetExcelData to do the work; it calls GetExcel
Function GetExcel() As Object
'---------------------------------------------------------------------------------------
' Procedure : GetExcel
' Author : Naresh Nichani / Steve Rindsberg
' Purpose :
' Check if an instance of Excel is running. If so obtain a reference to the running Excel application
' Otherwise Create a new instance of Excel and assign the XL application reference to oXLApp object
' SR : Modified 2010-02-23 to ALWAYS create a new instance rather than using an existing one, so when we
' : close the one we open, we don't wack the user's other instances of Excel if any
' Params : None
' Returns : An Excel Application object on success, Nothing on failure
'---------------------------------------------------------------------------------------
On Error GoTo GetExcel_ErrorHandler
On Error Resume Next
Err.Number = 0
Dim oXLAPP As Object
' Comment out the following bits to force a new instance of Excel
' and leave any existing instances alone
' Set oXLApp = GetObject(, "Excel.Application")
' If Err.Number <> 0 Then
' Err.Number = 0
Set oXLAPP = CreateObject("Excel.Application")
If Err.Number <> 0 Then
'MsgBox "Unable to start Excel.", vbInformation, "Start Excel"
Exit Function
End If
' End If
On Error GoTo GetExcel_ErrorHandler
If Not oXLAPP Is Nothing Then
Set GetExcel = oXLAPP
Else
[MASTTBAR].rnrErrLog "modExcel:GetExcel - unable to invoke Excel instance"
End If
Set oXLAPP = Nothing
Exit Function
NormalExit:
On Error GoTo 0
Exit Function
GetExcel_ErrorHandler:
Resume NormalExit
End Function
Function GetExcelData(sFilename As String, _
Optional lWorksheetIndex As Long = 1, _
Optional sWorksheetName As String = "") As Variant
'---------------------------------------------------------------------------------------
' Purpose : Gets the "active" data from the file/worksheet specified
Dim oXLAPP As Object
Dim oxlWB As Object
Dim oxlRange As Object
Dim x As Long
Dim y As Long
Dim sMsg As String
Dim lVisibleRowCount As Long
Dim lVisibleColCount As Long
Dim aData() As String
On Error GoTo GetExcelData_ErrorHandler
Set oXLAPP = GetExcel()
If oXLAPP Is Nothing Then
Exit Function
End If
' open the workbook read-only
Set oxlWB = oXLAPP.Workbooks.Open(sFilename, , True)
If oxlWB Is Nothing Then
Exit Function
End If
If Len(sWorksheetName) > 0 Then
Set oxlRange = GetUsedRange(oxlWB.Worksheets(sWorksheetName))
Else
Set oxlRange = GetUsedRange(oxlWB.Worksheets(lWorksheetIndex))
End If
If oxlRange Is Nothing Then
Exit Function
End If
' Get a count of visible rows/columns (ignore hidden rows/cols)
For x = 1 To oxlRange.Rows.Count
If Not oxlRange.Rows(x).Hidden Then
lVisibleRowCount = lVisibleRowCount + 1
End If
Next ' row
For y = 1 To oxlRange.Columns.Count
If Not oxlRange.Columns(y).Hidden Then
lVisibleColCount = lVisibleColCount + 1
End If
Next
ReDim aData(1 To lVisibleRowCount, 1 To lVisibleColCount)
lVisibleRowCount = 0
For x = 1 To oxlRange.Rows.Count
If Not oxlRange.Rows(x).Hidden Then
lVisibleRowCount = lVisibleRowCount + 1
lVisibleColCount = 0
For y = 1 To oxlRange.Columns.Count
If Not oxlRange.Columns(y).Hidden Then
lVisibleColCount = lVisibleColCount + 1
aData(lVisibleRowCount, lVisibleColCount) = oxlRange.Cells(x, y).Text
End If
Next
End If
Next
' return data in array
GetExcelData = aData
NormalExit:
On Error GoTo 0
' Close the workbook
If Not oxlWB Is Nothing Then
oXLAPP.DisplayAlerts = False
oxlWB.Close
oXLAPP.DisplayAlerts = True
End If
'To Close XL application
If Not oXLAPP Is Nothing Then
oXLAPP.Quit
End If
'Set the XL Application and XL Workbook objects to Nothing
Set oxlRange = Nothing
Set oxlWB = Nothing
Set oXLAPP = Nothing
Exit Function
GetExcelData_ErrorHandler:
Resume NormalExit
End Function
Blockquote
Blockquoteenter code here
Yes, this can certainly be done. It takes a bit more code than I have at the tip of my fingers and you'd need to adapt whatever I posted. But have a look here for examples you can start with. These point to the PowerPoint FAQ site that I maintain. No charge for anything.
Controlling Office Applications from PowerPoint (by Naresh Nichani and Brian Reilly)
http://www.pptfaq.com/FAQ00795.htm
Automate Excel from PowerPoint. Automate PowerPoint from Excel. And so on.
http://www.pptfaq.com/FAQ00368.htm
I'd probably do this by opening the excel file, reading the contents into an array, then using the data from the array to do the actual work in PPT.
If you need help with the PPT part, let us know. It'd mostly be a matter of writing a function like [aircode]:
Sub SetRectangleSize ( sRectangleName as string, sngWidth as Single, sngHeight as Single)
Dim oShp as Shape
Set oShp = GetShapeNamed(sRectangleName, lSlideIndex)
If Not oShp is Nothing Then
With oShp
.Width = sngWidth
.Height = sngHeight
End With
End If
End Sub
And
Function GetShapeNamed(sName as String, lSlideIndex as Long) as Shape
On Error Resume Next
Set GetShapeNamed = ActivePresentation.Slides(lSlideIndex).Shapes(sName)
If Err.Number <> 0 Then
' no shape by that name on the slide; return null
Set GetShapeNamed = Nothing
End If
End Function
Incidentally, I would consider using tags to identify the rectangles rather than shape names (which tend to be less reliable).

Resources