I have searched for months to find an answer to my problem and think I'm close, but not sure how to rectify it. All my VBA knowledge comes from Google, Stack Overflow, & various forums so please excuse the state of my code.
Overall goal:
I have a PPT template file containing 1 template slide that has several charts full of "dummy" data, formatted exactly how I need it. I also have an Excel master file containing data on a particular sheet (also contains the VBA). I need to duplicate the template slide for numNames data rows, then on each slide populate the charts (and other items) with the real data contained in each row.
The issues:
Very low reliability of this code at scale. This code works well with numNames < ~15. If I have more rows of data/slides to populate, the code fails.
Sometimes graphs will "disappear" after populating with data leading to errors in later subs. This can happen to any of the circular graphs on any slide. I added .Refresh and .DoEvents to fix this, to no avail. Missing Graph
PPT sucks up a ton of available memory if I populate the charts too quickly which I think contributes to some of my headaches (hence the Application.Wait). I am using a work laptop running 64 bit Excel/PPT with approx 4GB RAM available at most times. Peak PPT memory usage ~1.3GB while inside loop. Not sure what is going on here.
I have tried Application.ScreenUpdating = false and it helps a bit, but the issues above still occur.
I believe all of my problems stem from how I'm populating these graphs with the real data, but so far I have not found any better solutions. I am looking for any advice on how to populate these graphs in a better/quicker way, or generally clean up this code so that it runs more smoothly. Thanks.
If you want to skip the setup portion of this sub, just ctrl+F '$
*some code here is not my own, not taking credit for any code I did not personally write
Option Explicit
'Excel
Public ProjectName As String
Public NewCtrlFileExists As String
Public wb As Workbook
Public ctrl As Worksheet
Public xData As Worksheet
Public iHeaders As Integer
Public numNames As Integer
Public FirstRow As Integer
Public LastRow As Integer
Public LastCol As Integer
'Powerpoint
Public myPres As PowerPoint.Presentation
'Error handling
Public errArea As String
Public g_objFSO As Scripting.FileSystemObject
Public g_scrText As Scripting.TextStream
Public Msg, Style, Response
Sub CreateDashboards()
'1. Add PPT refs to Excel: Tools > References > Microsoft PowerPoint
'2. Add error logging: Tools > References > Microsoft Scripting Runtime
iHeaders = 0
numNames = 0
FirstRow = 0
LastRow = 0
LastCol = 0
On Error GoTo Failure
Startup:
errArea = "Startup"
Set wb = Excel.Application.ActiveWorkbook
Sheet1.Activate 'Control sheet
Set ctrl = wb.ActiveSheet
'File names
ProjectName = ctrl.Range("ProjectName") 'project name
Dim PptTemplateName As String
PptTemplateName = ctrl.Range("PptTemplateName") 'template name
'Get data
Sheet2.Activate 'Data
Set xData = wb.ActiveSheet
iHeaders = 2
FirstRow = iHeaders + 1
LastRow = xData.UsedRange.Rows.Count
LastCol = xData.UsedRange.Columns.Count
numNames = LastRow - iHeaders
Initialize:
errArea = "Initialize"
ctrl.Range("PptReportName") = ProjectName 'PptReportName: default is project name, but also user-defined if desired
'Round and clean data
Call CleanData
'get E chart data
Dim rngEcols As Range
Set rngEcols = xData.Range("1:1")
Dim iEcount As Integer, lEstartCol As Integer, lEendCol As Integer
iEcount = Excel.Application.CountIf(rngEcols, "E")
lEstartCol = WorksheetFunction.Match("E", rngEcols, 0)
lEendCol = lEstartCol + iEcount - 1
'get max value for all E chart data
Dim dEmaxvalue As Single 'decimal
Dim dEAxisMax As Single 'decimal
dEmaxvalue = Application.Max(xData.Range(Cells(iHeaders + 1, lEstartCol), Cells(LastRow, lEendCol)))
'define the axis max as dEmaxvalue rounded up to nearest 10%, then add 5%
dEAxisMax = Application.RoundUp(dEmaxvalue, 1) + 0.05
'get attribute label positions
Dim lEstart, lEend
Set lEstart = xData.Cells((FirstRow - 1), lEstartCol)
Set lEend = xData.Cells((FirstRow - 1), lEendCol)
'get PPT
Set myPres = GetOpenOrClosedPPT(wb.Path & "\" & PptTemplateName & ".pptx")
myPres.Windows(1).Activate
'transpose attribute labels into PPT E chart
With myPres.Slides(1).Shapes("E").Chart
.ChartData.Workbook.Sheets(1).Range("A2:" & Cells(iEcount + 1, 1).Address & "") _
= Excel.Application.Transpose(xData.Range("" & lEstart.Address & ":" & lEend.Address & ""))
Dim rngEdata As Range 'get E data range
Set rngEdata = Range("A1:" & Cells(iEcount + 1, 2).Address & "")
Dim sEchartsource As String
sEchartsource = "='Sheet1'!" & rngEdata.Address & "" 'set chart data source to E data range
.SetSourceData Source:=sEchartsource
.Axes(xlValue).MinimumScale = 0
.Axes(xlValue).MaximumScale = dEAxisMax
End With
Execute:
errArea = "Execute"
'create slide for each row of data
Dim i As Long
For i = 1 To numNames - 1 'template slide already exists
myPres.Slides(1).Duplicate
Next i
'populate slides with data
Dim lDataRow As Integer, lSldNum As Integer
lSldNum = 1
lDataRow = lSldNum + iHeaders 'account for headers
Dim Slide As Slide
Dim y As Integer
'$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
'$$$$$$$ Begin populate chart data $$$$$$$$$$$$$$$$
'$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
For Each Slide In myPres.Slides
errArea = "Slide " & lSldNum
myPres.Slides(lSldNum).Select
With myPres.Slides(lSldNum)
With .Shapes("B").Chart
.ChartData.Workbook.Sheets(1).Range("B2").Value = xData.Cells(lDataRow, 5) * 100
.Refresh
.ChartData.Workbook.Close
End With
With .Shapes("C").Chart
.ChartData.Workbook.Sheets(1).Range("B2").Value = xData.Cells(lDataRow, 6) * 100
.Refresh
.ChartData.Workbook.Close
End With
With .Shapes("E").Chart
For y = 1 To iEcount
.ChartData.Workbook.Sheets(1).Cells(1 + y, 2) = xData.Cells(lDataRow, (lEstartCol - 1) + y)
Next y
.Refresh
.ChartData.Workbook.Close
End With
With .Shapes("G").Chart
.ChartData.Workbook.Sheets(1).Range("B2").Value = xData.Cells(lDataRow, 11) * 100
.Refresh
.ChartData.Workbook.Close
End With
With .Shapes("K").Chart
.ChartData.Workbook.Sheets(1).Range("B2").Value = xData.Cells(lDataRow, 13) * 100
.Refresh
.ChartData.Workbook.Close
End With
End With
'increment slide & row indices
lSldNum = lSldNum + 1
lDataRow = lDataRow + 1
Application.Wait (Now + TimeValue("0:00:02"))
DoEvents
Next Slide
myPres.Slides(1).Select 'return to starting position
GoTo Success
Success:
'Write to log file
Call LogFile_Write(wb.Path, "LoadDashboards", "SUCCESS", numNames & " names' data loaded")
myPres.SaveAs Filename:=wb.Path & "\" & ProjectName & ".pptx"
'Notify user
AppActivate Application.Caption
MsgBox "Data loaded successfully.", vbSystemModal + vbInformation
Exit Sub
Failure:
'write to log file
Call LogFile_Write(wb.Path, "LoadDashboards", "ERROR", errArea & " - " & Err.Number & " - " & Err.Description)
'Notify user
AppActivate Application.Caption
MsgBox "An error occurred. Please try again.", vbSystemModal + vbCritical, "Error"
Exit Sub
End Sub
Public Function CleanData()
On Error GoTo Failure
Dim x As Integer 'Rows
Dim y As Integer 'Cols
For y = 3 To LastCol
Select Case y
'Round raw data to 2 decimal places
Case 5, 6, 7, 9, 11, 13 'E attributes data first, then E average
For x = FirstRow To LastRow
xData.Cells(x, y) = Application.WorksheetFunction.Round(xData.Cells(x, y), 2)
Next x
End Select
Next y
Exit Function
Failure:
'Write to log file
Call LogFile_Write(wb.Path, "CleanData", "ERROR", " - " & Err.Number & " - " & Err.Description)
'Notify user
AppActivate Application.Caption
MsgBox "An error occurred. Please try again.", vbSystemModal + vbCritical, "Error"
End Function
Public Function GetOpenOrClosedPPT(ByVal sTargetFullName As String) As Object
Dim funcPPTApp As Object
Dim p As PowerPoint.Presentation
On Error Resume Next
Set funcPPTApp = GetObject(, "PowerPoint.Application") 'Check if PPT is running
PPTisOpen:
If Not (funcPPTApp Is Nothing) Then 'If PPT is running
For Each p In funcPPTApp.Presentations 'For all open Presentations
If p.FullName = sTargetFullName Then 'If name matches target Presentation
Set GetOpenOrClosedPPT = p 'Set function result to Presentation
Exit Function
End If
Next p
GoTo PPTisNotOpen 'If PPT is running but file is not open
End If
PPTisNotOpen:
Set funcPPTApp = CreateObject("PowerPoint.Application")
funcPPTApp.Presentations.Open (sTargetFullName) 'Open target Presentation
Set GetOpenOrClosedPPT = funcPPTApp.Presentations(sTargetFullName) 'Set function result to Presentation
End Function
Public Function LogFile_Write( _
ByVal sPath As String _
, ByVal sProcedure As String _
, ByVal sType As String _
, ByVal sDescription As String)
Dim sFilePath As String
sFilePath = sPath & "\debug_log.txt" 'logfile path
Dim sText As String
On Error GoTo ErrorHandler
If (g_objFSO Is Nothing) Then
Set g_objFSO = New FileSystemObject 'Initialize var
End If
If (g_scrText Is Nothing) Then
If (g_objFSO.FileExists(sFilePath) = False) Then 'If logfile does not already exist, create one
Set g_scrText = g_objFSO.OpenTextFile(sFilePath, IOMode.ForWriting, True)
sText = "File created:" & Format(Date, "DD MMM YYYY") & vbCrLf
Else
Set g_scrText = g_objFSO.OpenTextFile(sFilePath, IOMode.ForAppending)
End If
End If
'Append new line to existing text
sText = sText & "- " & _
sProcedure & " " & _
sType & ": " & _
Format(Date, "DD MMM YYYY") & "-" & _
Time() & " || " & _
sDescription
g_scrText.WriteLine sText
g_scrText.Close
Set g_scrText = Nothing
Exit Function
ErrorHandler:
Set g_scrText = Nothing
Call MsgBox("Unable to write to log file", vbCritical, "LogFile_Write")
End Function
Try sleep command of windows. (sleep will pause the code for sometime)
To let charts sometime to refresh.
Type below line on top of the program:
Public Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal Milliseconds As LongPtr)
In the code, after updating the charts and before closing the chart workbook,
Type:
sleep 5000
5000 is for 5 seconds, you can modify for your choice.
Regards,
Balu.
Related
I have between 800 excels files that I need to transfer over to one sheet but before the transfer, I need to add a column ("A:A") and copy one cell value (before column added ("C1") after column ("D1")) and use column ("C:C") to get the range it would need to be pasted in column("A:A")
I have done the code already but struggling to add this on. If anyone can help that would be amazing.
Sub LoopThrough()
Dim MyFile As String, Str As String, MyDir As String
Dim sh As Worksheet, MasterRange As Range, TempWB As Workbook, TempSH As Worksheet, TempRng As Range, TempRow As Range
Dim NewMasterLine As Long
Dim StartTime As Double
Dim MinutesElapsed As String
StartTime = Timer
On Error GoTo ErrorHandler
Set sh = ThisWorkbook.Worksheets("Sheet1")
' Change address to suite
MyDir = "C:\"
MyFile = Dir(MyDir & "*.xls")
ChDir MyDir
' The following lines will put excel in a state similar to "frozen" mode. This will increase the code performance, as CPU will solely focus on performing
' the operations required by the code and not on showing the changes happening on excel
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Dim x As Long
x = 0
' Here starts the loop related to the files in folder
Do While MyFile <> ""
'TempWB is a Worksheet object - will be the importing worksheet. TempRng is the used range in sheet 1 of the workbook
Set TempWB = Workbooks.Open(FileName:=MyFile, UpdateLinks:=False, Password:=CalcPassword(MyFile))
Columns(1).Insert
Range("c2").Copy Range("A4:A10000")
Set TempSH = TempWB.Worksheets(1)
Set TempRng = TempSH.Range("A1:DB" & TempSH.Range("A" & TempSH.Rows.Count).End(xlUp).Row)
TempRng.Range("A:A").Insert ' This is where I tried to add in the extra column
TempRng.Range("A1").Value = TempRng.Range("D1").Value ' Tried doing this as a test but still pasted as if no changes had been made????
'NewMasterLine is the last used row (+1) of the Master Workbook (It is basically where the new rows will start to be imported)
NewMasterLine = sh.Range("A" & sh.Rows.Count).End(xlUp).Row
If NewMasterLine > 1 Then NewMasterLine = NewMasterLine + 1
'This will loop through all the rows of the range to be imported, checking the first column.
' If the value in the second column is work-xne-ams, will import the single row in the master workbook
For Each TempRow In TempRng.Rows
If Left(TempRow.Cells(1, 2).Value, 5) = "SHIFT" Or TempRow.Row < 4 Then
'If TempRow.Cells(1, 2).Value = "SHIFT--1" Or TempRow.Row < 4 Then
Set MasterRange = sh.Range("A" & NewMasterLine & ":DA" & NewMasterLine)
MasterRange.Value = TempRow.Value
NewMasterLine = NewMasterLine + 1
End If
Next
TempWB.Close savechanges:=False
MyFile = Dir()
x = x + 1
ThisWorkbook.Worksheets("PWD").Range("H2") = x
Loop
ErrorHandler:
If Err.Number <> 0 Then MsgBox "An error occurred." & vbNewLine & vbNewLine & "Last file that was attempted to be opened: " & MyFile & vbNewLine & vbNewLine & Err.Description
Application.ScreenUpdating = True
Application.DisplayAlerts = True
MinutesElapsed = Format((Timer - StartTime) / 86400, "hh:mm:ss")
MsgBox "This code ran successfully in " & MinutesElapsed & " minutes", vbInformation
End Sub
Function CalcPassword(FileName As String) As String
CalcPassword = ""
On Error Resume Next
Dim TheFile As String: TheFile = Split(Split(FileName, "\")(UBound(Split(FileName, "\"))), ".")(0)
Dim PWD As Range: Set PWD = ThisWorkbook.Worksheets("PWD").ListObjects("PWD").DataBodyRange
CalcPassword = WorksheetFunction.VLookup(TheFile, PWD, 5, False)
End Function
I have an Excel work book which is acting as a database and a UserForm which acts as a UI. Both are in different workbooks.
I want to populate the UserForm with data from Excel workbook .
Private Sub CommandButton4_Click()
Dim n As Long, i As Long
n = 0
Dim mydata1 As Workbook
Set mydata1 = Workbooks.Open("\\NTSYDFSP150\Shared\fmd\credit\LEM_Reports\SV References\SV Entry Form Input.xlsx")
mydata1.Worksheets("sheet1").Activate
mydata1.Worksheets("sheet1").Range("A1").Select
n = Worksheets("sheet1").Range("a1").CurrentRegion.Rows.Count
For i = 2 To n
If Trim(Sheet1.Cells(i, 1)) <> Trim(UserForm1.TextBox157.Text) And i = n Then
MsgBox ("Name not found")
End If
If Trim(Sheet1.Cells(i, 1)) = Trim(UserForm1.TextBox157.Text) Then
UserForm1.TextBox1.Text = Sheet1.Cells(i, 1)
Exit For
End If
Next i
mydata1.Save
mydata1.Close
MsgBox "Data searched successfully", 0, vbNullString
End Sub
Issue :
When I run the code am not able to retrieve data from workbook Excel database.
Sheet1.Cells(i, 1): - This field still refers to Shee1 from User form work book while it should be referring to work book at shared drive location since I had activated and opened that .
Note: n is calculated correctly.
I cleaned up your code and qualified the ranges where necessary. Not qualifying the ranges is most likely the error here. Example: Worksheets("sheet1").Range("a1"). ... needs to be mydata1.Worksheets("sheet1").Range("a1"). .... Try the following code:
Private Sub CommandButton4_Click()
Dim n As Long, i As Long
n = 0
Dim mydata1 As Workbook
Set mydata1 = Workbooks.Open("\\NTSYDFSP150\Shared\fmd\credit\LEM_Reports\SV References\SV Entry Form Input.xlsx")
n = mydata1.Worksheets("sheet1").Range("a1").CurrentRegion.Rows.Count
For i = 2 To n
If Trim(mydata1.Sheet1.Cells(i, 1)) <> Trim(UserForm1.TextBox157.Text) And i = n Then
MsgBox ("Name not found")
End If
If Trim(mydata1.Sheet1.Cells(i, 1)) = Trim(UserForm1.TextBox157.Text) Then
UserForm1.TextBox1.Text = mydata1.Sheet1.Cells(i, 1)
Exit For
End If
Next i
mydata1.Save
mydata1.Close
MsgBox "Data searched successfully", 0, vbNullString
End Sub
Note that activating the workbook and .Selecting a Range is not necessary in this case (so I deleted it) and should be avoided in general (see comment above for additional advice).
This is just a suggested way to prevent opening another workbook:
Private Sub CommandButton4_Click()
Dim wbPath As String: wbPath = "\\NTSYDFSP150\Shared\fmd\credit\LEM_Reports\SV References\"
Dim wbName As String: wbName = "SV Entry Form Input.xlsx"
Dim wsName As String: wsName = "sheet1"
Dim arrList As Object: Set arrList = CreateObject("System.Collections.ArrayList")
Dim lr As Long, x As Long
'Get the last row from A column, notice we need R1C1 notation for Excel4Macro
lr = ExecuteExcel4Macro("MATCH(""zzz"",'" & wbPath & "[" & wbName & "]" & wsName & "'!C1)")
'Let's use an ArrayList to get our validation list
For x = 2 To lr
arrList.Add Trim(ExecuteExcel4Macro("'" & wbPath & "[" & wbName & "]" & wsName & "'!R" & x & "C1"))
Next x
'Check if ArrayList contains your lookup value
If arrList.Contains(Trim(UserForm1.TextBox157.Text)) Then
UserForm1.TextBox1.Text = UserForm1.TextBox157.Text
Else
MsgBox ("Name not found")
End If
MsgBox "Data searched successfully"
End Sub
I have an imported CSV which will always put part numbers into Column B, the part drawing PDF is located in a central location.
I am trying to copy each drawing from one folder location to another, this part i have been successful with, however some of the files can have up to 3000 lines which means the copy sub can take some time to complete and may seem like excel is not functioning.
I have created a progress bar from some helpful tutorial, but i am struggling to combine them.
I understand the progress bar needs to calculate something in order to move the slider so i included a sub to count the number of unique entries in column B ( this would be the number of drawing which need copied ) The figure can then be used to create a percentage of completion?
Sub start()
UserForm1.Show
End Sub
Sub code()
Dim i As Integer, j As Integer, pctCompl As Single
'Sheet1.Cells.Clear
For i = 1 To 100
For j = 1 To 1000
Cells(i, 1).Value = j
Next j
pctCompl = i
progress pctCompl
Next i
End Sub
Sub progress(pctCompl As Single)
UserForm1.Text.Caption = pctCompl & "% Completed"
UserForm1.Bar.Width = pctCompl * 2
UserForm1.Caption = ListCount & "Files"
DoEvents
End Sub
Sub CountUniqueValues()
Dim LstRw As Long, Rng As Range, List As Object, ListCount As Long
LstRw = Cells(Rows.Count, "B").End(xlUp).Row
Set List = CreateObject("Scripting.Dictionary")
For Each Rng In Range("B2:B" & LstRw)
If Not List.Exists(Rng.Value) Then List.Add Rng.Value, Nothing
Next
ListCount = List.Count
End Sub
Sub PDFcopy()
Dim R As Range
Dim SourcePath As String, DestPath As String, FName As String
'Setup source and dest path (Note: must have a trailing backslash!)
SourcePath = "G:\test-copyfrom\" 'choose directory to copy from
DestPath = "C:\test-copyto\" 'choose directory to copy to
'Visit each used cell in column B
For Each R In Range("B1", Range("B" & Rows.Count).End(xlUp))
'Search the file with the file mask from the cell (Note: can contain wildcards like *.xls)
FName = Dir(SourcePath & R.Value & ".pdf")
'Loop while files found
Do While FName <> ""
'Copy the file
FileCopy SourcePath & FName, DestPath & FName
'Search the next file
FName = Dir()
Loop
Next
MsgBox ("files copied")
Here's how I code my progress bar
Sub progress(percentComplete As Single)
ProgressBar.Text.Caption = percentComplete & "% Completed"
ProgressBar.Bar.Width = percentComplete * 2
DoEvents
End Sub
And in my sub that does stuff:
'Update ProgressBar at certain points in the code
percentComplete = 11
progress percentComplete
Or
For each cell in Range("A1:A" & LRow)
'Do stuff
'Update ProgressBar in a loop
percentComplete = 11 + Int(cell.Row / LRow * 60) 'where 11 is the starting value, and 60 the percentage to be added
progress percentComplete
Next cell
This is to support my comment about using the progress bar
Dim f As UserForm1
Sub SetUpAProgressBar()
Set f = New UserForm1
f.Show vbModeless
f.ProgressBar1.Min = 0
f.ProgressBar1.Max = Range("a" & Rows.Count).End(xlUp).Row
f.ProgressBar1.Value = 0
End Sub
Sub IncrementProgressBar()
f.ProgressBar1.Value = f.ProgressBar1.Value + 1
End Sub
you need to add some sort of reference to your current row number in PDFcopy() sub. then count the total amount of loops to be completed. and finally, work out the percentage to pass to the progress bar!
Sub PDFcopy()
Dim R As Range
Dim I as long
Dim Total as long
Dim SourcePath As String, DestPath As String, FName As String
'Setup source and dest path (Note: must have a trailing backslash!)
SourcePath = "G:\test-copyfrom\" 'choose directory to copy from
DestPath = "C:\test-copyto\" 'choose directory to copy to
'Visit each used cell in column B
I = 0
Total = Range("B" & Rows.Count).End(xlUp)
For Each R In Range("B1", Range("B" & Rows.Count).End(xlUp))
'Search the file with the file mask from the cell (Note: can contain wildcards like *.xls)
FName = Dir(SourcePath & R.Value & ".pdf")
'Loop while files found
Do While FName <> ""
'Copy the file
FileCopy SourcePath & FName, DestPath & FName
'Search the next file
FName = Dir()
Loop
I = I + 1
call progress(I/(total/100))
Next
MsgBox ("files copied")
I need to merge two Excel files, but only certain columns from each. I need to use a userform to select the two files to merge and then also use column mapping to select which columns from each sheet need appear where in the new output sheet.
So far I have this.
Private Sub AddFilesButton_Click()
Dim arrFiles As Variant
On Error GoTo ErrMsg
'Let the user choose the files they want to merge
#If Mac Then
arrFiles = Select_File_Or_Files_Mac()
#Else
arrFiles = Application.GetOpenFilename("Excel Files (*.xls; *.xlsx), *.xls;*.xlsx", 1, "Choose Excel Files", "Select", True)
#End If
If IsNull(arrFiles) Or UBound(arrFiles) = -1 Then
MsgBox "Please choose at least one Excel file"
Else
For Each file In arrFiles
FilesListBox.AddItem file
Next file
MergeButton.Enabled = True
End If
ErrMsg:
If Err.Number <> 0 Then
MsgBox "There was an error. Please try again. [" & Err.Description & "]"
End If
End Sub
Private Sub CancelButton_Click()
Unload Me
End Sub
Private Sub MergeButton_Click()
Dim fileName As Variant
Dim wb As Workbook
Dim s As Sheet1
Dim thisSheet As Sheet1
Dim lastUsedRow As Range
Dim columnMap As Collection
Dim filePath As Variant
Dim dataRange As Range
Dim insertAtRowNum As Integer
Dim outColName As String
Dim colName As String
Dim fromRange As String
Dim fromRangeToCopy As Range
Dim toRange As String
On Error GoTo ErrMsg
Application.ScreenUpdating = False
Set thisSheet = ThisWorkbook.ActiveSheet
For i = 0 To FilesListBox.ListCount - 1
fileName = FilesListBox.List(i, 0)
'Get the map of columns for this file
Set columnMap = MapColumns(fileName)
'Open the spreadsheet in ReadOnly mode
Set wb = Application.Workbooks.Open(fileName, ReadOnly:=True)
For Each sourceSheet In wb.Sheets
'Get the used range (i.e. cells with data) from the opened spreadsheet
If firstRowHeaders And i > 0 Then 'Only include headers from the first spreadsheet
Dim mr As Integer
mr = wb.ActiveSheet.UsedRange.Rows.Count
Set dataRange = wb.ActiveSheet.UsedRange.Offset(1, 0).Resize(mr - 1)
Else
Set dataRange = wb.ActiveSheet.UsedRange
End If
For Each col In dataRange.Columns
'Get corresponding output column. Empty string means no mapping
colName = GetColName(col.Column)
outColName = GetOutputColumn(columnMap, colName)
If outColName <> "" Then
fromRange = colName & 1 & ":" & colName & dataRange.Rows.Count
Set fromRangeToCopy = dataRange.Range(fromRange)
fromRangeToCopy.Copy
toRange = outColName & insertAtRowNum & ":" & outColName & (insertAtRowNum + fromRangeToCopy.Rows.Count - 1)
thisSheet.Range(toRange).PasteSpecial
End If
Next col
insertAtRowNum = insertAtRowNum + dataRange.Rows.Count
Next sourceSheet
Application.CutCopyMode = False
Next i
ThisWorkbook.Save
Set wb = Nothing
#If Mac Then
'Do nothing. Closing workbooks fails on Mac for some reason
#Else
'Close the workbooks except this one
Dim file As String
For i = 0 To FilesListBox.ListCount - 1
file = FilesListBox.List(i, 0)
file = Right(file, Len(file) - InStrRev(file, Application.PathSeparator, , 1))
Workbooks(file).Close SaveChanges:=False
Next i
#End If
Application.ScreenUpdating = True
Unload Me
ErrMsg:
If Err.Number <> 0 Then
MsgBox "There was an error. Please try again. [" & Err.Description & "]"
End If
End Sub
Function MapColumns(fileName As Variant) As Object
Dim colMap As New Collection
Select Case fileName
Case "ExcelFile1.xlsx"
colMap.Add Key:="C", Item:="A"
colMap.Add Key:="D", Item:="B"
colMap.Add Key:="E", Item:="C"
colMap.Add Key:="I", Item:="D"
Case "ExcelFile2.xlsx"
colMap.Add Key:="B", Item:="F"
colMap.Add Key:="J", Item:="G"
colMap.Add Key:="H", Item:="H"
colMap.Add Key:="C", Item:="I"
End Select
Set MapColumns = colMap
End Function
Function GetOutputColumn(columnMap As Collection, col As String) As String
Dim outCol As String
outCol = ""
If columnMap.Count > 0 Then
outCol = columnMap.Item(col)
End If
GetOutputColumn = outCol
End Function
'From: http://www.mrexcel.com/forum/excel-questions/16444-getting-column-name-given-column-number.html
Function GetColName(ColumnNumber)
FuncRange = Cells(1, ColumnNumber).AddressLocal(False, False) 'Creates Range (defaults Row to 1) and retuns Range in xlA1 format
FuncColLength = Len(FuncRange) 'finds length of range reference
GetColName = Left(FuncRange, FuncColLength - 1) 'row always "1" therefore take 1 away from string length and you are left with column ref
End Function
'From: http://msdn.microsoft.com/en-us/library/office/hh710200%28v=office.14%29.aspx#odc_xl4_ta_ProgrammaticallySelectFileforMac_DifferencesWindowsandMac
Function Select_File_Or_Files_Mac() As Variant
Dim MyPath As String
Dim MyScript As String
Dim MyFiles As String
Dim MySplit As Variant
Dim N As Long
Dim Fname As String
Dim mybook As Workbook
On Error Resume Next
MyPath = MacScript("return (path to documents folder) as String")
'Or use MyPath = "Macintosh HD:Users:Ron:Desktop:TestFolder:"
' In the following statement, change true to false in the line "multiple
' selections allowed true" if you do not want to be able to select more
' than one file. Additionally, if you want to filter for multiple files, change
' {""com.microsoft.Excel.xls""} to
' {""com.microsoft.excel.xls"",""public.comma-separated-values-text""}
' if you want to filter on xls and csv files, for example.
MyScript = _
"set applescript's text item delimiters to "","" " & vbNewLine & _
"set theFiles to (choose file of type " & _
" {""com.microsoft.Excel.xls"",""org.openxmlformats.spreadsheetml.sheet""} " & _
"with prompt ""Please select a file or files"" default location alias """ & _
MyPath & """ multiple selections allowed true) as string" & vbNewLine & _
"set applescript's text item delimiters to """" " & vbNewLine & _
"return theFiles"
MyFiles = MacScript(MyScript)
On Error GoTo 0
MySplit = False 'Assume no files = cancel
If MyFiles <> "" Then
With Application
.ScreenUpdating = False
.EnableEvents = False
End With
MySplit = Split(MyFiles, ",")
With Application
.ScreenUpdating = True
.EnableEvents = True
End With
End If
Select_File_Or_Files_Mac = MySplit
End Function
Function bIsBookOpen(ByRef szBookName As String) As Boolean
' Contributed by Rob Bovey
On Error Resume Next
bIsBookOpen = Not (Application.Workbooks(szBookName) Is Nothing)
End Function
I am using VBA in Autocad in order to count blocks in drawings.
With some search through the internet and some tries I have managed to complete the following code and count all blocks in any drawing, or by layer or the selected ones.
Sub BlockCount_Test()
dispBlockCount "COUNT_ALL"
dispBlockCount "COUNT_BY_LAYER"
dispBlockCount "COUNT_BY_FILTER"
End Sub
Sub dispBlockCount(ByVal strAction As String)
On Error Resume Next
Dim objBlkSet As AcadSelectionSet
Dim objBlkRef As AcadBlockReference
Dim strBlkNames() As String
Dim iGpCode(0) As Integer
Dim vDataVal(0) As Variant
Dim iSelMode As Integer
Dim iBlkCnt As Integer
iGpCode(0) = 0
vDataVal(0) = "INSERT"
iSelMode = 0 '|-- Selection Modes (0 = Select All, 1 = Select On Screen) --|
Set objBlkSet = getSelSet(iGpCode, vDataVal, iSelMode)
If objBlkSet.Count <> 0 Then
Select Case strAction
Case "COUNT_ALL"
ReDim strBlkNames(objBlkSet.Count - 1)
iBlkCnt = 0
For Each objBlkRef In objBlkSet
strBlkNames(iBlkCnt) = objBlkRef.Name
iBlkCnt = iBlkCnt + 1
Next
MsgBox getUniqBlockCount(strBlkNames), , "Count All"
Case "COUNT_BY_LAYER"
Dim objCadEnt As AcadEntity
Dim vBasePnt As Variant
ThisDrawing.Utility.GetEntity objCadEnt, vBasePnt, "Pick a block reference:"
If Err.Number <> 0 Then
MsgBox "No block references selected."
objBlkSet.Delete
Exit Sub
Else
If objCadEnt.ObjectName = "AcDbBlockReference" Then
Dim objCurBlkRef As AcadBlockReference
Dim strLyrName As String
iBlkCnt = 0
Set objCurBlkRef = objCadEnt
strLyrName = objCurBlkRef.Layer
For Each objBlkRef In objBlkSet
If StrComp(objBlkRef.Layer, strLyrName, vbTextCompare) = 0 Then
ReDim Preserve strBlkNames(iBlkCnt)
strBlkNames(iBlkCnt) = objBlkRef.Name
iBlkCnt = iBlkCnt + 1
End If
Next
MsgBox getUniqBlockCount(strBlkNames), , "Count by Layer"
Else
ThisDrawing.Utility.prompt "The selected object is not a block reference."
End If
End If
Case "COUNT_BY_FILTER"
Dim strFilter As String
iBlkCnt = 0
strFilter = ThisDrawing.Utility.GetString(False, "Enter a filter option:")
If strFilter <> "" Then
For Each objBlkRef In objBlkSet
If UCase(objBlkRef.Name) Like UCase(strFilter) Then
ReDim Preserve strBlkNames(iBlkCnt)
strBlkNames(iBlkCnt) = objBlkRef.Name
iBlkCnt = iBlkCnt + 1
End If
Next
MsgBox getUniqBlockCount(strBlkNames), , "Count by Filter"
Else
ThisDrawing.Utility.prompt "Search criteria should not be empty."
End If
Case Else
ThisDrawing.Utility.prompt "Invalid action mode."
End Select
Else
ThisDrawing.Utility.prompt "No block references were found."
End If
objBlkSet.Delete
If Err.Number <> 0 Then
ThisDrawing.Utility.prompt Err.Description
End If
End Sub
Function getSelSet(ByRef iGpCode() As Integer, vDataVal As Variant, iSelMode As Integer) As AcadSelectionSet
Dim objSSet As AcadSelectionSet
Set objSSet = ThisDrawing.SelectionSets.Add("EntSet")
Select Case iSelMode
Case 0
objSSet.Select acSelectionSetAll, , , iGpCode, vDataVal
Case 1
ReSelect:
objSSet.SelectOnScreen iGpCode, vDataVal
If objSSet.Count = 0 Then
Dim iURep As Integer
iURep = MsgBox("No entities selected, Do you want to select again?", _
vbYesNo, "Select Entity")
If iURep = 6 Then GoTo ReSelect
objSSet.Delete
Set getSelSet = Nothing
Exit Function
End If
Case Else
ThisDrawing.Utility.prompt "Invalid selection mode...."
End Select
Set getSelSet = objSSet
End Function
Function getUniqBlockCount(ByRef strBlkNames() As String) As String
Dim strUniqBlkNames() As String
Dim iBlkCount() As Integer
Dim iArIdx1, iArIdx2 As Integer
iArIdx1 = 0: iArIdx2 = 0
For iArIdx1 = LBound(strBlkNames) To UBound(strBlkNames)
If iArIdx1 = 0 Then
ReDim strUniqBlkNames(iArIdx2)
strUniqBlkNames(iArIdx2) = strBlkNames(iArIdx1)
iArIdx2 = iArIdx2 + 1
End If
Dim iUnqArIdx As Integer
Dim blUniq As Boolean
blUniq = True
For iUnqArIdx = LBound(strUniqBlkNames) To UBound(strUniqBlkNames)
If StrComp(strBlkNames(iArIdx1), strUniqBlkNames(iUnqArIdx), vbTextCompare) = 0 Then
blUniq = False
Exit For
End If
Next
If blUniq Then
ReDim Preserve strUniqBlkNames(iArIdx2)
strUniqBlkNames(iArIdx2) = strBlkNames(iArIdx1)
iArIdx2 = iArIdx2 + 1
End If
Next
iArIdx1 = 0: iArIdx2 = 0
For iArIdx1 = LBound(strUniqBlkNames) To UBound(strUniqBlkNames)
For iArIdx2 = LBound(strBlkNames) To UBound(strBlkNames)
If StrComp(strBlkNames(iArIdx2), strUniqBlkNames(iArIdx1), vbTextCompare) = 0 Then
ReDim Preserve iBlkCount(iArIdx1)
iBlkCount(iArIdx1) = iBlkCount(iArIdx1) + 1
End If
Next
Next
For iUnqArIdx = LBound(iBlkCount) To UBound(iBlkCount)
strUniqBlkNames(iUnqArIdx) = strUniqBlkNames(iUnqArIdx) & vbTab & vbTab & vbTab & iBlkCount(iUnqArIdx) & vbCrLf
Next
Dim strTitle, strBlkCount As String
strBlkCount = Join(strUniqBlkNames)
strTitle = "Block Name" & vbTab & vbTab & "Count" & vbCrLf
strTitle = strTitle & String(14, "-") & vbTab & vbTab & String(8, "-") & vbCrLf
getUniqBlockCount = strTitle & strBlkCount
End Function
My aim is to take these block numbers and insert them automatically in an excel sheet and in a certain sheet and cells.
Can someone help me find a solution to this problem?
I somehow managed to call an excel sheet but I am currently lost on how to put the block counts in the right position.
i.e. Let's say that I want them in a list as they present on the table I get from the count in my code, how could I achieve this?
P.S. I am new here and if you need any more info I would gladly add any more information needed in order to find a solution.
Thanks in advance
Georgia
I don't use AutoCad VBA myself, but based on the simple nature of your question, my guess is that this may help you on the road:
If you want to create a new Excel application:
Dim oApp_Excel as Excel.Application
Dim oBook as Excel.workbook
Set oApp_Excel = CreateObject("EXCEL.APPLICATION")
set oBook = oApp_Excel.workbooks.add
oBook.sheets("<Name>").cells(<Counter>, <Column_Number>).value = <BlockNr (based on counter)>
oBook.SaveAs(<Path>)
oBook.close
oApp_Excel.quit
set oBook = nothing
You can place the values in any cell or form you want; these are the basics of Excel VBA.
Another way is to load you BlockNumbers in an array first (in your current code) and then filling in values. This way you can set a range dynamically and load all the data from the array into the range at once.
I hope that I didn't misunderstand your question and that my reply serves your purpose.
'Create new excel instance.
Set excelApp = CreateObject("Excel.Application")
If err <> 0 Then
MsgBox "Could not start Excel!", vbExclamation, "Warning"
End
Else
excelApp.Visible = True
excelApp.ScreenUpdating = False
'Add a new workbook and set the objects.
Set wkbObj = excelApp.Workbooks.Add(1)
Set shtObj = excelApp.Worksheets(1)
shtObj.Name = "Measured Polylines"
With shtObj.Range("A1:D1")
.Font.Bold = True
.Autofilter
End With