I have been using the below code for years to generate communications. I am adapting it to new requirements.
The code grabs data from Sharepoint/MS Teams to filter then copy into a new tab. The code calls an email template that contains placeholders in multiple tables which includes a banner. By using strings it replaces the value of cells with the placeholder.
The data has bullet points and is in paragraphs. However when the email is generated, it has the data in one block as one continuous line.
I tried inserting line breaks but without success.
My latest iteration is to use a replace function, after the loop through the string arrays.
.HTMLBody = replace(.htmlBody, ";", "<BR>")
I put ";" at the end of a line when I want to go the next line.
However, whilst the <BR> does add the line break, it changes the font to Times New Roman and puts in a lot HTML garbage when the email is generated. I suspect is it is from the "<BR>".
The odd thing is when I add a debug.print onto .htmlbody it shows the font and line breaks are correct, with or without the second replace function.
I also tried to change "<BR>" with CHR(10) and vbnewline and other permutations.
I have not included the code that declares the outlook objects and the location of the email template as that works.
Sub ImportSPData() 'Source the Sharepoint data
Dim objMyList As ListObject
Dim objWksheet As Worksheet
Const strSPServer As String = "https://xxxx.xxxxx.xxx.com/teams/xxxx/_vti_bin" 'Sharepoint Url
Const LISTNAME As String = "{1574AC55-E21A-41D2-9EEC-891CFEC69BF6}" 'Sharepoint list code - where the data is inputted
Const VIEWNAME As String = "{34D4B58A-D4C6-4190-9248-896D062543C6}" 'Sharepoint View code - The specific view of the list
Set objWksheet = Worksheets("ImportData") 'Where the data is exported to
objWksheet.Select
If ActiveSheet.AutoFilterMode Then
ActiveSheet.Range("A1").AutoFilter
End If
objWksheet.Cells.Select
Selection.ClearContents
Range("A1").Select
Set objMyList = objWksheet.ListObjects.Add(xlSrcExternal, Array(strSPServer, LISTNAME, VIEWNAME), False, , Range("A1")) 'where the above export lands
Range("A1").Select
ActiveSheet.ListObjects(1).Unlist
If Not ActiveSheet.AutoFilterMode Then
ActiveSheet.Range("A1").AutoFilter
End If
Call applyAutoFilter 'sets up the stage. However when the "Import Data" tab is viewed, the line breaks and bullet points are missing.
End Sub
Sub Replace()
'Populate replacement strings from sharepoint. .Range("xx") corresponds to the column containing new text.
Dim repNumberText As String: repNumberText = dataSheet.Range("f2").Value
Dim repTitleText As String: repTitleText = dataSheet.Range("I2").Value
Dim repSummaryText As String: repSummaryText = dataSheet.Range("B2").Value
Dim repImpactText As String: repImpactText = dataSheet.Range("C2").Value
Dim repUnderwayText As String: repUnderwayText = dataSheet.Range("D2").Value
Dim repCompletedText As String: repCompletedText = dataSheet.Range("E2").Value
Dim repUpdateText As String: repUpdateText = dataSheet.Range("G2").Value
repSummaryText = "<p>" & repSummaryText & "</p>"
repCompletedText = "<p>" & repCompletedText & "</p>"
Dim replaceStrings() As Variant
Dim replaceWithStrings() As Variant
'Replacement Array, replaceStrings are the text placeholders in the email templates, replacewithstrings are the variables assigned above.
replaceStrings = Array("NumberText", "TitleText", "SummaryText", "ImpactText", "UnderwayText", "CompletedText", "UpdateText")
replaceWithStrings = Array(repNumberText, repTitleText, repSummaryText, repImpactText, repUnderwayText, repCompletedText, repUpdateText)
Dim currentItem As String
Dim currentReplaceItem As String
Dim i As Integer
i = UBound(replaceStrings)
Dim j As Integer
j = 0
With msgFile
Today = Format(Now(), "DDDD DD MMM yyyy")
'Dim HtmlBody As String
'Loop through arrays and replace text
Do Until j = i + 1
.HtmlBody = Replace(.HtmlBody, replaceStrings(j), replaceWithStrings(j))
j = j + 1
Loop
'Replace subject texts.\
' .Subject = "Communications"
' .Subject = Today
.Subject = Replace(.Subject, "NumberText", repNumberText)
.Subject = Replace(.Subject, "TitleText", repTitleText) & " " & "-" & " " & Today
.Display
It is not clear when and where all these customizations are made. Also it is not clear where the original item comes form. Anyway, keep in mind that you need to prepare a well-formed HTML document to be able to set up the HTMLBody property correctly. Even if Outlook handles cases where only some tags are provided, it is better to deal with a fully-formed HTML document.
The Outlook object model supports three main ways of customizing the message body:
The Body property returns or sets a string representing the clear-text body of the Outlook item.
The HTMLBody property of the MailItem class returns or sets a string representing the HTML body of the specified item. Setting the HTMLBody property will always update the Body property immediately. For example:
Sub CreateHTMLMail()
'Creates a new e-mail item and modifies its properties.
Dim objMail As Outlook.MailItem
'Create e-mail item
Set objMail = Application.CreateItem(olMailItem)
With objMail
'Set body format to HTML
.BodyFormat = olFormatHTML
.HTMLBody = "<HTML><BODY>Enter the message text here. </BODY></HTML>"
.Display
End With
End Sub
The Word object model can be used for dealing with message bodies. See Chapter 17: Working with Item Bodies for more information.
Note, the MailItem.BodyFormat property allows you to programmatically change the editor that is used for the body of an item.
Related
I need to pass an array as a property of a custom class, but cannot find a way to do it, and didn't find a question already answered that worked for me. So here I am.
My 'movie' class has 4 properties as you can see below: titre, annee, duree and genres. The first 3 are working well, but I'am blocked on the last one. From a string, separated by commas, I need to create an array of the different genres of the movie (ie: adventure, action, etc). I can easily create this array, but cannot find a way to assign it as a movie.property.
The code below returns an incompatibility error. I probably have to implement a let property but don't understand how to do it... Does anyone has a clue of what I am doing wrong ? Tell me if you need a bigger sample of the code.
Public Sub initialiser(ByVal strLigne As String)
Dim arrData
arrData = Split(strLigne, vbTab)
strtitre = arrData(0)
strAnnee = arrData(1)
intDuree = ConvertToInt(arrData(2))
genres = CreerTabGenre(arrData(3))
End Sub
Property Get genre() As eGenre ' eGenre is an enum, and I have no choice about that (homework...)
genre = genres() 'Here I tried all the combinations between parenthesis on both, on none, etc
End Property
edit: Maybe the problem is how I access the property after ? tabFilms is an array of all the different movies. As a test, I try to access the first genre of the array, but I'm met with 'incorrect affectation of property' error...
Function genrePopulaire(tabFilms() As film) As String
Dim i As Integer, j As Integer
For i = 0 To UBound(tabFilms)
MsgBox tabFilms(i).genre(0)
Next i
End Function
First of all, your split array uses vbTab to split the string. And you said that they are separeted by commas. If so, you should add a comma at the end of the string. It would look like:
Lord of the Rings; 2002; 200; SciFi;
and use the split array like this:
arrData = split(strLigne, ";")
Also, I've tried this code working with class module.
Try this, on the module procedure:
Sub Test()
Dim Movie As Object
Set Movie = New clsMovie
Dim arrData
'strligne example
Dim strligne As String
strligne = "TLOTR" & vbTab & "2002" & vbTab & 200 & vbTab & "SciFi"
arrData = Split(strligne, vbTab)
With Movie
.TestMovie arrData
'OUTPUT EXAMPLE:
Debug.Print "Movie's name is " & .Titre & ". La duree est " & .Duree & " minutes and it was realised on " & .Annee & " and its genre is " & .Genre
End With
'you can change value at any time
movie.titre = "Harry Potter"
debug.print movie.titre & " " & movie.duree
End Sub
On the class Module (IMPORTANT: called clsMovie):
Option Explicit
'Declare all variables as private. If you want, instead of "Private" you can do it "Public" but it can be quite dangerous on a huge project. As you are declaring them Private, it's necessary to work with Let / Get statements below.
Private pTitre As String, pAnnee As String, pDuree As String, pGenres As String
Function TestMovie(arr)
Titre = arr(0)
Annee = arr(1)
Duree = arr(2)
Genres = arr(3)
End Function
'From here to the end are the "Let / Get" properties as we work with private variables in the class
Property Get Titre() As String
Titre = pTitre
End Property
Private Property Let Titre(value As String)
pTitre = value
End Property
Property Get Annee() As String
Annee = pAnnee
End Property
Property Let Annee(value As String)
pAnnee = value
End Property
Property Get Duree() As String
Duree = pDuree
End Property
Property Let Duree(value As String)
pDuree = value
End Property
Property Get Genres() As String
Genres = pGenres
End Property
Property Let Genres(value As String)
pGenres = value
End Property
I'm trying to create a single PDF file containing a sheet for each tab which I have listed from cell J2 in my Control sheet but I keep getting a Subscript Out Of Range error.
When I record the action I see that it creates an array of sheet names which it then selects to export, so I have a For loop which goes through the list and creates an array which adds to itself until it reaches the end of the list - the aim being to create one long string which I then select as an array.
All appears to be good (the variable PDFArray displays a string of the tab names in what appears to be the correct format) but when I get to the line 'Worksheets(Array(PDFarray)).Select' then I get the error. I've made sure the sheet names contain no undesirable characters or spaces but still no joy. Any help would be very much appreciated. Thank you
Sub B_PDFs()
Dim PDFarray As String, PDFName as String, sht As String
Sheets("Control").Select
PLFile = ActiveWorkbook.Name
PDFLoc = Application.ActiveWorkbook.Path & "\"
PDFName = Range("A20")
PDFSheetCount = Range("J1").Offset(Rows.Count - 1, 0).End(xlUp).Row
'Loop through column J and create a string with each tab name to be exported
For x = 2 To PDFSheetCount Step 1
If x = PDFSheetCount Then
sht = """ " & "" & Cells(x, 10) & """ "
Else
sht = """" & "" & Cells(x, 10) & """" & ", "
End If
PDFarray = PDFarray & sht
Next x
'Create PDF from the array above
Worksheets(Array(PDFarray)).Select - this is where I get the error Subscript Out Of Range
Selection.ExportAsFixedFormat Type:=xlTypePDF, Filename:=PFDLoc & PDFName, Quality:= _
xlQualityStandard, IncludeDocProperties:=True, IgnorePrintAreas:=False,
OpenAfterPublish:=False
Workbooks(PLFile).Activate
End Sub
I don't understand why MS makes NOT requiring variable declaration the default. Select Tools/Options/Editor and check Require Variable Declaration. This will place Option Explicit at the start of any new module. To correct this module, enter it manually at the beginning.
Doing so would have enabled you to find and correct a typo in your code.
You should also be avoiding Select, Selection and Activate. They rarely serve any purpose at all, and can cause multiple problems because they lull into avoiding explicit declarations of which workbook, worksheet, etc. you need. See How to avoid using Select in Excel VBA
However in using the ExportAsFixedFormat method to export selected worksheets, it seems Selection and ActiveSheet are required for it to work.
Array(str_variable) returns an array with a single entry that contains the entire string variable. It does not interpret the string variable so as to split it into separate elements.
So, rewriting your code somewhat (I will leave it to you to clean up the PDF document):
Option Explicit
Sub B_PDFs()
Dim PDFarray As Variant, PDFName As String, PLFile As String, PDFLoc As String
Dim wsControl As Worksheet
Dim WB As Workbook
'Consider wheter you want to use ThisWorkbook or a specific workbook
Set WB = ThisWorkbook
With WB
Set wsControl = .Worksheets("Control")
PLFile = .Name
PDFLoc = .Path & "\"
End With
With wsControl
PDFName = .Range("A20")
'create PDFarray
'This will be a 1-based 2D array starting at J1
'If you need to start at J2, alter the initial cell
PDFarray = .Range(.Cells(1, 10), .Cells(.Rows.Count, 10).End(xlUp))
End With
'convert to a 1D array
PDFarray = WorksheetFunction.Transpose(PDFarray)
'Note the use of `Select` and `ActiveSheet` when using this `ExportAsFixedFormat` method
Worksheets(PDFarray).Select
'Create PDF from the array above
ActiveSheet.ExportAsFixedFormat Type:=xlTypePDF, Filename:=PDFLoc & PDFName, Quality:= _
xlQualityStandard, IncludeDocProperties:=True, IgnorePrintAreas:=False, OpenAfterPublish:=False
End Sub
What #RonRosenfeld has suggested is correct about select and selection. The expression you are building is string whereas, Excel expects it to be real array.
So in principle an approach like below shall work for you which will create an array for processing and can be used as you want to utilise.
Dim shtNames As Variant
Dim pdfArray
shtNames = Range("J2:J" & Range("J1").Offset(Rows.Count - 1, 0).End(xlUp).Row).Value
pdfArray = Application.Transpose(shtNames)
I am trying to read a table of contents in a word document without reading the page numbers. I want to read the table and save it into an array. Coding is not my strong suit so apologies in advance if this question is directed to the wrong area. I have the following piece of code which I found inline, which reads the table and the page numbers but I can't work out how to just read the table.
Dim sourceDocument As Document
Set sourceDocument = ActiveDocument
Dim myField As Field
For Each myField In sourceDocument.TablesOfContents(1).Range.Fields
Debug.Print myField.result.Text ', Chr(13), "-") & " " & " Type: " & myField.Type
DoEvents
Next
I would appreciate any help with this.
Thanks,
Robbie
This should get you on your way:
Public Function GetTOCItems(Optional ByVal fromDocument As Document = Nothing) As Variant
If fromDocument Is Nothing Then _
Set fromDocument = ActiveDocument
Dim toc As TableOfContents
Set toc = fromDocument.TablesOfContents(1)
toc.IncludePageNumbers = False
Dim tocText As Variant
tocText = toc.Range.Text
toc.IncludePageNumbers = True
GetTOCItems = Split(tocText, Chr(13))
End Function
And you can call it like this:
Dim tocItems as Variant
tocItems = GetTOCItems
Hello again community,
After I got so much help from you with my last Problem, that promted me to rework the entire code in a more efficient manner, I would like to ask two more questions regarding the same Project.
(1) I would like to implement a live-filter in my listbox CGList1, which is connected to the textbox SearchCGList1. Whenever someone types in the textbox, the results in the listbox should be adjusted. I found this Article on your website, as well as this Article 3 on an external Webpage. However, due to my very limited skills, I have not been able to adapt it properly. More later.
(2) After multiple items from the same listbox CGList1 have been transferred to the second listbox CGList2 via a button (which works like a treat), I would like to save them in the same cell (Range "BM") on my Worksheet Meta DB. For this problem I also used Google extensively and tried to adapt the findings (see links below) for my code - without success.
I hope that the Patient ones amongst you can help me out once again, in the knowledge that I am trying to learn as much as possible. My Problem is that for a lot of things, I simply do not know what to look for.
My preliminary code for Problem 1:
CGList1 and CGList2 have no code. They are populated in the Userform_Initialize sub via:
'Fill Material Groups Listbox1 dynamically
Dim cell As Range
Dim rng As Range
With ThisWorkbook.Sheets("Commodity Groups")
'Range to 500 in order to allow for further additions
Set rng = .Range("A2", .Range("A500").End(xlUp))
End With
Me.CGList1.ColumnWidths = "20;80"
For Each cell In rng.Cells
'Filter out blanks
If cell <> "" Then
With Me.CGList1
.AddItem cell.value
.List(.ListCount - 1, 1) = cell.Offset(0, 1).value
End With
End If
Next cell
I cannot just use .AddItem and then filter through the columns like you find in many examples online because it needs to be dynamic and there are many blanks in between the selection items on the Worksheet.
The buttons:
Private Sub addCGbutton_Click()
For i = 0 To CGList1.ListCount - 1
If CGList1.Selected(i) = True Then
'Copy only CG Name, not respective number/letter combination (only more work to cut out when working with it later)
CGList2.AddItem CGList1.List(i, 1)
End If
Next i
End Sub
'Delete selected Commodity Groups from List 2 for re-selection
Private Sub delCGbutton_Click()
Dim counter As Integer
counter = 0
For i = 0 To CGList2.ListCount - 1
If CGList2.Selected(i - counter) Then
CGList2.RemoveItem (i - counter)
counter = counter + 1
End If
Next i
End Sub
After a lot of trial and failure trying to adapt the linked approaches from other people, I tried something more simple:
Private Sub SearchCGList1_Change()
'Only show with textbox matching items in CGList1 (filter)
Dim strSQL As String
strSQL = "SELECT fieldname FROM table WHERE fieldname = "
strSQL = strSQL & "'" & Me!SearchCGList1 & "*'"
strSQL = strSQL & " ORDER BY fieldname;"
Me!SearchCGList1.RowSource = strSQL
End Sub
But without success.
Regarding Problem 2:
To save the multiple selections from CGList2 in Range BM on Worksheet "Meta DB", I toyed around a lot and my last try was:
Save multiple selections from Commodity Group List 2 to the same cell in Excel
Dim listItems As String, c As Long
With CGList2
For c = 0 To .ListCount - 1
If .Selected(c) Then listItems = listItems & .List(c) & ", "
Next c
End With
Range("BM") = Left(listItems, Len(listItems) - 2)
Usually, all my other UserForm entries are saved with a single command button in the following fasion:
Private Sub CommandButton21_Click()
'Application.ScreenUpdating = False
'Define all relevant WBs we will be working with
Dim wbInput As Workbook
Dim wb As Workbook
Set wb = ActiveWorkbook
Dim ws As Worksheet
Set ws = wb.Worksheets("Meta DB")
Dim LastRow As Long
'Save Userform Inputs
With ws
.Activate
LastRow = ws.Range("A" & Rows.Count).End(xlUp).row + 1
.
.
Range("BK" & LastRow).value = Me.payinfo90
Range("BL" & LastRow).value = Me.payinfo90more
'Risk Management - Residual Information
Range("BM" & LastRow).value = Me.CGList2
Range("BN" & LastRow).value = Me.suppsince
.
.
End With
End Sub
Again, I thank everyone who took the time to read my post and answer with tips on what to improve.
Everyone have a great day.
Using a helper column with array formula.
So if say you had your data for the 1st list box in a1:a10 and the selection from this listbox is placed in D1, the 2nd complete listbox selections are in B1:B10, but not used, then in E1:E10, I have the following array formula filled down, so you would populate the 2nd listbox off the helper column E.
Beginning with
=INDEX($B$1:$B$10,SMALL(IF(LEFT($B$1:$B$10,LEN($D$1))=$D$1,ROW($B$1:$B$10),""),ROWS($E$1:$E1)),1)
Containing
=INDEX($B$1:$B$10,SMALL(IF(NOT(ISERR(SEARCH($D$1,$B$1:$B$10))),ROW($B$1:$B$10)),ROWS($E$1:E1)),1)
You need to press CTRL SHIFT and ENTER for array formula.
The below codes are from Outlook 2010 developer reference, explaining the use of " Exception.AppointmentItem Property"
However while pasting and runing it, it pops up "array index out of bounds (run time error -2147352567(80020009) , and debug points to
Set myException = myRecurrPatt.Exceptions.item(1)
'Get the recurrence pattern for the master
'AppointmentItem. Access the collection of
'exceptions to the regular appointments.
Set myRecurrPatt = myApptItem.GetRecurrencePattern
Set myException = myRecurrPatt.Exceptions.item(1)
'Display the original date, time, and subject
'for this exception.
MsgBox myException.OriginalDate & ": " & saveSubject
The problem persists after I changed the index to 0. Please help, thank you!
Below is the full oringinal code:
Option Explicit
Public Sub cmdExample()
Dim myApptItem As Outlook.AppointmentItem
Dim myRecurrPatt As Outlook.RecurrencePattern
Dim myNameSpace As Outlook.NameSpace
Dim myFolder As Outlook.Folder
Dim myItems As Outlook.Items
Dim myDate As Date
Dim myOddApptItem As Outlook.AppointmentItem
Dim saveSubject As String
Dim newDate As Date
Dim myException As Outlook.Exception
Set myApptItem = Application.CreateItem(olAppointmentItem)
myApptItem.Start = #2/2/2003 3:00:00 PM#
myApptItem.End = #2/2/2003 4:00:00 PM#
myApptItem.Subject = "Meet with Boss"
'Get the recurrence pattern for this appointment
'and set it so that this is a daily appointment
'that begins on 2/2/03 and ends on 2/2/04
'and save it.
Set myRecurrPatt = myApptItem.GetRecurrencePattern
myRecurrPatt.RecurrenceType = olRecursDaily
myRecurrPatt.PatternStartDate = #2/2/2003#
myRecurrPatt.PatternEndDate = #2/2/2004#
myApptItem.Save
'Access the items in the Calendar folder to locate
'the master AppointmentItem for the new series.
Set myNameSpace = Application.GetNamespace("MAPI")
Set myFolder = myNameSpace.GetDefaultFolder(olFolderCalendar)
Set myItems = myFolder.Items
Set myApptItem = myItems("Meet with Boss")
'Get the recurrence pattern for this appointment
'and obtain the occurrence for 3/12/03.
myDate = #3/12/2003 3:00:00 PM#
Set myRecurrPatt = myApptItem.GetRecurrencePattern
Set myOddApptItem = myRecurrPatt.GetOccurrence(myDate)
'Save the existing subject. Change the subject and
'starting time for this particular appointment
'and save it.
saveSubject = myOddApptItem.Subject
myOddApptItem.Subject = "Meet NEW Boss"
newDate = #3/12/2003 3:30:00 PM#
myOddApptItem.Start = newDate
myOddApptItem.Save
'Get the recurrence pattern for the master
'AppointmentItem. Access the collection of
'exceptions to the regular appointments.
Set myRecurrPatt = myApptItem.GetRecurrencePattern
Set myException = myRecurrPatt.Exceptions.Item(1)
'Display the original date, time, and subject
'for this exception.
MsgBox myException.OriginalDate & ": " & saveSubject
'Display the current date, time, and subject
'for this exception.
MsgBox myException.AppointmentItem.Start & ": " & _
myException.AppointmentItem.Subject
End Sub
Simply, there are zero elements in the array. The online tutorial probably had an exception on that example recurring patterned appointment. You on the other hand do not on your Outlook calendar.
Consider wrapping the Exceptions.Item() in a loop. This way if the array is empty nothing will be processed:
Dim ItemIndex As Variant
...
For Each ItemIndex in myRecurrPatt.Exceptions.Items
Set myException = myRecurrPatt.Exceptions.Item(ItemIndex)
'Display the original date, time, and subject
'for this exception.
MsgBox myException.OriginalDate & ": " & saveSubject
'Display the current date, time, and subject
'for this exception.
MsgBox myException.AppointmentItem.Start & ": " & _
myException.AppointmentItem.Subject
Next ItemIndex
The Exceptions class provides the Count property which you may check out before trying to access any item in the collection.
I'd suggest breaking the chain of property and method calls and declaring each property or method on a single line of code. Thus, you will be able to find what property or method call causes the issue.
Finally, you may find the Getting Started with VBA in Outlook 2010 article in MSDN helpful.