First time I ask a question here, but so far answers from this forum have always helped me a lot. However, now I have been dealing with a problem for weeks and unfortunately never found sufficient answers.
The current task is to update an already existing macro regarding data processing and display to a newer UI and increased usability. However, in the same sense I would like to improve the speed of the macro.
My current problem is opening, loading, splitting and pasting multiple selected .txt files into appropriately named sheets. The files can very quickly have over a hundred thousand entries of data, all separated by either space or enter. For importing and splitting a single file, I stumbled across this code, which I have subsequently adapted for my circumstances:
Private Sub testModule1()
Dim arr, tmp, output
Dim Datei
Dim FSO
Dim x, y As Integer
Dim str_string, filePath As String
Set FSO = CreateObject("Scripting.FilesystemObject")
filePath = Application.GetOpenFilename
Set Datei = FSO.OpentextFile(filePath)
str_string = Datei.readall
Datei.Close
arr = Split(str_string, vbCrLf)
ReDim output(UBound(arr), 50)
For x = 0 To UBound(arr)
tmp = Split(arr(x), " ")
For y = 0 To UBound(tmp)
output(x, y) = tmp(y)
Next
Next
Sheets("Sheet1").Range("A1").Resize(UBound(output) + 1, UBound(output, 2)) = output
End Sub
This part lets me select a single file, splits the cells as desired, and finally posts it to the first sheet.
For importing multiple files and naming the sheet after the file name, I found code here (the first solution):
https://www.mrexcel.com/board/threads/importing-multiple-text-files-in-to-multiple-work-sheets-with-text-file-names.1147363/
However, it currently opens in a new workbook, Windows tells me several times that data is overwritten when I do this, and the delimiter is also limited to only one character.
I am currently failing to find a reasonable combination of these two actions. The direct opening and reasonable splitting of several selected files and the subsequent integrating into several sheets named accordingly by file name.
In the old version of the macro, all file paths were first retrieved and stored in cells for this purpose, and later looped through these cells while reading and integrating the individual data in the process. However, everything in a sheet and rather, as I find, cumbersome.
I hope to find a more elegant solution for this problem than having to store data in sheets during editing and I am also happy about other suggestions and solutions.
EDIT:
After the hint from Solar Mike I was able to adapt the code to this:
Private Sub testModule2()
Dim fDialog As FileDialog
Dim fPath As Variant
Dim FSO
Dim Datei
Dim arr, tmp, output
Dim file, fileName As String
Dim x, y As Integer
Dim newSht As Worksheet
Application.ScreenUpdating = False
Set fDialog = Application.FileDialog(msoFileDialogFilePicker)
With fDialog
.AllowMultiSelect = True
.Title = "Please select files to import"
.Filters.Clear
.Filters.Add "VBO Files", "*.vbo"
If .Show = True Then
For Each fPath In .SelectedItems
Set FSO = CreateObject("Scripting.FilesystemObject")
fileName = FSO.GetFilename(fPath)
Set Datei = FSO.OpentextFile(fPath)
file = Datei.readall
Datei.Close
arr = Split(file, vbCrLf)
ReDim output(UBound(arr), 50)
For x = 0 To UBound(arr)
tmp = Split(arr(x), " ")
For y = 0 To UBound(tmp)
output(x, y) = tmp(y)
Next
Next
Set newSht = ActiveWorkbook.Sheets.Add(after:=ActiveWorkbook.Worksheets(ActiveWorkbook.Worksheets.Count))
newSht.Name = fileName
Sheets(fileName).Range("A1").Resize(UBound(output) + 1, UBound(output, 2)) = output
Next
End If
End With
Application.ScreenUpdating = True
End Sub
This does what is actually required, but just importing only 5 files already takes about a minute. Since on average up to 20 files can/should be imported and the processing of the data still takes place afterwards, this still seems a bit much to me.
It should be noted that the data sets are reduced again during processing, something between 40 and 80% of the data are filtered. Unfortunately I don't have the expertise to do this before, even if this would reduce the loading time.
Does this do what you want?
Sub CombineTextFiles()
'updateby Extendoffice
Dim xFilesToOpen As Variant
Dim I As Integer
Dim xWb As Workbook
Dim xTempWb As Workbook
Dim xDelimiter As String
Dim xScreen As Boolean
On Error GoTo ErrHandler
xScreen = Application.ScreenUpdating
Application.ScreenUpdating = False
xDelimiter = "|"
xFilesToOpen = Application.GetOpenFilename("Text Files (*.txt), *.txt", , "Kutools for Excel", , True)
If TypeName(xFilesToOpen) = "Boolean" Then
MsgBox "No files were selected", , "Kutools for Excel"
GoTo ExitHandler
End If
I = 1
Set xTempWb = Workbooks.Open(xFilesToOpen(I))
xTempWb.Sheets(1).Copy
Set xWb = Application.ActiveWorkbook
xTempWb.Close False
xWb.Worksheets(I).Columns("A:A").TextToColumns _
Destination:=Range("A1"), DataType:=xlDelimited, _
TextQualifier:=xlDoubleQuote, _
ConsecutiveDelimiter:=False, _
Tab:=False, Semicolon:=False, _
Comma:=False, Space:=False, _
Other:=True, OtherChar:="|"
Do While I < UBound(xFilesToOpen)
I = I + 1
Set xTempWb = Workbooks.Open(xFilesToOpen(I))
With xWb
xTempWb.Sheets(1).Move after:=.Sheets(.Sheets.Count)
.Worksheets(I).Columns("A:A").TextToColumns _
Destination:=Range("A1"), DataType:=xlDelimited, _
TextQualifier:=xlDoubleQuote, _
ConsecutiveDelimiter:=False, _
Tab:=False, Semicolon:=False, _
Comma:=False, Space:=False, _
Other:=True, OtherChar:=xDelimiter
End With
Loop
ExitHandler:
Application.ScreenUpdating = xScreen
Set xWb = Nothing
Set xTempWb = Nothing
Exit Sub
ErrHandler:
MsgBox Err.Description, , "Kutools for Excel"
Resume ExitHandler
End Sub
Unfortunately, after a bit more trial and error with my edited code, I haven't found a better way to optimize the process, so I'm posting the final code in response to the question.
The reduction of the amount of data is now done by a third party program and speeds up the processing sufficiently.
Option Explicit
Public Sub fileImporter()
Dim fDialog As FileDialog
Dim fPath As Variant
Dim FSO
Dim Data
Dim arr, tmp, output
Dim file, fileName As String
Dim x, y As Integer
Dim newSht As Worksheet
Application.ScreenUpdating = False
Set fDialog = Application.FileDialog(msoFileDialogFilePicker)
With fDialog
.AllowMultiSelect = True
.Title = "Please select files to import"
.Filters.Clear
.Filters.Add "VBO Files", "*.vbo" 'VBO Files are opened and handled like Text Files
If .Show = True Then
For Each fPath In .SelectedItems
Set FSO = CreateObject("Scripting.FilesystemObject")
fileName = FSO.GetFilename(fPath)
Set Data = FSO.OpentextFile(fPath)
file = Data.readall
Data.Close
arr = Split(file, vbCrLf)
ReDim output(UBound(arr), 50)
For x = 0 To UBound(arr)
tmp = Split(arr(x), " ")
For y = 0 To UBound(tmp)
output(x, y) = tmp(y)
Next
Next
Set newSht = ActiveWorkbook.Sheets.Add(after:=ActiveWorkbook.Worksheets(ActiveWorkbook.Worksheets.Count))
newSht.Name = fileName
Sheets(fileName).Range("A1").Resize(UBound(output) + 1, UBound(output, 2)) = output
Next
End If
End With
Application.ScreenUpdating = True
End Sub
Thanks for all the help and I hope this can help someone else too.
Related
I'm trying to reduce redundancy in my macros but I'm struggling with getting an element from a jagged array and using it elsewhere.
The premise is a single workbook with many sheets being split by groups of sheet names into new documents that I can then send to the process owners so they only get their own data.
Previously I was selecting sheet names listed explicitly and pasting to a new document that was named explicitly, but I had to run 10 separate almost identical macros to do that and I have heard of select being a bad choice in many instances as well.
Below is my latest attempt, the first issue is at the printOut line I get a Type Mismatch.
Sub CopyOut()
Dim printOut, groupNames, Group1, groupArray() As Variant
Dim n, j As Long
Dim reNamed, fileName As String
Dim ws As Worksheet
Dim wb1, wb2 As Workbook
groupNames = Array("Group 1", "Group 2", "Group 3", "Group 4") 'other arrays left off for length
Group1 = Array("FA_1A Report", "FA_1A", "FA_2ACS Report", "FA_2ACS", "FA_2BCS Report", "FA_2BCS", "FANUCMED Report", "FANUCMED", "FA_RRTP1 Report", "FA_RRPT1")
groupArray = Array(groupNames, Group1)
For n = 1 To UBound(groupArray)
fileName = "CS Data Sheet" & " " & Format(Date, "mmmyy") & "-" & groupArray(n - n)(n - 1) & ".xlsm" 'concat file name string. this is not just tacked on the end of reName because i use it on it's own later
reNamed = "C:\Users\xx\Desktop\" & fileName 'concat save location string
Set wb1 = ThisWorkbook
Set wb2 = Workbooks.Add 'create a new workbook, wb2
wb2.SaveAs fileName:=reNamed, FileFormat:=xlOpenXMLWorkbookMacroEnabled 'save with that name and location
printOut = Join(Application.Index(groupArray, n, 0), ",")
wb1.Sheets(printOut).Copy Before:=Workbooks(fileName).Sheets(1) 'copy the sheets for the group and paste into the newly created document
Next
End Sub
If I nix printOut altogether and put in a specific worksheet name instead it does work for just that one sheet (of course) but I need it to copy multiple to each new document.
I have also tried:
For n = 1 To UBound(groupArray)
...
for j= LBound(groupArray(n)) To UBound(groupArray(n))
wb1.Sheets(groupArray(n)(j)).Copy Before:=Workbooks(fileName).Sheets(1)
next
next
to iterate through the subarray and copy a sheet at a time, but it gives subscript out of range. With this version I tried various methods of making the groupArray(n)(j) value into a string or into a "worksheet" type to set as a variable and use the variable in the sheets().copy, to no avail.
Any idea where I could be going wrong?
thanks so much
EDIT:
I got my above code working by wrapping it in split (was trying to use printOut as an array when it was only a string) and fixing the arguments of Index as below, however the resulting code still needs work, since if a sheet is missing it won't run.
printOut = Split(Join(Application.Index(groupArray(n), 1, 0), ","), ",")
In my experience, if you find yourself hard-coding values like sheet names, group names, and other data directly in your code it tends to become difficult to maintain. Adding more groups, or re-shuffling the sheets in each group becomes problematic. My recommendation is to create a (possibly hidden) worksheet that maps your worksheet names into groups. Then you have a small set of code that operates directly on that.
My example data is set up like this:
Next, in its own code module, I created a few methods to work directly with this group map data. The main idea here is to move the group map data into a memory-based array. While in general I rarely use module-level global variables, I have one in this example to illustrate how to work with the data by only reading it into the array once every time the macro is executed.
(These are Subs and Functions. For my own code, I likely would have created a VBA class to handle the data in an object-oriented way.)
So there is a Private Sub to get the data:
Option Explicit
Private groupData As Variant
Private Sub GetGroupData()
Const GROUP_WS_NAME As String = "GroupMap"
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets(GROUP_WS_NAME)
Dim lastRow As Long
Dim lastCol As Long
With ws
'--- how many columns of groups?
lastCol = .Cells(1, .Columns.Count).End(xlToLeft).Column
lastRow = .UsedRange.Find("*", , , , xlByRows, xlPrevious).Row
groupData = .Range("A1").Resize(lastRow, lastCol).Value
End With
End Sub
Now it's easy to figure out how many groups there are:
Public Function NumberOfGroups() As Long
If IsEmpty(groupData) Then GetGroupData
NumberOfGroups = UBound(groupData, 2)
End Function
And how many items in a particular group:
Public Function NumberInGroup(ByVal groupNumber As Long)
If IsEmpty(groupData) Then GetGroupData
'--- count the number of array values that have data
Dim i As Long
For i = LBound(groupData, 1) To UBound(groupData, 1)
If groupData(i, groupNumber) = vbNullString Then
'--- we found the first empty cell in this array, we're done
Exit For
Else
NumberInGroup = NumberInGroup + 1
End If
Next i
'--- subtract one to discount the header value
NumberInGroup = NumberInGroup - 1
End Function
The easiest of all is getting the value of any group:
Public Function GetGroupValue(ByVal groupNumber As Long, _
ByVal groupIndex As Long) As Variant
If IsEmpty(groupData) Then GetGroupData
'--- always add one to the index to account for the header value
GetGroupValue = groupData(groupIndex + 1, groupNumber)
End Function
Notice the check for If IsEmpty(groupData) Then GetGroupData at the beginning of each method. This makes sure the groupData array is always loaded if necessary.
This example gives it a quick test (in a different code module):
Option Explicit
Sub test()
Dim totalGroups As Long
totalGroups = NumberOfGroups()
Dim i As Long
Dim j As Long
For i = 1 To totalGroups
Dim totalInGroup As Long
totalInGroup = NumberInGroup(i)
For j = 1 To totalInGroup
Debug.Print "group " & i & " = " & GetGroupValue(i, j)
Next j
Next i
End Sub
Here's the whole group data code module in a single block:
Option Explicit
Private groupData As Variant
Private Sub GetGroupData()
Const GROUP_WS_NAME As String = "GroupMap"
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets(GROUP_WS_NAME)
Dim lastRow As Long
Dim lastCol As Long
With ws
'--- how many columns of groups?
lastCol = .Cells(1, .Columns.Count).End(xlToLeft).Column
lastRow = .UsedRange.Find("*", , , , xlByRows, xlPrevious).Row
groupData = .Range("A1").Resize(lastRow, lastCol).Value
End With
End Sub
Public Function NumberOfGroups() As Long
If IsEmpty(groupData) Then GetGroupData
NumberOfGroups = UBound(groupData, 2)
End Function
Public Function NumberInGroup(ByVal groupNumber As Long)
If IsEmpty(groupData) Then GetGroupData
'--- count the number of array values that have data
Dim i As Long
For i = LBound(groupData, 1) To UBound(groupData, 1)
If groupData(i, groupNumber) = vbNullString Then
'--- we found the first empty cell in this array, we're done
Exit For
Else
NumberInGroup = NumberInGroup + 1
End If
Next i
'--- subtract one to discount the header value
NumberInGroup = NumberInGroup - 1
End Function
Public Function GetGroupValue(ByVal groupNumber As Long, ByVal groupIndex As Long) As Variant
If IsEmpty(groupData) Then GetGroupData
'--- always add one to the index to account for the header value
GetGroupValue = groupData(groupIndex + 1, groupNumber)
End Function
If I got this right, you have one master workbook with n sheets and you want to group some of them, then create a new workbook for each group and paste in its assigned sheets.
I think an approach where you keep a "config" file in your master workbook for setting up groups and sheets, is more suitable rather than editing into code. Example:
The below code will create a file using the names from column A and copy all the sheets defined on their respective row.
Option Explicit
Sub CopyOut()
Dim groupArr() As Variant
Dim wb2 As Workbook
Dim lastRow As Long, lastCol As Long, highestNumOfSheets As Long, i As Long, j As Long, arrColumns As Long
Dim reNamed As String, fileName As String, configSheet As String
Dim removedSheet1 As Boolean
' Modify the sheet name here
configSheet = "config"
' Build an array from sheet defined groups
With ThisWorkbook.Worksheets(configSheet)
lastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
For i = 2 To lastRow
lastCol = .Cells(i, .Columns.Count).End(xlToLeft).Column
If lastCol > highestNumOfSheets Then highestNumOfSheets = lastCol
Next i
groupArr = .Range(.Cells(2, 1), .Cells(lastRow, highestNumOfSheets)).Value2
End With
Application.ScreenUpdating = False
For i = LBound(groupArr) To UBound(groupArr)
fileName = "CS Data Sheet " & Format(Date, "mmmyy") & "-" & groupArr(i, 1) & ".xlsm"
reNamed = Environ("UserProfile") & "\Desktop\" & fileName
removedSheet1 = False ' Reset this on each new workbook created
Set wb2 = Workbooks.Add
' Pick all the sheet names for the current group
For j = 2 To UBound(groupArr, 2)
' Skip empty values from array (if it's the case) and skip missing sheets
If Trim(groupArr(i, j)) <> vbNullString And SheetExists(groupArr(i, j)) Then
ThisWorkbook.Worksheets(groupArr(i, j)).Copy Before:=wb2.Worksheets(1)
' Remove Sheet1 from the new Workbook
If removedSheet1 = False Then
With Application
.DisplayAlerts = False
wb2.Worksheets("Sheet1").Delete
removedSheet1 = True
.DisplayAlerts = True
End With
End If
End If
Next j
' Here you might need an error handler if you think you're going to run the macro multiple times in the same day
' If the file exists already this will throw an error
' A quick lazy way is to add time (including seconds) when you define the file name above
wb2.SaveAs fileName:=reNamed, FileFormat:=xlOpenXMLWorkbookMacroEnabled
wb2.Close
If Not wb2 Is Nothing Then Set wb2 = Nothing
Next i
Application.ScreenUpdating = True
End Sub
Function SheetExists(ByVal sheetName As String) As Boolean
Dim ws As Worksheet
On Error Resume Next
Set ws = ThisWorkbook.Worksheets(sheetName)
On Error GoTo 0
If Not ws Is Nothing Then
SheetExists = True
Set ws = Nothing
End If
End Function
Of course it can be tweaked around, with error handling and other checks (depending on what you want to achieve entirely) but it should give you an alternative view of your code.
EDIT: Added a function to check if sheet exists.
Scenario: I have a workbook with multiple worksheets. I am trying to use a function (called within a sub) to export arrays with data from certain worksheets. The arrays are created before the function with the content from the worksheet with:
If ws.Name = "AA" Then
expaa = ws.UsedRange.Value
End if
where expaa is previously defined as variant.
The function I am using apparently finishes running, but the output on the new file saved is weird: instead of having one row of headers, the first row is split into 2 for some reason (all the others remain the same).
This is the function I am using:
Function Exporter(arr As Variant, y As String, OutPath As String) As Variant
Dim lrow As Long, lColumn As Long
Dim w2 As Workbook
Dim d As Date
Workbooks.Add
Set w2 = ActiveWorkbook
w2.Worksheets(1).Range("A1").Resize(UBound(arr, 1), UBound(arr, 2)) = arr
Application.DisplayAlerts = False
w2.SaveAs Filename:=OutPath & "\" & y, FileFormat:=6
Application.DisplayAlerts = True
w2.Close True
End Function
Which I call from the main sub with:
If aa_name <> "" Then
Exporter expaa , "aa_OK", wbpath
End If
where aa_name is the name of the file used to retrieve the path.
Obs: The wbpath variable is a string with the path of my main workbook (therefore the new file is saved at the same location).
Question: What may be causing the first row of my output to be split? How can that be fixed?
Obs2: I know this can be done with copy procedure, and looping through the array and so on. I even got it to work with other methods. This post is only to understand what I am doing wrong with the current code.
Obs3: Regarding the data that is going to be passed: it is a matrix of days, identifiers and data, ex:
Item1 Item2 Item3
01/01/2000 1 1 2
02/01/2000 1 2 1
03/01/2000 2 2 2
with around 2000 rows and 3000 columns.
UPDATE: After retesting the code multiple times, It appears that the data of the first row only gets split when the file is save as csv (when the array is pasted, the output is normal). Any idea on what may be the cause for that?
I know this is old but here is my solution for the googlers. This accepts an array and creates a CSV at a path you define. Its probably not perfect but it has worked so far for me.
Option Explicit
Option Private Module
Public Function SaveTextToFile(ByVal targetarray As Variant, ByVal filepath As String) As Boolean
On Error GoTo CouldNotMakeFile
Dim fso As FileSystemObject
Set fso = New FileSystemObject
Dim fileStream As TextStream
' Here the actual file is created and opened for write access
Set fileStream = fso.CreateTextFile(filepath)
' Write something to the file
Dim Row As Long, Col As Long
For Row = LBound(targetarray, 1) To UBound(targetarray, 1)
For Col = LBound(targetarray, 2) To UBound(targetarray, 2)
fileStream.Write StringCompliance(targetarray(Row, Col)) & IIf(Col = UBound(targetarray, 2), "", ",")
Next Col
fileStream.WriteBlankLines 1
Next Row
' Close it, so it is not locked anymore
fileStream.Close
' Here is another great method of the FileSystemObject that checks if a file exists
If fso.FileExists(filepath) Then
SaveTextToFile = True
End If
CouldNotMakeFile:
End Function
Private Function StringCompliance(ByVal InputString As String) As String
Dim CurrentString As String
CurrentString = InputString
'Test if string has qoutes
If InStr(CurrentString, Chr$(34)) > 0 Then
CurrentString = Chr$(34) & Replace(CurrentString, Chr$(34), Chr$(34) & Chr$(34)) & Chr$(34)
StringCompliance = True
Else
'Tets if string has commas or line breaks
If InStr(CurrentString, ",") > 0 Or InStr(CurrentString, vbLf) > 0 Then
CurrentString = Chr$(34) & CurrentString & Chr$(34)
Else
StringCompliance = False
End If
End If
StringCompliance = CurrentString
End Function
I need to replace 108 images in word. I wrote VBA code so that for each inline image read in the document, the image will be replace by a new image. The new image is specified by a array that has filepaths in each element. The array comes from a text file.
For some reason, my code won't work if I have my variable, strPath as
strPath = dataArray(i)
or
strPath = Chr(34) & dataArray(i) & Chr(34)
What does work is if I type in
dataArray(0) = "C:\IMGS\G.2.1\NZ_DWH_v_SIMAP_AR1_biovalbox1_1100-1400m_Apr20-May26.png"
The path in the textfile is
C:\IMGS\G.2.1\NZ_DWH_v_SIMAP_AR1_biovalbox1_1100-1400m_Apr20-May26.png
I have 108 lines in the textfile, each for the image that needs to be replaced.
I have displayed the path in a message box and it looks like the above, so I am not sure why I can't get file paths from an array. Can someone help me?
'1-loop thru all figs
'2-bring up box to select figure
'3-add figure
Dim intChoice As Integer
Dim strPath As String
Dim objPic As InlineShape
Dim intCount As Integer
'import text
Dim dataArray() As String
Dim i As Integer
Dim g As Integer
strFileName = "C:\Users\cturner\Desktop\filesimgs_order.txt"
Open strFileName For Input As #1
dataArray = Split(Input$(LOF(1), #1), vbLf)
Close #1
g = 0
intCount = ActiveDocument.InlineShapes.Count
'loop through inline shapes
For i = 0 To intCount
strPath = Chr(34) & dataArray(i) & Chr(34)
MsgBox (TypeName(strPath))
g = g + 1
'check if valid filepath
'Debug.Print FileExists(strPath)
MsgBox strPath
'check if the current shape is an picture
If ActiveDocument.InlineShapes.Item(g).Type = wdInlineShapePicture Then
Set objPic = ActiveDocument.InlineShapes.Item(g)
objPic.Select
'insert the image
Selection.InlineShapes.AddPicture FileName:= strPath, _
LinkToFile:=False, SaveWithDocument:=True
End If
Next i
End Sub
Suprise Suprise, I forgot to trim the carriage return vbCr. After I got that out, code works fine and I am able to replace my buttload of images.
Part 1. I checked my string if it has carriage return. Main problem is that I didn't clean my string, and thus putting it into a function made vba throw the error message (no bueno). If you have it, UBound should = 1
For i = 0 To 2
strPath = dataArray(i)
checkcr = Split(strPath, vbCr)
firstIndex = LBound(checkcr)
lastIndex = UBound(checkcr)
MsgBox (firstIndex)
MsgBox (lastIndex)
MsgBox checkcr(lastIndex)
newstrPath = Replace(strPath, vbCr, "")
MsgBox newstrPath
Next i
Part 2. Corrected Code + bonus. Bouns: some code to check if the file exists (from here )
fix:
newstrPath = Replace(strPath, vbCr, "")
Updated Code:
Sub replacefigs()
' replacefigs Macro
' To replace old figures with new figures (updated disclosure)
'0-get full file paths from textfile
'1-loop thru all figs
'2-bring up box to select figure
'3-add figure
Dim intChoice As Integer
Dim strPath As String
Dim objPic As InlineShape
Dim intCount As Integer
'import text
Dim dataArray() As String
Dim i As Integer
Dim g As Integer
strFileName = "C:\Users\cturner\Desktop\filesimgs_order.txt"
Open strFileName For Input As #1
dataArray = Split(Input$(LOF(1), #1), vbLf)
Close #1
g = 0
intCount = ActiveDocument.InlineShapes.Count
'loop through inline shapes
For i = 0 To intCount
strPath = dataArray(i)
'Get rid of carriage returns
newstrPath = Replace(strPath, vbCr, "")
g = g + 1
'to check if file exists
'Debug.Print FileExists(newstrPath)
'check if the current shape is an picture
If ActiveDocument.InlineShapes.Item(g).Type = _
wdInlineShapePicture Then
Set objPic = ActiveDocument.InlineShapes.Item(g)
objPic.Select
'insert the image
Selection.InlineShapes.AddPicture FileName:= _
newstrPath, LinkToFile:=False, _
SaveWithDocument:=True
End If
Next i
End Sub
I have more than 50 files needed to create the pivot table and each file has the same exact formort with different contents. So far, I have finished creating the code for the pivot and it works very well when running alone, however, it failed when I tried to run the code for all workbooks in the same folder. I don't know what happened and why it kept showing that no files could be found despite nothing wrong about the pathname.
Sub DoAllFiles()
Dim Filename, Pathname As String
Dim WB As Workbook
Pathname = "D:\Reports"
Filename = Dir(Pathname & "\*.xls*")
Do While Filename <> ""
Application.DisplayAlerts = False
Application.ScreenUpdating = False
Set WB = Workbooks.Open(Pathname & Filename) 'open all files
PivotX WB
WB.Close SaveChanges:=True
Application.DisplayAlerts = True
Application.ScreenUpdating = True
Filename = Dir()
Loop
End Sub
Here is the code for pivot and it works very well when running it alone:
Sub PivotX(WB As Workbook)
Dim Lrow, Lcol As Long
Dim wsData As Worksheet
Dim rngRaw As Range
Dim PvtTabCache As PivotCache
Dim PvtTab As PivotTable
Dim wsPvtTab As Worksheet
Dim PvtFld As PivotField
Set wsData = ActiveSheet
Lrow = wsData.Cells(Rows.Count, "B").End(xlUp).Row
Lcol = wsData.Cells(1, Columns.Count).End(xlToLeft).Column
Set rngRaw = wsData.Range(Cells(1, 1), Cells(Lrow, Lcol))
Set wsPvtTab = Worksheets.Add
wsData.Select
ActiveWorkbook.PivotCaches.Create(SourceType:=xlDatabase, SourceData:=rngRaw, Version:=xlPivotTableVersion12).CreatePivotTable TableDestination:=wsPvtTab.Range("A3"), TableName:="PivotTable1", DefaultVersion:=xlPivotTableVersion12
Set PvtTab = wsPvtTab.PivotTables("PivotTable1")
PvtTab.ManualUpdate = True
Set PvtFld = PvtTab.PivotFields("Month")
PvtFld.Orientation = xlPageField
PvtTab.PivotFields("Month").ClearAllFilters
Set PvtFld = PvtTab.PivotFields("Year")
PvtFld.Orientation = xlPageField
PvtTab.PivotFields("Year").ClearAllFilters
Set PvtFld = PvtTab.PivotFields("Fund_Code")
PvtFld.Orientation = xlRowField
PvtFld.Position = 1
Set PvtFld = PvtTab.PivotFields("Curr")
PvtFld.Orientation = xlColumnField
PvtFld.Position = 1
wsPvtTab.PivotTables("PivotTable1").PivotFields("Curr").PivotItems("USD").Position = 1
With PvtTab.PivotFields("Trx_Amount")
.Orientation = xlDataField
.Function = xlSum
.NumberFormat = "#,##0;[red](#,##0)"
End With
wsPvtTab.PivotTables("Pivottable1").RowAxisLayout xlTabularRow
'Remove grand total
wsPvtTab.PivotTables("Pivottable1").RowGrand = False
For Each PvtTbCache In ActiveWorkbook.PivotCaches
On Error Resume Next
PvtTbCache.Refresh
Next PvtTbCache
'Determine filter value
Set PvtFld = PvtTab.PivotFields("Year")
PvtFld.ClearAllFilters
PvtFld.EnableMultiplePageItems = True
With PvtFld
.AutoSort xlmnual, .SourceName
For Each Pi In PvtFld.PivotItems
Select Case Pi.Name
Case "2014"
Case Else
Pi.Visible = False
End Select
Next Pi
.AutoSort xlAscending, .SourceName
End With
'determine filter value
Set PvtFld = PvtTab.PivotFields("Month")
PvtFld.ClearAllFilters
PvtFld.EnableMultiplePageItems = True
With PvtFld
.AutoSort xlmnual, .SourceName
For Each Pi In PvtFld.PivotItems
Select Case Pi.Name
Case "11"
Case Else
Pi.Visible = False
End Select
Next Pi
.AutoSort xlAscending, .SourceName
End With
PvtTab.ManualUpdate = False
End Sub
Any help would be very much appreciated. Thank you very much in advance.
This should solve your problem:
Set WB = Workbooks.Open(Pathname & "\" & Filename)
When I tried using your code, for some reason, it did not retain the backslash you put at the beginning of the "Filename" variable. That would explain why VBA couldn't find the files. Adding it back should between the path name and file name should make it work correctly
I believe you have the answer to your base problem above but I would offer the following 'tweaks' to avoid screen flashing and unrecovered variable assignment.
Application.DisplayAlerts = False
Application.ScreenUpdating = False
Do While Filename <> ""
Set WB = Workbooks.Open(Pathname & "\" & Filename) 'open all files
Call PivotX(WB)
WB.Close SaveChanges:=True
Set WB = Nothing
Filename = Dir()
Loop
Application.DisplayAlerts = True
Application.ScreenUpdating = True
The Set WB = Nothing is really only purposeful on the last pass when WB is not reassigned but your PivotX sub could use several Set nnn = Nothing before exiting. While the reference count is supposed to be decremented (and memory consequently released), that is not always the case. (see Is there a need to set Objects to Nothing inside VBA Functions) In short, it is just good coding practise.
Finally, using Dim Filename, Pathname As String declares Filename as a variant, not a string type. It isn't making any difference here but you should be aware of what your variables are being declared as.
I have a code for Excel 2007 that runs without failing.
But it is extremely & unusually slow - making my computer unresponsive for the 1-2 minutes it runs.
The files are about 14,000 kb's - so not too large.
If possible I'd like someone to tell me what I could do to make it run without causing my computer to hang. Thanks in advance.
Sub ReadFileIntoExcel()
Dim fPath As String
Const fsoForReading = 1
Dim readlength As Integer
Dim readstart As Integer
readlength = Worksheets("READFILE").Cells(1, "E").Value
readstart = Worksheets("READFILE").Cells(1, "D").Value
fPath = Worksheets("READFILE").Cells(1, "C").Value
Dim objFSO As Object
Dim objTextStream As Object, txt, allread, rw
Set objFSO = CreateObject("scripting.filesystemobject")
If objFSO.FileExists(fPath) Then
Set objTextStream = objFSO.OpenTextFile(fPath, fsoForReading)
rw = 1
Do Until objTextStream.AtEndOfStream
txt = objTextStream.ReadLine
allread = Trim(Mid(txt, readstart, readlength))
With ActiveWorkbook.Sheets("READFILE").Cells(rw, 7).Resize(1, 1)
.NumberFormat = "#" 'format cells as text
.Value = Array(allread)
End With
rw = rw + 1
Loop
objTextStream.Close
Set objTextStream = Nothing
Set objFSO = Nothing
Exit Sub
I updated your code to use an array rather than cell by cell wrote and it ran instantly
Optimisations made
Avoid cell range loops, especially writing cell by cell. Use arrays instead. This is the big one
Resize(1,1) does nothing as it keeps the cell as a single cell
Long is more efficient than Integer
Use the string functions Mid$ rather than their slower variant alternatives Mid
The allread variable was an un-necessary intermediate step
Using variable names for objects (ie ws for the worksheet), prevents longer references
code
Sub ReadFileIntoExcel()
Dim fPath As String
Dim ws As Worksheet
Const fsoForReading = 1
Dim readlength As Long
Dim readstart As Long
Dim rw as Long
Dim X()
Set ws = Worksheets("READFILE")
readlength = ws.Cells(1, "E").Value
readstart = ws.Cells(1, "D").Value
fPath = ws.Cells(1, "C").Value
Dim objFSO As Object
Dim objTextStream As Object
Set objFSO = CreateObject("scripting.filesystemobject")
If objFSO.FileExists(fPath) Then
Set objTextStream = objFSO.OpenTextFile(fPath, fsoForReading)
rw = 1
ReDim X(1 To 1, 1 To 1000)
Do Until objTextStream.AtEndOfStream
txt = objTextStream.ReadLine
If rw Mod 1000 = 0 Then ReDim Preserve X(1 To 1, 1 To UBound(X, 2) + 1000)
X(1, rw) = Trim$(Mid$(txt, readstart, readlength))
rw = rw + 1
Loop
ws.[G1].Resize(UBound(X, 2), 1) = Application.Transpose(X)
ws.Columns("G").NumberFormat = "#"
objTextStream.Close
Set objTextStream = Nothing
Set objFSO = Nothing
Exit Sub
End If
End Sub
You might try turning off screen updating while the cells are being updated. If you are touching a great many cells, this will definitely speed things up.
Application.ScreenUpdating = False
...update cells...
Application.ScreenUpdating = True
There are other things you can do as well, such as turning off calculations, but it doesn't sound like you have formulas trying to evaluate the cells your setting.