Trying to Add a Footnote on the last Page of an Excel File using a loop - vba

I'm currently working on an invoice which could span multiple pages and I want the Signature to appear near the bottom of the last page of the invoice.
My idea:
If the invoice is only one page long I would like to place the signature on Row 39.
If there is data in Row 39,then Place the Footnote at the end of the next page which is Row 86 (add 47 rows).
Continue doing that until an empty row is found. So if Row 86 has Data add another 47 Rows and place the footer in Row 133.
I'm having some trouble figuring out how to get the loop to work, I know how to get a loop to work when you're using a count Do while i > (insert amount here) but I don't know how to do it until it finds an empty row.

Don't do it exactly that way. Start with the last used row (plus one), then use a loop to find the next appropriate row after that.
lastRow = activesheet.usedrange.rows.count + 1
sigRow = 39
while sigRow < lastRow
sigRow = sigRow + 47
wend
activesheet.cells(sigRow, c) = signature
Now, this is pseudocode, so you will need to adapt it to your specific use. For instance, you have to define the signature, and tell it what column to put it in (c is the placeholder). You may want to explicitly name the worksheet instead of just using activesheet.

Ended up Solving it
Public Sub Signature()
Dim Signature_Line As String
Dim Signature_Labels As String
Dim Signed As Integer
Dim Signature_Row As Integer
Signature_Line = " _________________________ _________________________ ______________________________"
Signature_Labels = " Name Date Signature"
Signed = 0
Signature_Row = 39
Do Until Signed = 1
If IsEmpty(ActiveSheet.Range("A" & Signature_Row - 1)) = True Then
ActiveSheet.Range("A" & Signature_Row).Value = Signature_Line
ActiveSheet.Range("A" & Signature_Row + 1).Value = Signature_Labels
Signed = Signed + 1
Else
Signature_Row = Signature_Row + 47
End If
Loop
End Sub

Related

Excel VBA - Expand range from using just one column to using multiple

I have a working piece of code that looks at a columns value, copies those values, and strips off the 'speed' component string of that value - Turning '200 Mbps' into just '200', etc.
They updated the source data on me and the values are now in three columns - AC, AD, AE instead of just AC now. So values can exist in either column and any row, can be Gbps and Mbps, etc.
At the end of the day, I need the total of the three columns and X number of rows. I have sample data below.
How can I (or can I even) modify this existing code to account for the extra two columns. I am wondering if the Dictionary approach is correct at this point. It was originally added at someone else's suggestion.
Dim Cla As Range
With CreateObject("scripting.dictionary")
For Each Cla In wbFrom.Sheets("Sheet0").Range("AC9", Range("AC" & Rows.Count).End(xlUp))
Cla.Value = Replace(Cla.Value, " Mbps", "")
Cla.Value = Replace(Cla.Value, " Gbps", "")
If Not .exists(Cla.Value) Then
.Add Cla.Value, Nothing
End If
Next Cla
wbTo.Sheets("Sheet1").Range("D13").Resize(.Count).Value = Application.Transpose(.keys)
End With
I don't really understand the If and With loops and how they combine with keys and Transpose like this. ((Thanks TinMan for the info))
I've tried moving this out, but having this outside the loop breaks the code. Is there something in this section that I need to update?
If Not .exists(Cla.Value) Then
.Add Cla.Value, Nothing
End If
Next Cla
Some sample data looks like this: Notice each element is on its own row.
AC AD AE
300
123
72
200
101
The 300 gets paste where it belongs but nothing else adds up or get grabbed I think. Also, when the data looks like THIS, it pastes two values instead of just one:
Notice the 300 and 123 are now on the same line, 300 gets paste into the destination cell and 123 gets paste into two cells below that.
AC AD AE
300 123
72
200
101
Example 1
Use Range.Resize() to extend the number of columns targeted.
For Each Cla In wbFrom.Sheets("Sheet0").Range("AC9", wbFrom.Sheets("Sheet0").Range("AC" & Rows.Count).End(xlUp)).Resize(, 3)
Next
Example 2
Set a helper variable to the Range and use Range.Resize() to extend the number of columns targeted.
Dim Target As Range
With wbFrom.Sheets("Sheet0")
Set Target = .Sheets("Sheet0").Range("AC9", .Range("AC" & Rows.Count).End(xlUp)).Resize(, 3)
Debug.Print Target.Address
End With
Addendum
This will strip away the " Mbps" and " Gbps" as well as insert the sum the a;; the numbers into Range("D13").
With wbFrom.Sheets("Sheet0")
Set Target = .Range("AC9", .Range("AC" & .Rows.Count).End(xlUp)).Resize(, 3)
For Each Cla In Target
Cla.Value = Replace(Cla.Value, " Mbps", "")
Cla.Value = Replace(Cla.Value, " Gbps", "")
Next Cla
.Range("D13").Value = WorksheetFunction.Sum(Target)
End With
I'm not sure that you are describing the problem correctly. Clearly, using the dictionary, get you a list of unique distinct values. But you stated that:
At the end of the day, I need the total of the three columns and X
number of rows.
The existing code leads me to believe that you are going to be doing a count of different speeds...how many 200 Mbps, how many 72 Mpbs, etc.
Either that, or the previous code didn't work as intended.
Assuming that you described the problem correctly and all you want is the total bandwidth then this should do the trick...
Dim LastRow As Long, Value As Long, Sum As Long, Count As Long
' Get the last row, looking at all 3 columns "AC:AE"
LastRow = FindLastRow(wbFrom.Sheets("Sheet0").Range("AC:AE"))
' Iterate through all 3 columns
For Each Cla In wbFrom.Sheets("Sheet0").Range("AC9:AE" & LastRow)
' Use Val() to get just the numeric value and the inline IIF() statment to automatically adjust the speed
Value = Val(Cla.Value) * IIf(InStr(Cla.Value, "Gbps") > 0, 1000, 1)
' Check if there is a Value, if so, Add it to the Sum (and increment the count)
If Value > 0 Then
Sum = Sum + Value
Count = Count + 1
End If
Next Cla
' Write the Sum to the other Workbook (not sure if you need the Count)
wbTo.Sheets("Sheet1").Range("D13") = Sum
And a function to find the last cell in a range (even if the list is filtered)
Public Function FindLastRow(r As Range) As Long
' Works on Filtered Lists/Hidden rows
Const NotFoundResult As Long = 1 ' If all cells are empty (no value, no formula), this value is returned
FindLastRow = r.Worksheet.Evaluate("IFERROR(LARGE(ROW('" & r.Worksheet.Name & "'!" & r.Address & ")*--(NOT(ISBLANK('" & r.Worksheet.Name & "'!" & r.Address & "))),1)," & NotFoundResult & ")")
End Function
From the comments on your question I gather that you want the sum of the original inputs that are contained in columns AC, AD and AE. Such sum you want to store it at cell d13. Since I have limited input, this is the least ugly code I can provide:
nRow1 = ActiveSheet.Cells(ActiveSheet.Rows.Count, "AC").End(xlUp).Row
nRow2 = ActiveSheet.Cells(ActiveSheet.Rows.Count, "AD").End(xlUp).Row
nRow3 = ActiveSheet.Cells(ActiveSheet.Rows.Count, "AE").End(xlUp).Row
nRow = Application.Max(nRow1, nRow2, nRow3)
input_range = Range("AC9:AE" & nRow)
acum = 0
For Each cell In input_range
If Not IsEmpty(cell) Then
temp = Split(cell, " ")
acum = acum + CInt(temp(0))
End If
Next
Range("D13").Value = acum

Excel VBA-Getting a blank message box with data derrived from an array

I am getting a blank message box for an array which should automatically have and display the following values:
00:00:00
01:00:00
02:00:00
and so on...
Here is my code
Dim i As Integer
i = 0
Dim sampleArr(0 To 24) As Variant
Dim a As Integer
a = 0
Do Until i > 23
sampleArr(a) = i & ":00:00"
a = a + 1
i = i + 1
MsgBox sampleArr(a)
Loop
Please tell me what's wrong with this code
You update the value of sampleArr(a), then increment a. So to get the just-updated value you need to use the pre-incremented value: a-1.
MsgBox sampleArr(a-1)
Put the Msgbox first before you increment a and i.
MsgBox sampleArr(a)
a = a + 1
i = i + 1
It's not entirely clear what you're trying to achieve here, (especially with a and i being identical. Presumably the msgbox is only actually in there to prove you've created the array correctly and will be removed later?
That said, as everyone is pointing out, you're incrementing your pointer before displaying the entry. The simplest way to fix that is to put the display line in immediately after creating the element.
I've also formatted i in order to produce the exact output you've requested.
Also, I suspect your array only needs to go 0 To 23 if this is some kind of time selector?
So, fixing your issue looks like:
Dim i As Integer
i = 0
Dim sampleArr(0 To 23) As Variant
Dim a As Integer
a = 0
Do Until i > 23
sampleArr(a) = Format(i, "00") & ":00:00"
MsgBox sampleArr(a)
a = a + 1
i = i + 1
Loop
However, you could just do the following:
Dim i As Integer
Dim sampleArr(0 To 23) As Variant
For i = 0 To 23
sampleArr(a) = Format(i, "00") & ":00:00"
MsgBox sampleArr(a)
Next
Beyond this, if you want to store the values in the array as TIME rather than a text representation of the time (useful for calculations etc.) then replace the sampleArr line with
sampleArr(a) = TimeSerial(i, 0, 0)

Excel VBA : Auto numbering

I'm creating a database on Excel, and encountered some problems as I tried to assign auto number to each row.
Requirements are:
generate auto number to each row(on the column A) when column B is not blank.
the number should be unique and must always be connected to the contents of the same row even when the column is sorted or when new rows are inserted, etc.
when a new row is inserted (anywhere on the same column), a new number should be assigned (the newest number should be the biggest number)
if
possible, the auto number should have a prefix, and number should be displayed in four digits (e.g. 0001, 0011)
I have tried some VBA codes I found from other people's questions (e.g. Excel VBA : Auto Generating Unique Number for each row).
So far, the code below has worked the best, but the requirement (3) and (4) couldn't be solved by that code.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim maxNumber
If Not Intersect(Target, Range("B:B")) Is Nothing Then
' don't run when more than one row is changed
If Target.Rows.Count > 1 Then Exit Sub
' if column A in the current row has a value, don't run
If Cells(Target.Row, 1) > 0 Then Exit Sub
' get the highest number in column A, then add 1 and write to the
' current row, column A
maxNumber = Application.WorksheetFunction.Max(Range("A:A"))
Target.Offset(0, -1) = maxNumber + 1
End If
End Sub
I'm short of the knowledge of VBA and I hope someone could help me this.
Many thanks.
Alternative via CustomDocumentProperties
Instead of using a hidden sheet as proposed by #TimWilliams, one can assign incremented values to a user defined custom document property (CDP), naming it e.g. "InvNo" holding the newest invoice number. The cdp remain stored in the saved workbook.
The function below gets the current number saved to this workbook related property and returns the next number by adding 1 to the current value. It uses a help procedure RefreshCDP to assign the new value (could be used of course independantly to reset values programmaticaly to any other value). - If the cdp name isn't passed as (optional) argument, the function assumes "InvNo" by default.
Note that code requires some error handling to check if the cdp exists.
Example call
Dim InvoiceNumber as Long
InvoiceNumber = NextNumber("InvNo") ' or simply: NextNumber
Public Function NextNumber(Optional CDPName As String = "InvNo") As Long
'a) get current cdp value
Dim curVal As Long
On Error Resume Next
curVal = ThisWorkbook.CustomDocumentProperties(CDPName)
If Err.Number <> 0 Then Err.Clear ' not yet existing, results in curVal of 0
'b) increment current cdp value by one to simulate new value
Dim newVal As Long
newVal = curVal + 1
'Debug.Print "Next " & CDPName & " will be: " & newVal
'c) assign new value to custom document property
RefreshCDP CDPName, newVal, msoPropertyTypeNumber
'Debug.Print "New " & CDPName & " now is: " & ThisWorkbook.CustomDocumentProperties(CDPName)
NextNumber = newVal
End Function
Help procedure RefreshCDP
Sub RefreshCDP(CDPName As String, _
newVal As Variant, docType As Office.MsoDocProperties)
On Error Resume Next
ThisWorkbook.CustomDocumentProperties(CDPName).Value = newVal
'If cdp doesn't exist yet, create it (plus adding the new value)
If Err.Number > 0 Then
ThisWorkbook.CustomDocumentProperties.Add _
Name:=CDPName, _
LinkToContent:=False, _
Type:=docType, _
Value:=newVal
End If
End Sub
Related links
MS help: Excel.Workbook.CustomDocumentProperties
Check if BuiltInDocumentProperty is set without error trapping
Chip Pearson: Document Properties
How to add a DocumentProperty to CustomDocumentProperties in Excel?
Do not use Max() to find the next number - use instead a hidden sheet or name to store the current number, and increment it each time a new Id is required.
For example:
Public Function NextNumber(SequenceName As String)
Dim n As Name, v
On Error Resume Next
Set n = ThisWorkbook.Names(SequenceName)
On Error GoTo 0
If n Is Nothing Then
'create the name if it doesn't exist
ThisWorkbook.Names.Add SequenceName, RefersTo:=2
v = 1
Else
'increment the current value
v = Replace(n.RefersTo, "=", "")
n.RefersTo = v + 1
End If
NextNumber = v
End Function
This allows you to use multiple different sequences as long as you give each one a distinct name.
Dim seq
seq = NextNumber("seqOne")
'etc

MS Excel 2010 - VBA to lookup in one column a customer number and Tag the corresponding column with Yes or No

I have an extremely large dataset with customer numbers and we cannot just use a =IF(E3=160248, "YES", "NO") to tag a particular customer number of 160248 with YES or NO. Instead, I would like to use VBA code to lookup Customer_Number in column E and return a YES or NO in the corresponding row in Column AG, called Incorporated_160248. I have not done an If then clause in VBA, so I have no idea where to start. Please note, each month the data set can change. One month it could be 4,000 entries and the next 3,500, so that has to be dynamic. Any thoughts?
Sub TagTryco()
Dim CN As Integer, result As String
CN = Range("E:E").Value
If CN = 160248 Then
result = "YES"
Else
result = "NO"
End If
Range("AG:AG").Value = result
End Sub
I get a Compile error: Wrong number of arguments or invalid property assignment.
This CODE Works now:
Sub TagTryco()
Dim listLength
listLength = Worksheets("ILS_Import").Cells(Rows.Count, "E").End(xlUp).Row - 1
Dim i As Integer
For i = 2 To listLength + 2
If Worksheets("ILS_Import").Range("E" & i) = 160248 Then
Worksheets("ILS_Import").Range("AG" & i) = "Yes"
Else
Worksheets("ILS_Import").Range("AG" & i) = "No"
End If
Next
End Sub
To know how many entries you have:
dim listLength
listlength = Sheet1.Cells(Rows.Count, "E").End(xlUp).Row - 1 'I assumed column E, starting at row 2
You need to loop from row 2 to the row 2 + listLength, check the cell in column E, and check if it is equal to your number:
dim i as integer
for i = 2 to listLength + 2
If Range("E" & i) = 160248 Then
Range("AG" & i) = "Yes"
Else
Range("AG" & i) = "No"
End If
Next
If you wish to scan for different numbers you can adapt the code to use a value from a cell in which you enter that number, OR use an inputbox to enter the number you want to look for, or something else. This code was not tested.
If you want to use the column name you assigned instead of AG (which is safer) you can use something along the lines of:
= Range("Incorporated_160248")(i+1)
Instead, which gives the column with an offset of i. Should bring you to the right cell.

Removing rows based on matching criteria

I have a dated CS degree so I understand the basics of VB but I don't write macros very often and need help solving a particular condition. (...but I understand functions and object oriented programming)
Assume the following:
- Column A contains reference ID's in alphanumeric form, sorted alphabetically.
- Column B contains strings of text, or blanks.
I'm trying to write a macro that automatically removes any extra rows for each unique reference number based on the contents of the "Notes" in column B. The problem is that if column A has multiple instances of a unique ref number, I need to identify which row contains something in column B. There is one catch: it is possible that the reference number has nothing in column B and should be retained.
To explain further, in the following screenshot I would need to:
Keep the yellow highlighted rows
Delete the remaining rows
I tried to show various configurations of how the report might show the data using the brackets on the right and marked in red. Its difficult to explain what I'm trying to do so I figured a picture would show what I need more clearly.
This task is making the report very manual and time consuming.
it's pretty simple
you just go throug the rows and check whether this row needs to be deleted, an earlier row with this id needs to be deleted or nothing should happen.
in my example i mark these rows and delete them in the end.
Sub foo()
Dim rngSelection As Range
Dim startingRow As Integer
Dim endRow As Integer
Dim idColumn As Integer
Dim noteColumn As Integer
Dim idValuableRow As New Dictionary
Dim deleteRows As New Collection
Set rngSelection = Selection
startingRow = rngSelection.Row
endRow = rngSelection.Rows.Count + startingRow - 1
idColumn = rngSelection.Column
noteColumn = idColumn + 1
For i = startingRow To endRow
currentID = Cells(i, idColumn)
If idValuableRow.Exists(currentID) Then
If Trim(idValuableRow(currentID)("note")) <> "" And Trim(Cells(i, noteColumn)) = "" Then
deleteRows.Add i
ElseIf idValuableRow(currentID)("note") = "" And Trim(Cells(i, noteColumn)) <> "" Then
deleteRows.Add idValuableRow(currentID)("row")
idValuableRow(currentID)("row") = i
idValuableRow(currentID)("note") = Cells(i, noteColumn)
End If
Else
Dim arr(2) As Variant
idValuableRow.Add currentID, New Dictionary
idValuableRow(currentID).Add "row", i
idValuableRow(currentID).Add "note", Cells(i, noteColumn)
End If
Next i
deletedRows = 0
For Each element In deleteRows
If element <> "" Then
Rows(element - deletedRows & ":" & element - deletedRows).Select
Selection.Delete Shift:=xlUp
deletedRows = deletedRows + 1
End If
Next element
End Sub
it could look something like this. the only thing you need is to add Microsoft Scripting Runtime in Tools/References