Efficiently transfer Excel formatting data to text file - vba

Here it is, I have a huge Excel workbook with which users write pricing quotes. On save, rather than saving the huge workbook, I'm transferring the relevant data to a text file and saving that text file. It's going off without a hitch, except for the one worksheet that contains formatting. I don't want the user to lose formatting when they load the previously saved quote (from the text file), so I need to determine a way to transfer that formatting data to and from the text file. Is there a smart way to do this without writing hundreds of lines of code or using any non-native Excel feature?
Here's a sample of the code for other sheets, but it's not much help for what I'm trying to do:
Sub WriteQuote()
Dim SourceFile As String
Dim data As String
Dim ToFile As Integer
Dim sh1, sh2, sh3 As Worksheet
Set sh1 = Sheets("sheet 1")
Set sh2 = Sheets("sheet 2")
Set sh3 = Sheets("sheet 3")
SourceFile = "C:\Users\███████\Desktop\test.txt"
ToFile = FreeFile
Open SourceFile For Output As #ToFile
'PRINT DETAILS TO TXT FILE
For i = 7 To 56
If sh1.Range("B" & i).Value <> "" Then
data = sh1.Range("B" & i).Value & "__"
If sh1.Range("D" & i).Value <> "" Then
data = data & sh1.Range("D" & i).Value & "__"
Else: data = data & " __"
End If
If sh1.Range("E" & i).Value <> "" Then
data = data & "ns" & "__"
Else: data = data & " __"
End If
data = data & sh1.Range("F" & i).Value & "__"
data = data & sh1.Range("G" & i).Value & "__"
data = data & sh1.Range("J" & i).Value & "__"
data = data & sh1.Range("M" & i).Value
Else: Exit For
End If
Print #ToFile, data
Next i
Close #ToFile
End Sub

This is an example using a user type ("record") and Random access IO.
There are limitations, and I believe using Random access
would probably waste space on disk, however it is a reasonable
way to go about doing this.
In the example I suggest using a bit mask for boolean properties,
for example "Bold" (a bit mask can save space and shorten the code).
The file read/write actions are based on :
https://support.microsoft.com/en-us/kb/150700
!!! It is possible that you'll get a "bad record length" error, although
every this is fine and works the first time. There are allot of reports about this issue (google VBA bad record length). If that is the case, you might want to change the IO to Binary instead of Random (code change will be needed).
!!!!! Add a module and paste the code there, or, for the very least,
paste the record in a module (not in a sheet).
Option Explicit
' Setting up a user type ("record").
' you can add more variables, however just makes sure they are fixed
' length, for example: integer\doube\byte\... Note that if you want to
' add a string, ' make sure to give it fixed length, as shown below.
Public Type OneCellRec
' this will hold the row of the source cell
lRow As Long
' this will hold the column of the source cell
lColumn As Long
' This will hold the value of the cell.
' 12 is the maximum length you expect a cell to have-
' CHANGE it as you see fit
Value As String * 12
' This hold the number format- again, you might need to
' twik the 21 length-
NumberFormat As String * 21
' will hold design values like Bold, Italic and so on
DesignBitMask1 As Integer
' will hold whether the cells has an underline- this is not boolean,
' as there are several type of underlines available.
UnderLine As Long
FontSize As Double
End Type
' ---- RUN THIS ---
Public Sub TestFullTransferUsingRec()
Dim cellSetUp As Range
Dim cellSrc As Range
Dim cellDst As Range
Dim r As OneCellRec
Dim r2 As OneCellRec
On Error Resume Next
Kill "c:\file1.txt"
On Error GoTo 0
On Error GoTo errHandle
' For the example,
' Entering a value with some design values into a cell in the sheet.
' --------------------------------------
Set cellSetUp = ActiveSheet.Range("A1")
cellSetUp.Value = 1.5
cellSetUp.Font.Bold = True
cellSetUp.Font.Size = 15
cellSetUp.Font.UnderLine = xlUnderlineStyleSingle
cellSetUp.NumberFormat = "$#,##0.00"
' Doing it again for example purposes, in a different cell.
Set cellSetUp = ActiveSheet.Range("C5")
cellSetUp.Value = "banana"
cellSetUp.Font.Bold = True
cellSetUp.Font.Size = 15
cellSetUp.Font.UnderLine = XlUnderlineStyle.xlUnderlineStyleDouble
' ============ saving the cells to the text file =============
' open file for write
Open "c:\file1.txt" For Random As #1 Len = Len(r)
' save to a record the value and the design of the cell
Set cellSrc = ActiveSheet.Range("A1")
r = MyEncode(cellSrc)
Put #1, , r
' save to a record the value and the design of the cell
Set cellSrc = ActiveSheet.Range("C5")
r = MyEncode(cellSrc)
Put #1, , r
Close #1
' ============ loading the cells from the text file =============
Application.EnableEvents = False
' open file for read
Dim i%
Open "c:\file1.txt" For Random As #1 Len = Len(r2)
' read the file
For i = 1 To Int(LOF(1) / Len(r))
Get #1, i, r2
' destination cell- write the value and design
' --------------------------------------------
Set cellDst = Sheet2.Cells(r2.lRow, r2.lColumn)
Call MyDecode(cellDst, r2)
Next
'Close the file.
Close #1
errHandle:
If Err.Number <> 0 Then
MsgBox "Error: " & Err.Number & " " & _
Err.Description, vbExclamation, "Error"
On Error Resume Next
Close #1
On Error GoTo 0
End If
Application.EnableEvents = True
End Sub
' Gets a single cell- extracts the info you want into a record.
Public Function MyEncode(cell As Range) As OneCellRec
Dim r As OneCellRec
Dim i%
i = 0
r.lRow = cell.row
r.lColumn = cell.column
r.Value = cell.Value
r.FontSize = cell.Font.Size
r.UnderLine = cell.Font.UnderLine
r.NumberFormat = cell.NumberFormat
' Use a bit mask to encode true\false excel properties.
' the encode is done using "Or"
If cell.Font.Bold = True Then i = i Or 1
If cell.Font.Italic = True Then i = i Or 2
'If cell. ..... .. = True Then i = i Or 4
'If cell. ..... .. = True Then i = i Or 8
'If cell. ..... .. = True Then i = i Or 16
'If cell. ..... .. = True Then i = i Or 32
'If cell. ..... .. = True Then i = i Or 64
'If cell. ..... .. = True Then i = i Or 128
'If cell. ..... .. = True Then i = i Or 256
' Remember the Integer limit. If you want more than int can handle,
' use long type for the i variable and r.DesignBitMask1 variable.
'If cell. ..... .. = True Then i = i Or ' (2^x)-
r.DesignBitMask1 = i
MyEncode = r
End Function
' Decode- write the info from a rec to a destination cell
Public Sub MyDecode(cell As Range, _
r As OneCellRec)
Dim i%
cell.Value = r.Value
i = r.DesignBitMask1
cell.Value = Trim(r.Value)
cell.Font.Size = r.FontSize
cell.Font.UnderLine = r.UnderLine
' trim is important here
cell.NumberFormat = Trim(r.NumberFormat)
' Use a bit mask to decode true\false excel properties.
' the decode is done using "And"
If i And 1 Then cell.Font.Bold = True
If i And 2 Then cell.Font.Italic = True
'If i And 4 Then ...
'If i And 8 Then ...
'...
End Sub

You could try TextToColumns. You're writing a delimiter in "__" that you could take advantage of. It also seems to keep the formatting of the cells when receiving the parsed text.
Sub ReadQuote()
SourceFile = "C:\Users\||||||\Desktop\test.txt"
Open SourceFile For Input As #8
Input #8, data
Range("M1") = data 'Temporary holder for an input line
'Range to start the parsed data "A1" in this example
Range("A1") = Range("M1").TextToColumns(, xlDelimited, , , , , , , , "__")
Close #8
End Sub

Related

VBA dynamic pictures

I am trying to insert a picture into excel based off a cell value. The Cell value is in the image path. I am new, what I have is partially based on recording the macro and part from looking stuff up. This is what I tried...
I keep getting an error on the ActiveSheet.Pictures.Insert line
Sub Part_Picture()
'
' Part_Picture Macro
'
Dim imageName As String
Dim imageFolder As String
Dim imagePath As String
For Each Cell In Range("B7")
imageName = Cell.Value
imageFolder = "Q:\New Project Part Folders\Elizabeth Delgado\Database pictures\Part\" & imageName
imagePath = imageFolder & ".jpg"
Range("B11").Select
'
ActiveSheet.Pictures.Insert(imagePath).Select
Next Cell
End Sub
"Unable to get the insert property of the Pictures class" is a generic error message which you may as well just translate as "Something went wrong with what you're trying to do and I can't give you more information". It's likely though that the path to the image file has not been build correctly.
1) Remove the .Select from your insert statement. Syntactically it makes no sense. Just use ActiveSheet.Pictures.Insert(imagePath)
2) Check the value in cell B7 is the file name only, not including the extension. Since your code adds ".jpg" you dont need that in B7.
3) Check the file is actually a jpg, not for instance a png
4) Check the file / folder actually exists
FYI For Each Cell In Range("B7") is only going to iterate one cell - B7 - and is unnecessary. If you only intended for one cell to be read you should use imageName = Range("B7").Value, or better yet since you need a string use imageName = Range("B7").Text
Consider this option.
Sub InsertPics()
Dim fPath As String, fName As String
Dim r As Range, rng As Range
Application.ScreenUpdating = False
fPath = "C:\your_path_here\"
Set rng = Range("A1:A" & Cells(Rows.Count, 1).End(xlUp).Row)
i = 1
For Each r In rng
fName = Dir(fPath)
Do While fName <> ""
If fName = r.Value Then
With ActiveSheet.Pictures.Insert(fPath & fName)
.ShapeRange.LockAspectRatio = msoTrue
Set px = .ShapeRange
If .ShapeRange.Width > Rows(i).Columns(2).Width Then .ShapeRange.Width = Columns(2).Width
With Cells(i, 2)
px.Top = .Top
px.Left = .Left
.RowHeight = px.Height
End With
End With
End If
fName = Dir
Loop
i = i + 1
Next r
Application.ScreenUpdating = True
End Sub
' Note: you need the file extension, such as ',jpg', or whatever you are using, so you can match on that.
Whatever picture name you put in Column A, will be imported into the adjacent cell in, Column B
The .Pictures.Insert("c:\fixedfile.png") asked a fix file name as its parameter. However you may use FileCopy "desiredfile.png", "fixedfile.png" to replace the content of fixedfile.png which then meet your needs.

How to remove extra empty text file created using vba excel macro wherein its filename is the cell in a sheet?

I'm just new in using excel vba macro. I am trying to create text file and use the cell values as name of individual text file. At the first place the value contains character and those character will be replaced. the only value will remain are all numbers. That function is working well. My problem is once I execute the create button, the program will create an extra text file which name is base on empty cell and no any input "D" as input in the text file. What I want is to create a text file without that extra text file created. below is my excel format and the code.
I have 3 column use as below:
LOG DATA INPUT BLOCK NAME
5687 D ASD
5689 D
5690 D
5692 D
5691 D
5688 D
4635 D
Correct result will create four text file:
abc-5687.req
abc-5689.req
abc-5690.req
abc-5692.req
Result with extra text file consider as wrong see below:
abc-.req <-- extra text file created
abc-5687.req
abc-5689.req
abc-5690.req
abc-5692.req
my code:
Private Sub CREATE_REQ_Click()
Dim myDataSheet As Worksheet
Dim myReplaceSheet As Worksheet
Dim myLastRow As Long
Dim myRow As Long
Dim myFind As String
Dim myReplace1 As String
Dim myReplace2 As String
Dim sExportFolder, sFN
Dim rArticleName As Range
Dim rDisclaimer As Range
Dim oSh As Worksheet
Dim oFS As Object
Dim oTxt As Object
' Specify name of Data sheet
Set myDataSheet = Sheets("Sheet1")
' Specify name of Sheet with list of replacements
Set myReplaceSheet = Sheets("Sheet2")
' Assuming list of replacement start in column A on row 2, find last entry in list
myLastRow = myReplaceSheet.Cells(Rows.Count, "A").End(xlUp).Row
Application.ScreenUpdating = False
' Loop through all list of replacments
For myRow = 2 To myLastRow
' Get find and replace values (from columns A and B)
myFind = myReplaceSheet.Cells(myRow, "A")
myReplace1 = myReplaceSheet.Cells(myRow, "B")
' Start at top of data sheet and do replacements
myDataSheet.Activate
Range("A2").Select
' Ignore errors that result from finding no matches
On Error Resume Next
' Do all replacements on column A of data sheet
Columns("A:A").Replace What:=myFind, Replacement:=myReplace1, LookAt:=xlPart, SearchOrder:=xlByRows, MatchCase:=False, SearchFormat:=False, ReplaceFormat:=False
Next myRow
sExportFolder = "D:\TEST\REQ_FILES_CREATED_HERE"
Set oSh = Sheet1
Set oFS = CreateObject("Scripting.Filesystemobject")
For Each rArticleName In oSh.UsedRange.Columns("A").Cells
Set rDisclaimer = rArticleName.Offset(, 1)
If rArticleName = "" & "LOG DATA" Then
oTxt = False
Else
'Add .txt to the article name as a file name
sFN = "-" & rArticleName.Value & ".req"
Set oTxt = oFS.OpenTextFile(sExportFolder & "\" & ActiveSheet.Cells(2, 3) & sFN, 2, True)
oTxt.Write rDisclaimer.Value
oTxt.Close
End If
Next
'Reset error checking
On Error GoTo 0
Application.ScreenUpdating = True
MsgBox "Replacements complete! "
End Sub
For Each rArticleName In oSh.UsedRange.Columns("A").Cells
Set rDisclaimer = rArticleName.Offset(, 1)
If Not(rArticleName = "" Or rArticleName = "LOG DATA") Then
'Add .txt to the article name as a file name
sFN = "-" & rArticleName.Value & ".req"
Set oTxt = oFS.OpenTextFile(sExportFolder & "\" & ActiveSheet.Cells(2, 3) & sFN, 2, True)
oTxt.Write rDisclaimer.Value
oTxt.Close
End If
Next
Pretty close to a one line fix. You just need to fix the If. Once that's right you don't need the Else.

VBA to Add 22 pipes(|) to Text file using Macro

I hope you can help I have a piece of code and what it does is it takes information from two excel sheets and puts it into two text docs for consumption in a database.
The code I have works fine but 22 columns have been added in the database where the text file is destined to be consumed so I need to put 22 pipes(|) before company Id in Notepad file
The first pic is of the Excel sheet where staff can input data
The second pic shows the excel sheet where the data is sorted from the 'Meeting Close Out Template' and the macro picks up the data for transformation to text. This sorting sheet is called 'Template-EFPIA-iTOV' the columns in grey are what the macro pics up
In the below pic you can see that Company Id is the last column in 'Template-EFPIA-iTOV
Below is how the sheet 'Template-EFPIA-iTOV ' is represented in text
Here is the Company IDs in the Text file
Because the destination database has now got an extra 22 columns before Company Id I need my macro to put 22 pipes(|) before Company id in the text doc.
The Excel sheet 'Template EFPIA Customer' is also converetd to text but this is fine and needs no amendments.
My Code is below. As always any help is greatly appreciated.
Pic of Macro front end
CODE
'Variables for Deduplication
Dim WB_Cust As Workbook
'File Variables
Dim DTOV_Directory As String
Dim DTOV_File As String
Dim ITOV_Directory As String
Dim ITOV_file As String
Const DELIMITER As String = "|"
' Variables for writing text into file
Dim WriteObject As Object
Dim OUTFilename As String
Dim MyWkBook As Workbook
Dim MyWkSheet As Worksheet
Dim OutputFile As String ' Output flat file name
Dim SysCode As String ' Variable for text string of system code to be filled into information system code column
Dim strFilenameOut As String ' Variable for name of file being processed. It is used for SysCode and OutputFile determination.
Dim CustAddressSave As Range
'Processing of one file. This procedure is called when only one of file types are selected
Public Sub Process_template(Directory As String, File As String, FileFlag As String)
Application.ScreenUpdating = False 'Turns off switching of windows
If FileFlag = "D" Then 'Variables setup for DTOV
DTOV_Directory = Directory
DTOV_File = File
ElseIf FileFlag = "I" Then 'Variables setup for ITOV
ITOV_Directory = Directory
ITOV_file = File
Else
MsgBox "Unhandled Exception - Unknown files sent"
Exit Sub
End If
Call Process(1, FileFlag)
Application.ScreenUpdating = True 'Turns On switching of windows
End Sub
'Processing of two file. This procedure is called when both file types are to be processed
Public Sub Process_Templates(DTOV_Dir As String, DTOV_Fil As String, ITOV_Dir As String, ITOV_Fil As String)
Application.ScreenUpdating = False 'Turns off switching of windows
DTOV_Directory = DTOV_Dir
DTOV_File = DTOV_Fil
ITOV_Directory = ITOV_Dir
ITOV_file = ITOV_Fil
Call Process(2, "B")
Application.ScreenUpdating = True 'Turns on switching of windows
End Sub
' *****************************************************************************
' Management of File to write in UT8 format
' *****************************************************************************
' This function open the file indicated to be able to write inside
Private Sub OUTFILE_OPEN(filename As String)
Set WriteObject = CreateObject("ADODB.Stream")
WriteObject.Type = 2 'Specify stream type - we want To save text/string data.
WriteObject.Charset = "utf-8" 'Specify charset For the source text data.
WriteObject.Open 'Open the stream And write binary data To the object
OUTFilename = filename
End Sub
' This function closes the file
Private Sub OUTFILE_CLOSE()
WriteObject.SaveToFile OUTFilename, 2
WriteObject.Close ' Close the file
End Sub
' Write a string in the outfile
Private Sub OUTFILE_WRITELINE(txt As String)
WriteObject.WriteText txt & Chr(13) & Chr(10)
txt = ""
End Sub
' subprocedure to read TOV data into stream and call procedure to generate file
Public Sub generate_tov(i_Sheet_To_Process As String, _
i_OffsetShift As Integer)
Dim sOut As String ' text to be written into file
'Set OutputFile = "sarin"
Sheets(i_Sheet_To_Process).Select
Range("C2").Select
'Parsing of system code from filename
strFilenameOut = ActiveWorkbook.Name 'example - initial file name: EFPIA_DTOV-BE-MTOV-201503271324.xlsx
SysCode = Left(strFilenameOut, InStrRev(strFilenameOut, "-") - 1) 'example - after LEFT cut EFPIA_ITOV-BE-MTOV
SysCode = Right(SysCode, Len(SysCode) - InStrRev(SysCode, "-")) 'example - after RIGHT cut MTOV
Do Until (IsError(ActiveCell.Offset(0, 1).Value) = True)
If ActiveCell.Offset(0, 1).Value = "" Then
'end-of-file reached, hence exist the do loop
Exit Do
End If
ActiveCell.Value = SysCode
ActiveCell.Offset(0, i_OffsetShift).Value = Application.WorksheetFunction.VLookup(Sheets("Template - EFPIA Customer").Cells(ActiveCell.Row, 3).Value, Sheets("Appendix").Range("N1:O103"), 2, "FALSE") & "_" & ActiveCell.Offset(0, i_OffsetShift).Value
ActiveCell.Offset(1, 0).Select
Loop
OutputFile = Left(strFilenameOut, InStrRev(strFilenameOut, ".") - 1) & ".txt"
If (IsError(ActiveCell.Offset(0, 1).Value) = True) Then
MsgBox ("incorrect data in the TOV source file. Please correct and re-run the macro")
Exit Sub
Else
Call generate_file
End If
End Sub
' procedures to write stream data into file for both TOV and customer
Public Sub generate_file()
Dim X As Integer
Dim Y As Long
Dim FieldValue As String
Dim NBCol As Integer
Dim sOut As String ' text to be written into file
OUTFILE_OPEN (OutputFile) 'Open (setup) the output file
'Open OutputFile For Output As #1 'Prepares new file for output
Set MyWkBook = ActiveWorkbook
Set MyWkSheet = ActiveSheet
NBCol = 0
Do While (Trim(MyWkSheet.Cells(1, NBCol + 1)) <> "")
NBCol = NBCol + 1
Loop
' Scroll all rows
Y = 1
Do While (Trim(MyWkSheet.Cells(Y, 4)) <> "")
sOut = ""
For X = 1 To NBCol
' here, if required, insert a convertion type function
FieldValue = Trim(MyWkSheet.Cells(Y, X))
FieldValue = Replace(FieldValue, "|", "/") 'Replaces pipes from input file to slashes to avoid mismatches during ETL
If FieldValue = "0" Then FieldValue = "" 'Replaces "only zeroes" - might need redoing only for amount columns
If InStr(MyWkSheet.Cells(1, X), "Amount") > 0 Then FieldValue = Replace(FieldValue, ",", ".")
' add into the string
If X = NBCol Then
sOut = sOut & FieldValue
Else
sOut = sOut & FieldValue & DELIMITER
End If
Next X
Y = Y + 1
OUTFILE_WRITELINE sOut
Loop
OUTFILE_CLOSE
End Sub
' read the customer data into stream
Public Sub read_customer(i_Sheet_To_Process As String, _
i_range As String)
Dim CCST As Workbook ' Variable to keep reference for template Workbook that is being used for copy-paste of Customer data into virtuall Workbook
Sheets(i_Sheet_To_Process).Select
ActiveSheet.UsedRange.Copy
Set CCST = ActiveWorkbook
WB_Cust.Activate
If i_range = "" Then
Sheets("Sheet1").Range(CustAddressSave.Address).PasteSpecial xlPasteValues
Range(CustAddressSave.Address).Select
ActiveCell.Offset(0, 2).Select
Rows(CustAddressSave.Row).EntireRow.Delete
Else
Sheets("Sheet1").Range("A1").PasteSpecial xlPasteValues
Range("C2").Select
End If
'Call LookingUp(CCST)
Do Until (IsError(ActiveCell.Offset(0, 1).Value) = True)
If ActiveCell.Offset(0, 1).Value = "" Then
'end-of-file reached, hence exist the do loop
Exit Do
End If
ActiveCell.Offset(0, 1).Value = Application.WorksheetFunction.VLookup(ActiveCell.Offset(0, 0).Value, CCST.Sheets("Appendix").Range("N1:O103"), 2, "FALSE") & "_" & ActiveCell.Offset(0, 1).Value
ActiveCell.Value = SysCode
ActiveCell.Offset(1, 0).Select
Loop
If (IsError(ActiveCell.Offset(0, 1).Value) = True) Then
MsgBox ("incorrect data in the source file. Please correct and re-run the macro")
Exit Sub
Else
Set CustAddressSave = ActiveCell.Offset(0, -2) 'Saves position where 2nd Cust data sheet will be copied
OutputFile = Left(Mid(strFilenameOut, 1, (InStr(strFilenameOut, "_"))) & "CUST" & Mid(strFilenameOut, (InStr(strFilenameOut, "-"))), InStrRev(strFilenameOut, ".") - 1) & ".txt"
End If
End Sub
'Main Procedure of the module that processes the files
Private Sub Process(Loops As Integer, FileFlag As String) 'Loops - number of files (1 or 2), FileFlag - which file is to be processed (I - ITOV, D - DTOV, B - Both)
Set WB_Cust = Workbooks.Add
' This virtual workbook is created only for duration of the processing. It is used to copy paste CUSTOMER data form one or both templates.
If FileFlag = "D" Or FileFlag = "B" Then
' process DTOV first always
Call Open_DTOV
'----------------------------------------------------------
Call generate_tov("Template - Transfer of Value", 3)
' if the file have data issues, then abort the procedure.
If (IsError(ActiveCell.Offset(0, 1).Value) = True) Then
GoTo HandleException
End If
'----------------------------------------------------------
Call read_customer("Template - EFPIA Customer", "A")
' if the file have data issues, then abort the procedure.
If (IsError(ActiveCell.Offset(0, 1).Value) = True) Then
GoTo HandleException
End If
End If
If FileFlag = "I" Or FileFlag = "B" Then
Call Open_ITOV
'----------------------------------------------------------
Call generate_tov("Template - EFPIA iToV", 17)
' if the file have data issues, then abort the procedure.
If (IsError(ActiveCell.Offset(0, 1).Value) = True) Then
GoTo HandleException
End If
'----------------------------------------------------------
If FileFlag = "B" Then
Call read_customer("Template - EFPIA Customer", "")
Else
Call read_customer("Template - EFPIA Customer", "A")
End If
' if the file have data issues, then abort the procedure.
If (IsError(ActiveCell.Offset(0, 1).Value) = True) Then
GoTo HandleException
End If
End If
Call Deduplicate
Call generate_file ' generate single customer file
MsgBox "Export Process is completed"
HandleException:
' Closes the virtual workbook used for consolidation and deduplication of customers
WB_Cust.Saved = True
WB_Cust.Close
ActiveWorkbook.Saved = True 'Closes Template
ActiveWorkbook.Close (False)
If Loops = 2 Then 'Closes second Template if two files are being processed
ActiveWorkbook.Saved = True
ActiveWorkbook.Close (False)
End If
Application.ScreenUpdating = True 'Turns back on switching to exported excel file once it gets opened
Exit Sub
End Sub
'Unused Procedure to reduce Customer data processing code. Does not work now.
Private Sub LookingUp(CCST As Workbook)
Do Until (ActiveCell.Offset(0, 1).Value = "")
ActiveCell.Offset(0, 1).Value = Application.WorksheetFunction.VLookup(ActiveCell.Offset(0, 0).Value, CCST.Sheets("Appendix").Range("N1:O103"), 2, "FALSE") & "_" & ActiveCell.Offset(0, 1).Value
ActiveCell.Value = SysCode
ActiveCell.Offset(1, 0).Select
Loop
End Sub
'Open DTOV Template
Private Sub Open_DTOV()
Workbooks.Open (DTOV_Directory + DTOV_File)
End Sub
'Open ITOV Template
Private Sub Open_ITOV()
Workbooks.Open (ITOV_Directory + ITOV_file)
End Sub
'Deduplicating Customer data based on Source_Party_Identifier, which already contains source code prefix
Private Sub Deduplicate()
ActiveSheet.UsedRange.RemoveDuplicates Columns:=4, Header:=xlYeas
End Sub
Since your code is set up to detect the number of columns using this section of generate_file:
Do While (Trim(MyWkSheet.Cells(1, NBCol + 1)) <> "")
NBCol = NBCol + 1
Loop
...and then dynamically saves all the rows to the pipe delimited text file, I strongly recommend just adding the new columns into your sheet, even if they are going to be blank.
However, if you want to jury-rig it to get the job done, you can always add 22 pipes to each output row. Replace OUTFILE_WRITELINE sOut in the generate_file loop with OUTFILE_WRITELINE "||||||||||||||||||||||" & sOut.
Make sure, if you do decide to use that ugly hack, that you comment it very carefully so that you and any other maintainers of the code can find and fix it when the requirements inevitably change again.

Making excel macro for file scanning more stable

I was curious if anybody could provide suggestions on how I can make an excel macro more stable.
The macro prompts the user for a path to a folder containing files to scan. The macro then iterates for every file in this folder.
It opens the excel file, scans Column D for the word fail, then copies that row of data to the data sheet in the excel file where this macro is programmed.
For the most part the macro runs perfectly but sometimes I get run time errors or 'excel has stopped working' errors. I can scan through 5000+ files at a time and the macro takes a while to run.
Any suggestions would be appreciated. Thanks!
Sub findFail()
Dim pathInput As String 'path to file
Dim path As String 'path to file after being validated
Dim fileNames As String 'path to test file
Dim book As Workbook 'file being tested
Dim sheet As Worksheet 'sheet writting data to
Dim sh As Worksheet 'worksheet being tested
Dim dataBook As Workbook 'where data is recorded
Dim row As Long 'row to start writting data in
Dim numTests As Long 'number of files tested
Dim j As Long 'counter for number of files tested
Dim i As Long 'row currently being tested
Dim lastRow As Long 'last row used
Dim startTime As Double 'time when program started
Dim minsElapsed As Double 'time it took program to end
Application.ScreenUpdating = False
j = 0
i = 1
row = 2
Set dataBook = ActiveWorkbook
Set sheet = Worksheets("Data")
sheet.Range("A2:i1000").Clear
startTime = Timer
'-----Prompt for Path-----
pathInput = InputBox(Prompt:="Enter path to files. It must have a \ after folder name.", _
Title:="Single Report", _
Default:="C:\Folder\")
If pathInput = "C:\Folder\" Or pathInput = vbNullString Then 'check to make sure path was inputed
MsgBox ("Please enter a valid file path and try again.")
Exit Sub
Else
path = pathInput 'path = "C:\Temp\212458481\" ' Path for file location
fileNames = Dir(path & "*.xls") 'for xl2007 & "*.xls?" on windows
'-----begin testing-----
Do While fileNames <> "" 'Loop until filename is blank
Set book = Workbooks.Open(path & fileNames)
Set sh = book.Worksheets(1)
lastRow = sh.UsedRange.Rows(sh.UsedRange.Rows.Count).row
If sh.Cells(lastRow, 2).Value - sh.Cells(1, 2).Value >= 0.08333333 Then
Do While sh.Range("D" & i).Value <> "" 'loop untile there are no rows left to test
If sh.Range("D" & i).Value = "Fail" Then 'record values if test result is false
sheet.Range("A" & row).Value = book.Name
sheet.Range("B" & row).Value = Format(sh.Range("B" & i).Value - sh.Range("B1").Value, "h:mm:ss")
sheet.Range("C" & row).Value = sh.Range("A" & i).Value
sheet.Range("D" & row).Value = Format(sh.Range("B" & i).Value, "h:mm:ss")
sheet.Range("E" & row).Value = sh.Range("C" & i).Value
sheet.Range("F" & row).Value = sh.Range("D" & i).Value
sheet.Range("G" & row).Value = sh.Range("E" & i).Value
sheet.Range("H" & row).Value = sh.Range("F" & i).Value
sheet.Range("I" & row).Value = sh.Range("G" & i).Value
row = row + 1
Exit Do
End If
i = i + 1
Loop
j = j + 1
dataBook.Sheets("Summary").Cells(2, 1).Value = j
End If
book.Close SaveChanges:=False
fileNames = Dir()
i = 1
Loop
numTests = j
Worksheets("Summary").Cells(2, "A").Value = numTests
minsElapsed = Timer - startTime
Worksheets("Summary").Cells(2, "B").Value = Format(minsElapsed / 86400, "hh:mm:ss")
End If
End Sub
Without the same dataset as you we, can not definitively supply an answer but I can recommend the below which is related to the error you are seeing.
Try freeing/destroying the references to book and sh.
You have a loop that sets them:-
Do While fileNames <> "" 'Loop until filename is blank
Set book = Workbooks.Open(path & fileNames)
Set sh = book.Worksheets(1)
However the end of the loop does not clear them, ideally it should look as below:-
Set sh = Nothing
Set book = Nothing
Loop
This is a better way to handle resources and should improve memory usage.
As a poor example, without it your code is saying, sh equals this, now it equals this instead, now it equals this instead, now it equals this instead, etc...
You end up with the previous reference that was subsequently overwritten being a sort of orphaned object that is holding some space in memory.
Depending on your case, you may use the following to make it faster -by turning off excel processes that you don't really need at the time of your macro execution-
Sub ExcelBusy()
With Excel.Application
.Cursor = xlWait
.ScreenUpdating = False
.DisplayAlerts = False
.StatusBar = False
.Calculation = xlCalculationManual
.EnableEvents = False
End With
End Sub
In your sub
Dim startTime As Double 'time when program started
Dim minsElapsed As Double 'time it took program to end
Call ExcelBusy
...
As a comment, you never set back screenupdating to true in your sub, that may lead to strange behavior in excel, you should turn everything to default after you are done with your stuff.
OT: Some processes can't be optimized any further -sometimes-, by what you are saying -scanning over 5k files?- surely it's going to take a time, you need to work in how to communicate the user that is going to take a while instead -perhaps an application status bar message or a user form showing process?-.

How to loop through worksheets in a defined order using VBA

I have the below working code which loops through each worksheet and if the value defined in the range (myrange) is 'Y', it outputs those sheets into a single PDF document. My challange is that i want to define the order that they are output in the PDF based on the number value in the range (for example 1,2,3,4,5,6,7 etc) instead of 'Y'. I plan on using the same column in the myrange to check whether it needs to be output to PDF, by simply swapping the 'Y' for a number, such as '1' and '2'.
Currently the order is defined based on the location of the worksheet tabs. from left to right.
Any help will be much appreciated.
Sub Run_Me_To_Create_Save_PDF()
Dim saveAsName As String
Dim WhereTo As String
Dim sFileName As String
Dim ws As Worksheet
Dim printOrder As Variant '**added**
Dim myrange
On Error GoTo Errhandler
Sheets("Settings").Activate
' Retrieve value of 'Period Header' from Settings sheet
Range("C4").Activate
periodName = ActiveCell.Value
' Retrieve value of 'File Name' from Settings sheet
Range("C5").Activate
saveAsName = ActiveCell.Value
' Retrieve value of 'Publish PDF to Folder' from Settings sheet
Range("C6").Activate
WhereTo = ActiveCell.Value
Set myrange = Worksheets("Settings").Range("range_sheetProperties")
' Check if Stamp-field has any value at all and if not, add the current date.
If Stamp = "" Then Stamp = Date
' Assemble the filename
sFileName = WhereTo & saveAsName & " (" & Format(CDate(Date), "DD-MMM-YYYY") & ").pdf"
' Check whether worksheet should be output in PDF, if not hide the sheet
For Each ws In ActiveWorkbook.Worksheets
Sheets(ws.Name).Visible = True
printOrder = Application.VLookup(ws.Name, myrange, 4, False)
If Not IsError(printOrder) Then
If printOrder = "Y" Then
Sheets(ws.Name).Visible = True
End If
Else: Sheets(ws.Name).Visible = False
End If
Next
'Save the File as PDF
ActiveWorkbook.ExportAsFixedFormat Type:=xlTypePDF, Filename:= _
sFileName, Quality _
:=xlQualityStandard, IncludeDocProperties:=True, IgnorePrintAreas:=False, _
OpenAfterPublish:=True
' Unhide and open the Settings sheet before exiting
Sheets("Settings").Visible = True
Sheets("Settings").Activate
MsgBox "PDF document has been created and saved to : " & sFileName
Exit Sub
Errhandler:
' If an error occurs, unhide and open the Settings sheet then display an error message
Sheets("Settings").Visible = True
Sheets("Settings").Activate
MsgBox "An error has occurred. Please check that the PDF is not already open."
End Sub
---------------------- UPDATE: -------------------------------------
Thank you for all your input so far. I did get it to work briefly, but with more playing i've become stuck. I am now receiving a 'Subscript our of range' error with the below code at :
If sheetNameArray(x) <> Empty Then
Any ideas?
Sub Run_Me_To_Create_Save_PDF()
Dim saveAsName As String
Dim WhereTo As String
Dim sFileName As String
Dim ws As Worksheet
Dim myrange
ReDim sheetNameArray(0 To 5) As String
Dim NextWs As Worksheet
Dim PreviousWs As Worksheet
Dim x As Integer
'On Error GoTo Errhandler
Sheets("Settings").Activate
' Retrieve value of 'Period Header' from Settings sheet
Range("C4").Activate
periodName = ActiveCell.Value
' Retrieve value of 'File Name' from Settings sheet
Range("C5").Activate
saveAsName = ActiveCell.Value
' Retrieve value of 'Publish PDF to Folder' from Settings sheet
Range("C6").Activate
WhereTo = ActiveCell.Value
' Check if Stamp-field has any value at all and if not, add the current date.
If Stamp = "" Then Stamp = Date
' Assemble the filename
sFileName = WhereTo & saveAsName & " (" & Format(CDate(Date), "DD-MMM-YYYY") & ").pdf"
Set myrange = Worksheets("Settings").Range("range_sheetProperties")
For Each ws In ActiveWorkbook.Worksheets
printOrder = Application.VLookup(ws.Name, myrange, 4, False)
If Not IsError(printOrder) Then
printOrderNum = printOrder
If printOrderNum <> Empty Then
'Add sheet to array
num = printOrderNum - 1
sheetNameArray(num) = ws.Name
End If
End If
Next
MsgBox Join(sheetNameArray, ",")
'Order Tab sheets based on array
x = 1
Do While Count < 6
If sheetNameArray(x) <> Empty Then
Set PreviousWs = Sheets(sheetNameArray(x - 1))
Set NextWs = Sheets(sheetNameArray(x))
NextWs.Move after:=PreviousWs
x = x + 1
Else
Count = Count + 1
x = x + 1
End If
Loop
Sheets(sheetNameArray).Select
'Save the File as PDF
ActiveSheet.ExportAsFixedFormat Type:=xlTypePDF, Filename:=sFileName, Quality _
:=xlQualityStandard, IncludeDocProperties:=True, IgnorePrintAreas:=False, _
OpenAfterPublish:=True
' open the Settings sheet before exiting
Sheets("Settings").Activate
MsgBox "PDF document has been created and saved to : " & sFileName
Exit Sub
Errhandler:
' If an error occurs, unhide and open the Settings sheet then display an error message
Sheets("Settings").Visible = True
Sheets("Settings").Activate
MsgBox "An error has occurred. Please check that the PDF is not already open."
End Sub
You would want to define the worksheets in an array.
This example uses a static array, knowing the sheets order and what you want to print in advance. This does work.
ThisWorkbook.Sheets(Array("Sheet1","Sheet2","Sheet6","Master","Sales")).Select
ActiveSheet.ExportAsFixedFormat Type:=xlTypePDF, fileName:=sFileName, Quality _
:=xlQualityStandard, IncludeDocProperties:=True, IgnorePrintAreas:=False, _
OpenAfterPublish:=True
The problem is that if a sheet is hidden, it will fail on the selection.
So you will need to already know which sheets pass the test to be printed or not before declaring the Array. Therefore you will need a dynamic array to build the list of Worksheets.
I did change how your PrintOrder works, instead of making the sheet invisible, it simply doesn't add it to the array, or vice versa, adds the ones you want to the array. Then you select the array at the end, and run your print macro that works.
I tested this using my own test values, and am trusting that your PrintOrder Test works. But this does work. I used it to print time sheets that only have more than 4 hours per day, and it succeeded, merging 5 sheets out of a workbook with 11 sheets into one PDF.. All of them qualified the test.
TESTED: Insert this instead of your For Each ws and add the Variable Declarations with yours
Sub DynamicSheetArray()
Dim wsArray() As String
Dim ws As Worksheet
Dim wsCount As Long
wsCount = 0
For Each ws In Worksheets
printOrder = Application.VLookup(ws.Name, myrange, 4, False)
If Not IsError(printOrder) Then
If printOrder = "Y" Then
wsCount = wsCount + 1
ReDim Preserve wsArray(1 To wsCount)
'Add sheet to array
wsArray(wsCount) = ws.Name
End If
End If
Next
Sheets(wsArray).Select
ActiveSheet.ExportAsFixedFormat Type:=xlTypePDF, fileName:=sFileName, Quality _
:=xlQualityStandard, IncludeDocProperties:=True, IgnorePrintAreas:=False, _
OpenAfterPublish:=True
End Sub
edit: further explained context of my code to OP
Here is a bit of code I came up with. Basically you would want to take this and adapt it to fit your specific needs but the general idea should work!
Sub MovingPagesAccordingToNumberInRange()
Dim ws As Worksheet
Dim NextWs As Worksheet
Dim PreviousWs As Worksheet
Dim sheetNameArray(0 To 400) As String
Dim i As Integer
'This first loop is taking all of the sheets that have a number
' placed in the specified range (I used Cell A1 of each sheet)
' and it places the name of the worksheet into an array in the
' order that I want the sheets to appear. If I placed a 1 in the cell
' it will move the name to the 1st place in the array (location 0).
' and so on. It only places the name however when there is something
' in that range.
For Each ws In Worksheets
If ws.Cells(1, 1).Value <> Empty Then
num = ws.Cells(1, 1).Value - 1
sheetNameArray(num) = ws.Name
End If
Next
' This next section simply moves the sheets into their
' appropriate positions. It takes the name of the sheets in the
' previous spot in the array and moves the current spot behind that one.
' Since I didn't know how many sheets you would be using I just put
' A counter in the prevent an infinite loop. Basically if the loop encounters 200
' empty spots in the array, everything has probably been organized.
x = 1
Do While Count < 200
If sheetNameArray(x) <> Empty Then
Set PreviousWs = sheets(sheetNameArray(x - 1))
Set NextWs = sheets(sheetNameArray(x))
NextWs.Move after:=PreviousWs
x = x + 1
Else
Count = Count + 1
x = x + 1
End If
Loop
End Sub