Building a Word report using vba that will include a chart. Problem is finding out how to insert chart at specific point int report. No matter what I try the chart ends up on page 1. I need it to e.g. be placed in page 2. See below for code. Works perfectly apart from chart placement
Public Function gbAuditReportGraphs(ByVal lAuditID As Long) As Boolean
'
' NRE 07-Oct-2017
'
' Purpose : Prototype graphs in Audit
' See also
' Mantis 2250
' https://msdn.microsoft.com/en-us/library/office/ff629397(v=office.14).aspx
' Note : This version outputs to a word document
' Mods
Dim objChart As chart
Dim chartWorkSheet As Excel.Worksheet
Dim rs As New ADODB.Recordset
Dim ssql As String
Dim chSeries As Series
Dim rng As Range
Dim i As Integer
Dim clsAudit_ As New clsAudit
Dim clsRig_ As New clsRig
Dim bOk As Boolean
Dim vRigName As Variant
On Error GoTo eh
' Initialise function as false
gbAuditReportGraphs = False
clsAudit_.AuditID = lAuditID
bOk = clsAudit_.mbLoad
clsRig_.RigID = clsAudit_.RigID
bOk = clsRig_.mbLoad
vRigName = clsRig_.RigName
ssql = " SELECT cl.checklistdesc" _
& " , COUNT(*) AS nccount " _
& " FROM tbltask t " _
& " , tblchecklist cl" _
& " WHERE cl.auditid=t.auditid" _
& " AND cl.checklistid = t.checklistid" _
& " AND cl.auditid = " & lAuditID _
& " AND t.tasktype = '" & gsO & "'" _
& " AND t.taskstatus>0" _
& " GROUP BY cl.checklistdesc" _
& " ORDER BY 1"
Debug.Print "modADCForms.gbAuditReportGraphs, ssql = " & ssql
' Declare the Word Application and Document
Set mobjWordApp = New Word.Application
Set mobjWordDoc = mobjWordApp.Documents.Add
mobjWordDoc.SetCompatibilityMode wdWord2010
' Add page numbers
With mobjWordDoc.Sections(1)
.Footers(wdHeaderFooterPrimary).PageNumbers.Add PageNumberAlignment:=wdAlignPageNumberRight, FirstPage:=True
'Add Date
.Footers(wdHeaderFooterPrimary).Range.InsertBefore Format(Date, "dd-MMM-YYYY") & Chr(9) & Chr(9)
.Footers(wdHeaderFooterPrimary).Range.Paragraphs.Alignment = wdAlignParagraphLeft
.Footers(wdHeaderFooterPrimary).Range.Font.Name = "ForzaMedium"
.Footers(wdHeaderFooterPrimary).Range.Font.Size = 12
End With
Debug.Print "modADCForms.gbAuditReportGraphs,0"
modADCForms.gInserttext wdStyleNormal, "Page 1", wdColorBlack
modADCForms.gInsertPage
modADCForms.gInserttext wdStyleNormal, "Page 2", wdColorBlack
Debug.Print "modADCForms.gbAuditReportGraphs 1"
Set rng = mobjWordDoc.Range
With rng
.Collapse wdCollapseEnd
.Collapse Direction:=wdCollapseEnd
.InsertParagraphAfter
.Collapse Direction:=wdCollapseEnd
End With
' Set objChart = mobjWordDoc.Shapes.AddChart(xl3DPie, , 60, , 450, rng) -- type mismatch
Set objChart = mobjWordDoc.Shapes.AddChart.chart
objChart.ChartType = xlPie
objChart.HasLegend = False
Debug.Print "modADCForms.gbAuditReportGraphs 2"
' Create chart worksheet
Set chartWorkSheet = objChart.ChartData.Workbook.Worksheets(1)
' Add a header
chartWorkSheet.Range("Table1[[#Headers],[Series 1]]").FormulaR1C1 = vRigName & " Non-Conformance Distribution"
rs.Open ssql, CurrentProject.Connection, adOpenStatic, adLockReadOnly
If Not rs.EOF Then
i = 2
Do While Not rs.EOF()
chartWorkSheet.Range("A" & i).FormulaR1C1 = rs.Fields("checklistdesc")
chartWorkSheet.Range("B" & i).FormulaR1C1 = rs.Fields("nccount")
i = i + 1
rs.MoveNext
Loop
End If
rs.Close
chartWorkSheet.ListObjects("Table1").Resize chartWorkSheet.Range("A1:B" & i - 1)
' Configure chart to show the values
With objChart
With .SeriesCollection(1)
.HasDataLabels = True
.DataLabels.ShowValue = True
.HasLeaderLines = True
.DataLabels.ShowCategoryName = True
End With
End With
' set the fonts
Debug.Print " Setting the fonts of the labels ..1."
objChart.ChartArea.Font.Size = 9
objChart.ChartArea.Font.Name = gsFontForzaMedium
' Set the location of the chart
With objChart.Parent
.Height = 450
.Top = 60
End With
' show the document
mobjWordApp.visible = True
' Close the spreadsheet chart object
objChart.ChartData.Workbook.Application.Quit
' Clear the objects
Set rs = Nothing
Set clsRig_ = Nothing
Set clsAudit_ = Nothing
' Set function to status OK
gbAuditReportGraphs = True
ex:
Exit Function
eh:
gError "Problem creating audit report graphs", "modADCForms", "gbAuditReportGraphs", Err, Error
Resume ex
End Function
Cindy, I have taken your value advice, and fixed it :)
Pass in parameter of the range
Public Function gbAuditReportGraphs(ByVal lAuditID As Long, rng As Range) As Boolean
Create chart as an inline shape
Set objChart = mobjWordDoc.InlineShapes.AddChart.Chart
objChart.ChartType = xlPie
Set the size with code such as
With mobjWordApp.ActiveDocument
.InlineShapes(1).Height = 450
.InlineShapes(1).Width = 400
End With
Copy and paste chart into the specified range
objChart.Copy
rng.Paste
Delete the original
objChart.Delete
I could find no other advice on how to do this; perhaps this post will help others.
One tip to make your solution more robust: Generally, you can't be sure that the InlineShape you insert is the first in the document, so ActiveDocument.InlineShapes(1) isn't reliable. Better to declare an InlineShape object and assign the InlineShape belonging to the chart to it, so you can be sure you're working with the right InlineShape:
'1.Pass in parameter of the range
Public Function gbAuditReportGraphs(ByVal lAuditID As Long, _
rng As Range) As Boolean
Dim objChart as Word.Chart
Dim objInlineShape as Word.InlineShape
'2.Create chart as an inline shape
Set objChart = mobjWordDoc.InlineShapes.AddChart.Chart
objChart.ChartType = xlPie
'3.Set the size with code such as
Set objInlineShape = objChart.Parent
With objInlineShape
.Height = 450
.Width = 400
End With
'4.Copy and paste chart into the specified range
objChart.Copy
rng.Paste
'5.Delete the original
objChart.Delete
End Function
Related
This is the code I use to dynamically create charts in Virtual Basic:
Dim Chart As Object
Set Chart = Charts.Add
With Chart
If bIssetSourceChart Then
CopySourceChart
.Paste Type:=xlFormats
End If
For Each s In .SeriesCollection
s.Delete
Next s
.ChartType = xlColumnClustered
.Location Where:=xlLocationAsNewSheet, Name:=chartTitle
Sheets(chartTitle).Move After:=Sheets(Sheets.count)
With .SeriesCollection.NewSeries
If Val(Application.Version) >= 12 Then
.values = values
.XValues = columns
.Name = chartTitle
Else
.Select
Names.Add "_", columns
ExecuteExcel4Macro "series.columns(!_)"
Names.Add "_", values
ExecuteExcel4Macro "series.values(,!_)"
Names("_").Delete
End If
End With
End With
#The CopySourceChart Sub:
Sub CopySourceChart()
If Not CheckSheet("Source chart") Then
Exit Sub
ElseIf TypeName(Sheets("Grafiek")) = "Chart" Then
Sheets("Grafiek").ChartArea.Copy
Else
Dim Chart As ChartObject
For Each Chart In Sheets("Grafiek").ChartObjects
Chart.Chart.ChartArea.Copy
Exit Sub
Next Chart
End If
End Sub
How can I keep the formatting of series that is applied in the If bIssetSourceChart part while deleting those series' data?
I have solved this issue before. I have charts that were created by macro but it only applied to the date I made them. So a made a refresh macro that runs after every Workbook open. I used source before and found that it deletes everything. then moved on to series only. I will paste my work here and try to explain. For quick navigation the second part of the code down there called sub aktualizacegrafu() might help you if you get lost find a reference in upper part of the code starting with sub generacegrafu()
Sub generacegrafu()
ThisWorkbook.Sheets("List1").CommandButton6.BackColor = &H0&
ThisWorkbook.Sheets("List1").CommandButton6.ForeColor = &HFFFFFF
Dim najdiposlradek As Object
Dim graf As Object
Dim vkladacistring As String
Dim vykreslenysloupec As Integer
Dim hledejsloupec As Object
Dim hledejsloupec2 As Object
Dim kvantifikator As Integer
Dim grafx As ChartObject
Dim shoda As Boolean
Dim jmenografu As String
Dim rngOrigSelection As Range
Cells(1, 1).Select
If refreshcharts = True Then
Set hledejsloupec = Range("11:11").Find(What:=prvnislovo, LookIn:=xlValues)
'dynamicaly generated, prvnislovo is for first word in graph and the macro looks for match in row 11 if it doesnt find any then
Else
'then it looks for match in option box
Set hledejsloupec = Range("11:11").Find(What:=ThisWorkbook.Sheets("List1").ComboBox1.Value, LookIn:=xlValues)
End If
If hledejsloupec Is Nothing Then
MsgBox "Zadaný sloupec v první nabídce nebyl nalezen."
Else
If refreshcharts = True Then
Set hledejsloupec2 = Range("11:11").Find(What:=druheslovo, LookIn:=xlValues)
Else
Set hledejsloupec2 = Range("11:11").Find(What:=ThisWorkbook.Sheets("List1").ComboBox2.Value, LookIn:=xlValues)
End If
If hledejsloupec2 Is Nothing Then
MsgBox "Zadaný sloupec v druhé nabídce nebyl nalezen."
Else
jmenografu = Cells(11, hledejsloupec.Column).Value & "_" & Cells(11, hledejsloupec2.Column).Value
Set najdiposlradek = Range("A:A").Find(What:=Date, LookIn:=xlValues)
Application.ScreenUpdating = False
Set rngOrigSelection = Selection
'This one selects series for new graph to be created
Cells(1048576, 16384).Select
Set graf = ThisWorkbook.Sheets("List1").Shapes.AddChart
rngOrigSelection.Parent.Parent.Activate
rngOrigSelection.Parent.Select
rngOrigSelection.Select 'trouble with annoing excel feature to unselect graphs
Application.ScreenUpdating = True
graf.Select
kvantifikator = 1
Do
shoda = False
For Each grafx In ThisWorkbook.Sheets("List1").ChartObjects
If grafx.Name = jmenografu Then
shoda = True
jmenografu = jmenografu & "(" & kvantifikator & ")"
kvantifikator = kvantifikator + 1
End If
Next grafx
'this checks if graph has younger brother in sheet
'but no we get to the part that matter do not bother playing with source of the graph because I have found it is quite hard to make it work properly
Loop Until shoda = False
'here it starts
ActiveChart.Parent.Name = jmenografu
ActiveChart.SeriesCollection.NewSeries 'add only series!
vkladacistring = "=List1!R12C" & hledejsloupec.Column & ":R" & najdiposlradek.Row & "C" & hledejsloupec.Column 'insert this into series
ActiveChart.SeriesCollection(1).Values = vkladacistring
vkladacistring = "=List1!R11C" & hledejsloupec.Column
ActiveChart.SeriesCollection(1).Name = vkladacistring
vkladacistring = "=List1!R12C" & hledejsloupec2.Column & ":R" & najdiposlradek.Row & "C" & hledejsloupec2.Column
ActiveChart.SeriesCollection(1).XValues = vkladacistring
'here it ends and onward comes formating
ActiveChart.Legend.Delete
ActiveChart.ChartType = xlConeColClustered
ActiveChart.ClearToMatchStyle
ActiveChart.ChartStyle = 41
ActiveChart.ClearToMatchStyle
ActiveSheet.Shapes(jmenografu).Chart.ChartArea.Format.ThreeD.RotationY = 90
ActiveSheet.Shapes(jmenografu).Chart.ChartArea.Format.ThreeD.RotationX = 0
ActiveChart.Axes(xlValue).MajorUnit = 8.33333333333333E-02
ActiveChart.Axes(xlValue).MinimumScale = 0.25
ActiveChart.Walls.Format.Fill.Visible = msoFalse
ActiveChart.Axes(xlCategory).MajorUnitScale = xlMonths
ActiveChart.Axes(xlCategory).MajorUnit = 1
ActiveChart.Axes(xlCategory).BaseUnit = xlDays
End If
End If
Call aktualizacelistboxu
ThisWorkbook.Sheets("List1").CommandButton6.BackColor = &H8000000D
ThisWorkbook.Sheets("List1").CommandButton6.ForeColor = &H0&
End Sub
the result i found is that you cannot keep formating completely when you close chart because source of chart doesnt work very well and when you delete it some format will be lost
I will post my actualization of chart as well
Sub aktualizacegrafu()
Dim grafx As ChartObject
Dim hledejsloupec As Object
Dim hledejsloupec2 As Object
Dim vkladacistring As String
Dim najdiposlradek As Object
For Each grafx In ThisWorkbook.Sheets("List1").ChartObjects
prvnislovo = Left(grafx.Name, InStr(1, grafx.Name, "_") - 1)
druheslovo = Right(grafx.Name, Len(grafx.Name) - InStr(1, grafx.Name, "_"))
'now it checks the names of charts .. the data loads from respective columns that are named the same way so I ussualy choose what statistic I want by choosing the columns needed
'for example I want to reflect my arrivals to work according to the hours I worked or to the date so I set 1st option to arrival and 2nd to date
grafx.Activate
Set najdiposlradek = Range("A:A").Find(What:=Date, LookIn:=xlValues)
Set hledejsloupec = Range("11:11").Find(What:=prvnislovo, LookIn:=xlValues)
If hledejsloupec Is Nothing Then
MsgBox "Hodnota v grafu již není mezi sloupci v tabulce. Aktualizace grafu " & grafx.Name & " bude ukončena."
Else
Set hledejsloupec2 = Range("11:11").Find(What:=druheslovo, LookIn:=xlValues)
If hledejsloupec2 Is Nothing Then
MsgBox "Hodnota v grafu již není mezi sloupci v tabulce. Aktualizace grafu " & grafx.Name & " bude ukončena."
Else
here it enters string that contains adress of desired cell I always enter it as string cause its easier to see with debug.print what is being entered
result looks like this List means Sheet in czech
activechart.seriescollection(1).values=List1!R12C1:R13C16
activechart.seriescollection(1).name=List1!R1C1:R1C15
vkladacistring = "=List1!R12C" & hledejsloupec.Column & ":R" & najdiposlradek.Row & "C" & hledejsloupec.Column
ActiveChart.SeriesCollection(1).Values = vkladacistring
vkladacistring = "=List1!R11C" & hledejsloupec.Column
ActiveChart.SeriesCollection(1).Name = vkladacistring
vkladacistring = "=List1!R12C" & hledejsloupec2.Column & ":R" & najdiposlradek.Row & "C" & hledejsloupec2.Column
ActiveChart.SeriesCollection(1).XValues = vkladacistring
End If
End If
Next grafx
Call aktualizacelistboxu
End Sub
so result of this is when you actually have a chart already but want to make slight changes to the area it applies to then it keeps the formating
hope this helped a bit if not I am sorry if it did keep the revard. It just got me curious because I was solving the same problem recently
if you need any further explanation comment this and I will try to explain
I need to call a word file from excel, and to create a template. Template will have 3 images UpperLeft, Upper Right and central image.
I thought that would be the best result do it through the table.
From some reason I can't create table from excel..
Private Sub CommandButton13_Click()
'Using early binding, not late-binding
Dim wsDoc As Word.Document
Dim wsApp As Word.Application
Dim wsRng As Word.Range
Dim wsTable as Word.Table
Dim intNoOfRows
Dim intNoOfColumns
Dim s As Word.InlineShape
Dim shp As Word.Shape
intNoOfRows = 4
intNoOfColumns = 2
Set wsApp = New Word.Application
wsApp.Visible = True
Set wsDoc = wsApp.Documents.Add
Set wsRange = wsDoc.Content
Set wsTable = wsDoc.Tables.Add(wsRange, intNoOfRows, intNoOfColumns)
wsTable.Borders.Enable = True
wsTable.Cell(1, 1).Range.InlineShapes.AddPicture _
UserForm1.txtImageLogoAdecco
wsTable.Cell(1, 2).Range.ParagraphFormat.Alignment = wdAlignParagraphRight
wsTable.Cell(1, 2).Range.InlineShapes.AddPicture _
UserForm1.txtImageLogoClient
Set wsRng = wsTable.Cell(2, 1).Range
With wsRng.Paragraphs.Add
wsTable.Cell(2, 1).Merge MergeTo:=wsTable.Cell(2, 2)
wsTable.Cell(2, 1).Height = 520
wsTable.Cell(2, 1).Range.Paragraphs.Add
wsTable.Cell(3, 1).Merge MergeTo:=objTable.Cell(3, 2)
wsTable.Cell(3, 1).Range.Text = "Prepared by:" & " " & UserForm1.txtPrepared
wsTable.Cell(4, 1).Merge MergeTo:=objTable.Cell(4, 2)
wsTable.Cell(4, 1).Range.ParagraphFormat.Alignment = wdAlignParagraphRight
wsTable.Cell(4, 1).Range.Text = "Belgrade," & " " & Format(Date, "MMMM DD, YYYY ")
Set wsRange = Nothing
Set wsTable = Nothing
Set wsDoc = Nothing
Set wsApp = Nothing
End Sub
Well, it's a bit clearer, but not completely. I've taken the liberty of editing your code so that it's correct, consistent and readable (without all the empty lines)!
I'll start with the part that is clear: << I want to move cursor a few lines down and to write some text. >>
To move the focus below a table, you get the table's range, then collapse it. For example:
Set wsRange = wsTable.Range
wsRange.Collapse wdCollapseEnd
'Now the range is in the paragraph following the table
wsRange.Text = "text following table"
"with background image I want to convert it to shape. I want to send iy behind the text"
This is the part that's not clear to me. Are you saying you want to insert one more image and position it behind the text? Use the Shapes.Add method and set the WrapFormat.Type to wdWrapBehind
I have 2 workbooks one has the vba (MainWb), the other is just a template (TempWb) that the code paste values and formulas from the mainworkbook. The TempWb only has one blank sheet named graphs. The code needs to open the xltx file (TempWb), add a sheet and rename based on value in a certain cell on the MainWb (if it does not already exist) and then to reference that new sheet in the copy values process from the MainWb. I tried recording a macro but it didn't really help. I have researched and put some stuff together but not sure if it fits and works. Any suggestions would be appreciated.
This is what I have so far.
Option Explicit
Sub ExportSave()
Dim Alpha As Workbook 'Template
Dim Omega As Worksheet 'Template
Dim wbMain As Workbook 'Main Export file
Dim FileTL As String 'Test location
Dim FilePath As String 'File save path
Dim FileProject As String 'Project information
Dim FileTimeDate As String 'Export Date and Time
Dim FileD As String 'Drawing Number
Dim FileCopyPath As String 'FileCopy save path
Dim FPATH As String 'File Search Path
Dim Extract As Workbook 'File Extract Data
Dim locs, loc 'Location Search
Dim intLast As Long 'EmptyCell Search
Dim intNext As Long 'EmptyCell Seach
Dim rngDest As Range 'Paste Value Range
Dim Shtname1 As String 'Part Platform
Dim Shtname2 As String 'Part Drawing Number
Dim Shtname3 As String 'Part Info
Dim rep As Long
With Range("H30000")
.Value = Format(Now, "mmm-dd-yy hh-mm-ss AM/PM")
End With
FilePath = "C:\Users\aholiday\Desktop\FRF_Data_Macro_Insert_Test"
FileCopyPath = "C:\Users\aholiday\Desktop\Backup"
FileTL = Sheets("Sheet1").Range("A1").Text
FileProject = Sheets("Sheet1").Range("E2").Text
FileTimeDate = Sheets("Sheet1").Range("H30000").Text
FileD = Sheets("Sheet1").Range("E3").Text
FPATH = "C:\Users\aholiday\Desktop\FRF_Data_Macro_Insert_Test\"
Shtname1 = wbMain.Sheets("Sheet1").Range("E2")
Shtname2 = wbMain.Sheets("Sheet1").Range("E3")
Shtname3 = wbMain.Sheets("Sheet1").Range("E4")
Select Case Range("A1").Value
Case "Single Test Location"
Case "Location 1"
Application.DisplayAlerts = False
Set wbMain = Workbooks("FRF Data Export Graphs.xlsm")
wbMain.Sheets("Sheet1").Copy
ActiveWorkbook.SaveAs Filename:=FileCopyPath & "\" & FileProject & Space(1) & FileD & Space(1) & FileTL & Space(1) & FileTimeDate & ".xlsx", FileFormat:=xlOpenXMLWorkbook
ActiveWorkbook.Close False
Set Alpha = Workbooks.Open("\\plymshare01\Public\Holiday\FRF Projects\Templates\FRF Data Graphs.xltx")
For rep = 1 To (Worksheets.Count)
If LCase(Sheets(rep)).Name = LCase(Shtname1 & Space(1) & Shtname2 & Space(1) & Shtname3) Then
MsgBox "This Sheet already exists"
Exit Sub
End If
Next
Sheets.Add after:=Sheets(Sheets.Count)
Sheets(ActiveSheet.Name).Name = Shtname1 & Space(1) & Shtname2 & Space(1) & Shtname3
Set Omega = Workbooks(ActiveWorkbook.Name).Sheets("ActiveWorksheet.Name")
locs = Array("FRF Data Export Graphs.xlsm")
'set the first data block destination
Set rngDest = Omega.Cells(3, 1).Resize(30000, 3)
For Each loc In locs
Set Extract = Workbooks.Open(Filename:=FPATH & loc, ReadOnly:=True)
rngDest.Value = Extract.Sheets("Sheet1").Range("A4:D25602").Value
Extract.Close False
Set rngDest = rngDest.Offset(0, 4) 'move over to the right 4 cols
Next loc
With ActiveWorksheet.Range("D3:D25603").Formula = "=SQRT((B3)^2+(C3)^2)"
ActiveWorkbook.Charts.Add
ActiveChart.ChartType = xlXYScatterLines
ActiveChart.SetSourceData Source:=Sheets("Graphs").Range("A3:D7"), PlotBy:=xlRows
ActiveChart.Location Where:=xlLocationAsNewSheet, Name:=Shtname2
With ActiveChart
.HasTitle = True
.ChartTitle.Characters.Text = Shtname2
.Axes(xlCategory, xlPrimary).HasTitle = True
.Axes(xlCategory, xlPrimary).AxisTitle.Characters.Text = "Hz"
.Axes(xlValue, xlPrimary).HasTitle = True
.Axes(xlValue, xlPrimary).AxisTitle.Characters.Text = "Blank"
End With
Application.ScreenUpdating = True
Case "Location 2"
Case "Location 3"
Case "Location 4"
Case Else
MsgBox "Export Failed!"
End Select
Application.DisplayAlerts = True
End Sub
Run-time error '91'
Object variable or With block not set
code lines
Shtname1 = wbMain.Sheets("Sheet1").Range("E2")
Shtname2 = wbMain.Sheets("Sheet1").Range("E3")
Shtname3 = wbMain.Sheets("Sheet1").Range("E4")
This is supposed to tell the code what to name the new created sheet
Fixed: Moved under
Set = wbMain = Workbooks("FRF Data Export Graphs.xlsm")
New Error:
Object doesnt support this property or method
code
If LCase(Sheets(rep)).Name = LCase(Shtname1 & Space(1) & Shtname2 & Space(1) & Shtname3) Then
A few things could be happening here
Shtname1 = wbMain.Sheets("Sheet1").Range("E2")
You are trying to access three objects and set a third. This means wbMain needs to be set and Sheets("Sheet1") need to be set and Range("E2") needs to exist.
You also, because you are setting Shtname1 as a string I'd be explicit about what value you want to go in there.
Shtname1 = wbMain.Sheets("Sheet1").Range("E2").Value
So with the breakpoint on that line and the locals window open (View > Locals Window) make sure everything is set. If it's not it needs to be. One of those values is not set.
If you do infact Set wbMain = Workbooks("FRF Data Export Graphs.xlsm") but it is in a different module or a different sub and wbMain is redeclared at the top of this sub these statements are in totally different contexts. The first wbMain is a different variable basically.
I have been working with code that I found HERE but I am having difficulty getting it to do one more task for me. I have added another column (3) to my excel document that has the "classification" of the acronym & definition and I want to add that to the newly created word doc in column 1, before the acronym. I have tried several different ways of moving the provided code around but it always results in an error. Any help is appreciated. I have included the working code below. Like I said, it works I just want it to do one more thing. Thank you!
Sub ExtractACRONYMSToNewDocument()
Dim oDoc_Source As Document
Dim oDoc_Target As Document
Dim strListSep As String
Dim strAcronym As String
Dim strDef As String
Dim oTable As Table
Dim oRange As Range
Dim n As Long
Dim m As Long
m = 0
Dim strAllFound As String
Dim Title As String
Dim Msg As String
Dim objExcel As Object
Dim objWbk As Object
Dim rngSearch As Object
Dim rngFound As Object
Dim targetCellValue As String
' message box title
Title = "Extract Acronyms to New Document"
' Set message box message
Msg = "This macro finds all Acronyms (consisting of 2 or more " & _
"uppercase letters, Numbers or '/') and their associated definitions. It " & _
"then extracts the words to a table at the current location you have selected" & vbCr & vbCr & _
"Warning - Please make sure you check the table manually after!" & vbCr & vbCr & _
"Do you want to continue?"
' Display message box
If MsgBox(Msg, vbYesNo + vbQuestion, Title) <> vbYes Then
Exit Sub
End If
' Stop the screen from updating
Application.ScreenUpdating = False
'Find the list separator from international settings
'May be a comma or semicolon depending on the country
strListSep = Application.International(wdListSeparator)
'Start a string to be used for storing names of acronyms found
strAllFound = "#"
' give the active document a variable
Set oDoc_Source = ActiveDocument
'Create a variable for excel and open the definition workbook
Set objExcel = CreateObject("Excel.Application")
Set objWbk = objExcel.Workbooks.Open("C:\Users\USERNAME\Documents\Test_Definitions.xlsx")
'objExcel.Visible = True
objWbk.Activate
'Create new document to temporarily store the acronyms
Set oDoc_Target = Documents.Add
' Use the target document
With oDoc_Target
'Make sure document is empty
.Range = ""
'Insert info in header - change date format as you wish
.PageSetup.TopMargin = CentimetersToPoints(3)
.Sections(1).Headers(wdHeaderFooterPrimary).Range.Text = _
"Acronyms extracted from: " & oDoc_Source.FullName & vbCr & _
"Created by: " & Application.UserName & vbCr & _
"Creation date: " & Format(Date, "MMMM d, yyyy")
'Adjust the Normal style and Header style
With .Styles(wdStyleNormal)
.Font.Name = "Arial"
.Font.Size = 10
.ParagraphFormat.LeftIndent = 0
.ParagraphFormat.SpaceAfter = 6
End With
With .Styles(wdStyleHeader)
.Font.Size = 8
.ParagraphFormat.SpaceAfter = 0
End With
'Insert a table with room for acronym and definition
Set oTable = .Tables.Add(Range:=.Range, NumRows:=2, NumColumns:=4)
With oTable
'Format the table a bit
'Insert headings
.Range.Style = wdStyleNormal
.AllowAutoFit = False
.Cell(1, 1).Range.Text = "Classification"
.Cell(1, 2).Range.Text = "Acronym"
.Cell(1, 3).Range.Text = "Definition"
.Cell(1, 4).Range.Text = "Page"
'Set row as heading row
.Rows(1).HeadingFormat = True
.Rows(1).Range.Font.Bold = True
.PreferredWidthType = wdPreferredWidthPercent
.Columns(1).PreferredWidth = 15
.Columns(2).PreferredWidth = 25
.Columns(3).PreferredWidth = 55
.Columns(4).PreferredWidth = 5
End With
End With
With oDoc_Source
Set oRange = .Range
n = 1 'used to count below
' within the total range of the source document
With oRange.Find
'Use wildcard search to find strings consisting of 3 or more uppercase letters
'Set the search conditions
'NOTE: If you want to find acronyms with e.g. 2 or more letters,
'change 3 to 2 in the line below
.Text = "<[A-Z][A-Z0-9/]{1" & strListSep & "}>"
.Forward = True
.Wrap = wdFindStop
.Format = False
.MatchCase = True
.MatchWildcards = True
'Perform the search
Do While .Execute
'Continue while found
strAcronym = oRange
'Insert in target doc
'If strAcronym is already in strAllFound, do not add again
If InStr(2, strAllFound, "#" & strAcronym & "#") = 0 Then
'Add new row in table from second acronym
If n > 1 Then oTable.Rows.Add
'Was not found before
strAllFound = strAllFound & strAcronym & "#"
'Insert in column 1 in oTable
'Compensate for heading row
With oTable
.Cell(n + 1, 2).Range.Text = strAcronym
'Insert page number in column 4
.Cell(n + 1, 4).Range.Text = oRange.Information(wdActiveEndPageNumber)
' Find the definition from the Excel document
With objWbk.Sheets("Sheet1")
' Find the range of the cells with data in Excel doc
Set rngSearch = .Range(.Range("A1"), .Range("A" & .Rows.Count).End(-4162))
' Search in the found range for the
Set rngFound = rngSearch.Find(What:=strAcronym, After:=.Range("A1"), LookAt:=1)
' if nothing is found count the number of acronyms without definitions
If rngFound Is Nothing Then
m = m + 1
' Set the cell variable in the new table as blank
targetCellValue = ""
' If a definition is found enter it into the cell variable
Else
targetCellValue = .Cells(rngFound.Row, 2).Value
End If
End With
' enter the cell varibale into the definition cell
.Cell(n + 1, 3).Range.Text = targetCellValue
End With
' add one to the loop count
n = n + 1
End If
Loop
End With
End With
'Sort the acronyms alphabetically - skip if only 1 found
If n > 2 Then
With Selection
.Sort ExcludeHeader:=True, FieldNumber:="Column 2", SortFieldType _
:=wdSortFieldAlphanumeric, SortOrder:=wdSortOrderAscending
'Go to start of document
.HomeKey (wdStory)
End With
End If
' update screen
Application.ScreenUpdating = True
'If no acronyms found set message saying so
If n = 1 Then
Msg = "No acronyms found."
' set the final messagebox message to show the number of acronyms found and those that did not have definitions
Else
Msg = "Finished extracting " & n - 1 & " acronymn(s) to a new document. Unable to find definitions for " & m & " acronyms."
End If
' Show the finished message box
On Error Resume Next
AppActivate Application.Caption
On Error GoTo 0
MsgBox Msg, vbOKOnly, Title
'Close Excel after
objWbk.Close Saved = True
'Clean up
Set oRange = Nothing
Set oDoc_Source = Nothing
Set oDoc_Target = Nothing
Set oTable = Nothing
Set objExcel = Nothing
Set objWbk = Nothing
End Sub
If anyone is looking for this solution, I was able to figure it out by duplicating the following lines. It then counts how many definitions and classifications it was unable to find and reports at the end.
' Find the definition from the Excel document
With objWbk.Sheets("Sheet1")
' Find the range of the cells with data in Excel doc
Set rngSearch = .Range(.Range("A1"), .Range("A" & .Rows.Count).End(-4162))
' Search in the found range for the
Set rngFound = rngSearch.Find(What:=strAcronym, After:=.Range("A1"), LookAt:=1)
' if nothing is found count the number of acronyms without definitions
If rngFound Is Nothing Then
m = m + 1
' Set the cell variable in the new table as blank
targetCellValue = ""
' If a definition is found enter it into the cell variable
Else
targetCellValue = .Cells(rngFound.Row, 2).Value
End If
End With
' enter the cell varibale into the definition cell
.Cell(n + 1, 3).Range.Text = targetCellValue
End With
I'm trying to write a macro that will create a table of contents, listing the name of each of the worksheets currently selected by the user, together with the number of the page on which it starts when printed. I've taken the code from this page and adapted it a little as below.
However, when the new worksheet ("Contents") is created, that becomes the active, selected sheet, such that I can no longer use ActiveWindow.SelectedSheets to refer back to the collection of worksheets selected by the user. So I would like to store that information before creating the new sheet. How can I do this?
I have tried assigning it to a variable of type Worksheets as you can see, but this generates an error message. (I also tried Collection but to no avail.)
Sub CreateTableOfContents()
' Determine if there is already a Table of Contents
' Assume it is there, and if it is not, it will raise an error
' if the Err system variable is > 0, you know the sheet is not there
Dim WST As Worksheet
Dim SelSheets As Worksheets
Set SelSheets = ActiveWindow.SelectedSheets
On Error Resume Next
Set WST = Worksheets("Contents")
If Not Err = 0 Then
' The Table of contents doesn't exist. Add it
Set WST = Worksheets.Add(Before:=Worksheets("blankMagnitude"))
WST.Name = "Contents"
End If
On Error GoTo 0
' Set up the table of contents page
WST.[A2] = "Table of Contents"
With WST.[A6]
.CurrentRegion.Clear
.Value = "Subject"
End With
WST.[B6] = "Page(s)"
WST.Range("A1:B1").ColumnWidth = Array(36, 12)
TOCRow = 7
PageCount = 0
' Do a print preview on all sheets so Excel calcs page breaks
' The user must manually close the PrintPreview window
Msg = "Excel needs to do a print preview to calculate the number of pages." & vbCrLf & "Please dismiss the print preview by clicking close."
MsgBox Msg
SelSheets.PrintPreview
' Loop through each sheet, collecting TOC information
For Each S In SelSheets
If S.Visible = -1 Then
S.Select
ThisName = ActiveSheet.Name
HPages = ActiveSheet.HPageBreaks.Count + 1
VPages = ActiveSheet.VPageBreaks.Count + 1
ThisPages = HPages * VPages
' Enter info about this sheet on TOC
WST.Select
Range("A" & TOCRow).Value = ThisName
Range("B" & TOCRow).NumberFormat = "#"
If ThisPages = 1 Then
Range("B" & TOCRow).Value = PageCount + 1 & " "
Else
Range("B" & TOCRow).Value = PageCount + 1 & " " ' & - " & PageCount + ThisPages
End If
PageCount = PageCount + ThisPages
TOCRow = TOCRow + 1
End If
Next S
End Sub
I just amended your code. Is this what you are trying? Honestly all you had to do was
Change Dim SelSheets As Worksheets to Dim SelSheets and your original code would have worked :)
Option Explicit
Sub CreateTableOfContents()
Dim WST As Worksheet, S As Worksheet
Dim SelSheets
Dim msg As String
Dim TOCRow As Long, PageCount As Long, ThisPages As Long
Dim HPages As Long, VPages As Long
Set SelSheets = ActiveWindow.SelectedSheets
On Error Resume Next
Application.DisplayAlerts = False
Worksheets("Contents").Delete
Application.DisplayAlerts = True
On Error GoTo 0
Set WST = Worksheets.Add(Before:=Worksheets("blankMagnitude"))
With WST
.Name = "Contents"
.[A2] = "Table of Contents"
.[A6] = "Subject"
.[B6] = "Page(s)"
.Range("A1:B1").ColumnWidth = Array(36, 12)
End With
TOCRow = 7: PageCount = 0
msg = "Excel needs to do a print preview to calculate the number of pages." & vbCrLf & "Please dismiss the print preview by clicking close."
MsgBox msg
SelSheets.PrintPreview
For Each S In SelSheets
With S
HPages = .HPageBreaks.Count + 1
VPages = .VPageBreaks.Count + 1
ThisPages = HPages * VPages
WST.Range("A" & TOCRow).Value = .Name
WST.Range("B" & TOCRow).NumberFormat = "#"
If ThisPages = 1 Then
WST.Range("B" & TOCRow).Value = PageCount + 1 & " "
Else
WST.Range("B" & TOCRow).Value = PageCount + 1 & " " ' & - " & PageCount + ThisPages
End If
PageCount = PageCount + ThisPages
TOCRow = TOCRow + 1
End With
Next S
End Sub
EDIT: One important thing. It's always good to use OPTION EXPLICIT :)
You could store references to each sheet;
function getSheetsSnapshot() as Worksheet()
dim shts() As Worksheet, i As long
redim shts(ActiveWindow.SelectedSheets.Count - 1)
for i = 0 to ActiveWindow.SelectedSheets.Count - 1
set shts(i) = ActiveWindow.SelectedSheets(i + 1)
next
getSheetsSnapshot = shts
end function
fetch & store them:
dim oldsel() as Worksheet: oldsel = getSheetsSnapshot()
do your stuff then refer back to the original selected sheets;
for i = 0 to ubound(oldsel)
msgbox oldsel(i).name
next
Dim wks as Worksheet, strName as String
For each wks in SelSheets
strName = strName & wks.Name & ","
Next
strName = Left(strName, Len(strName) -1)
Dim arrWks() as String
arrWks = Split(strName,",")
End Sub
Your will have all the selected sheets, by name, in an arrWks, which you can then process through. You could also add each sheet name to a collection as well in the loop making it smoother.
It's best to stay away from ActiveSheet as much as possible. In this way you can loop through array with a counter and process
So:
Dim intCnt as Ingeter
For intCnt = Lbound(arrWks) to UBound(arrWks)
Worksheets(arrWks(intCnt)).Activate
.... rest of code ....
Next
replaces
For Each S In SelSheets