I am using Ron de Bruin's code to automate an email message within Excel, and I would like to copy and paste a dynamic range of cells from the Excel workbook into the email.
The first column of cells is populated using a SPILL formula, so the number of rows will vary depending on when the code is used. The remainder of the columns are also SPILL formulas dependent on the first column. A table example is provided below (no headers):
Fund 1
ABCDE
02/14/2023
1%
$10
Fund 2
BCDEF
02/15/2023
2%
$20
The cell with the contents "Fund 1" is labeled as "BeginningTable".
After running the code below, the spill filter formula in "BeginningTable" disappears after this line of code:
rng = ShS.Range("BeginningTable", Cells(lRow, lCol))
That is:
#REF
#REF
#REF
#REF
My idea was to have a starting point in "BeginningTable" so that if the number of funds increased tomorrow, I would be able to define a table going to the last row (number of columns won't changed). I thought the rng = ShS.Range line would correctly define this new range, but it appears to not be the case.
Sub Macro1()
'For simplicity, I purposely left out Ron de Bruin's Function RangetoHTML code.
Dim ShS As Worksheet
Dim rng As Range
Dim lCol As Integer
Dim lRow As Integer
Set ShS = ThisWorkbook.Sheets("Sheet1")
Set rng = ShS.Range("BeginningTable")
lCol = Range("BeginningTable").End(xlToRight).Column
lRow = Range("BeginningTable").End(xlDown).Row
rng = ShS.Range("BeginningTable", Cells(lRow, lCol))
Set OutApp = CreateObject("Outlook.Application")
Set OutMail = OutApp.CreateItem(0)
With OutMail
.To = ShS.Range("To")
.Cc = ShS.Range("CC")
.Bcc = ShS.Range("BCC")
.Subject = ShS.Range("Subject")
.Display
.HTMLBody = "Hi! Here is your table:" & RangetoHTML(rng) & .HTMLBody
End With
Set OutMail = Nothing
Set OutApp = Nothing
End Sub
Why does the rng = ShS.Range line erase the value in BeginningTable?
Is this issue related to the table using a SPILL filter?
How should I go about defining the new range so that I can copy and paste it into the email?
Other solutions I have tried:
Defining BeginningTable as the row from Fund 1 to $10.
When I run the code, the whole range is erased.
I tried using a SPILL in the VBA formula without lCol and lRow. However, my output in the email only shows Fund 1 and Fund 2.
Set rng = ShS.Range("BeginningTable#")
Fund 1
Fund 2
Since I thought this was progress, I tried a combination of the two above - naming BeginningTable as the row from Fund 1 to $10 and using the same code from the second bullet point.
This yielded in an run time error 1004.
Any help on this would be greatly appreciated. Let me know if I need to provide any more information.
Related
I have a spreadsheet that operators input data in, with the A column being the date, and the data is input by row. The A column is a formula that adds +1 to the date in the previous cell, going all the way down recursively to auto-populate the date as the sheet is filled out.
I have to have a report printed out at the end of every day, and I am trying to use VBA to filter the rows out by a date that the operator inputs on another sheet in cell B2. I need the macro to grab that date value, and pass it as a variable to the filter in order to pull the 12 rows of that date and paste it into a new sheet. Unfortunately, the value it pulls is not being passed, and when I put a MsgBox command in there, it shows it's pulling 12:00 AM and not a date. When using the Date variable, it also breaks the filter on the bottom macro below (trying 2 different versions just to get this working).
I'm not good with VBA, so my macros were pulled from example websites and I tailored them to what I need.
This is one macro I have tried:
Sub For_RangeCopy()
Dim rDate As Date
Dim rSheet As Worksheet
Set rSheet = ThisWorkbook.Worksheets("EOS")
rDate = CDate(rSheet.Range("B2").Value)
MsgBox (rDate)
' Get the worksheets
Dim shRead As Worksheet
Set shRead = ThisWorkbook.Worksheets("Bi-Hourly Report")
Dim shWrite As Worksheet
Set shWrite = ThisWorkbook.Worksheets("Report")
' Get the range
Dim rg As Range
Set rg = shRead.Range("A1").CurrentRegion
With shWrite
' Clear the data in output worksheet
.Cells.ClearContents
' Set the cell formats
'.Columns(1).NumberFormat = "dd/mm/yyyy"
'.Columns(3).NumberFormat = "$#,##0;[Red]$#,##0"
'.Columns(4).NumberFormat = "0"
'.Columns(5).NumberFormat = "$#,##0;[Red]$#,##0"
End With
' Read through the data
Dim i As Long, row As Long
row = 1
For i = 1 To rg.Rows.Count
If rg.Cells(i, 1).Value2 = rDate Or i = 1 Then
' Copy using Range.Copy
rg.Rows(i).Copy
shWrite.Range("A" & row).PasteSpecial xlPasteValues
' move to the next output row
row = row + 1
End If
Next i
End Sub
And here is another Macro I have tried to use. This one actually gives me the 3 header rows which I don't need, but I don't mind, this paste is a reference for the report layout anyway, so the operators won't see this sheet. But this macro does give me the first block of the date range: 1/1/2023. I do know that the "rgCriteria As String" is likely incorrect, but that is how I get anything useful from this macro. If I change that rgCriteria to a Date, it breaks the rgData.AdvancedFilter command, and I haven't learned enough VBA to know why. And my boss wants this done today, although here I am posting here, thus it's not getting done today.
Sub AdvancedFilterExample()
' Get the worksheets
Dim rSheet As Worksheet
Set rSheet = ThisWorkbook.Worksheets("EOS")
Dim shRead As Worksheet, shWrite As Worksheet
Set shRead = ThisWorkbook.Worksheets("Bi-Hourly Report")
Set shWrite = ThisWorkbook.Worksheets("Report")
' Clear any existing data
shWrite.Cells.Clear
' Remove the any existing filters
If shRead.FilterMode = True Then
shRead.ShowAllData
End If
' Get the source data range
Dim rgData As Range, rgCriteria As String
Set rgData = shRead.Range("A1").CurrentRegion
' IMPORTANT: Do not have any blank rows in the criteria range
'Set rgCriteria = rSheet.Range("B2")
rgCriteria = rSheet.Range("B2").Value
MsgBox (rgCriteria)
' Apply the filter
rgData.AdvancedFilter Action:=xlFilterCopy, CriteriaRange:=rgCriteria _
, CopyToRange:=shWrite.Range("A1")
End Sub
I don't know which method of filtering and pasting is best for my situation, but I do know that the faster is better. I'm copying entire rows, and it needs to be efficient because this log contains a lot of data. I only need one of these macros to work, but I will be heavily modifying them and chaining them together with about 5 other filter/copy/paste sequences to follow, along with printOut commands after that, and finalized by clearing the sheets it pastes to, and then re-enabling all the functionality of the sheet (calculations, displaystatusbar, events, and screenupdating) all to make it quicker while the macro is running. All of these reports will be run using the macro with a button click.
Any thoughts or suggestions would be greatly appreciated. I've been struggling with this for a couple of weeks now. I'm at a loss and turning to the community that has helped me with a TON of questions over the past 20 or so years just by a Google search!
Other information:
I'm using Office 365 on a Windows 10/11 machine. The headers of the sheet it filters does contain merged cells as the header is rows 1-3, there is a lot of data in this sheet that grows through the year. 12 rows per day for an entire year. These macros are written in a Module aptly named "Module 1" if that helps. I do have this workbook, and the original log saved on OneDrive that can be shared.
When using Advanced Filter your criteria range should have headers which match your data table.
Sub AdvancedFilterExample()
Dim rSheet As Worksheet, shRead As Worksheet, shWrite As Worksheet
Dim rgData As Range, rgCriteria As Range
Set rSheet = ThisWorkbook.Worksheets("EOS")
Set shRead = ThisWorkbook.Worksheets("Bi-Hourly Report")
Set shWrite = ThisWorkbook.Worksheets("Report")
Set rgData = shRead.Range("A1").CurrentRegion 'source data range
'## criteria range needs to include a matching date header...
Set rgCriteria = rSheet.Range("B3:B4") 'eg. "Date" in B3, date value in B4
shWrite.Cells.Clear ' Clear any existing data
If shRead.FilterMode = True Then shRead.ShowAllData ' Remove the any existing filters
rgData.AdvancedFilter Action:=xlFilterCopy, _
CriteriaRange:=rgCriteria, _
CopyToRange:=shWrite.Range("A1")
End Sub
After numerous failed attempts I am really hoping someone can with my problem. It theory what I am trying to do sounds easy enough but I have spent hours on it today with no success.
I have tried all the possible solutions from this thread but to no avail: Excel vba Autofill only empty cells
Also looked here : https://www.mrexcel.com/board/threads/macro-to-copy-cell-value-down-until-next-non-blank-cell.660608/
I am looking to autofill a formula down a column(a vlookup from another sheet) but if there is already populated cells then to skip and continue the formula in the next available blank cell. For example, in rows A2:A10, row A5 has a value in it, so the formula gets into in A2, then fills to A4, then skips A5, then continues in A6 to A10.
This below code works the first time you use it but then on the second run it debugs with a "Run-time error '1004' - No cells were found". I noticed it it putting the formula into the first cell (B2) and then debugging out.
Sub FillDownFormulaOnlyBlankCells()
Dim wb As Workbook
Dim ws1, ws2 As Worksheet
Dim rDest As Range
Set wb = ThisWorkbook
Set ws1 = Sheets("Copy From")
Set ws2 = Sheets("Copy To")
ws2.Range("A1").Formula = "=IFERROR(IF(VLOOKUP(A2,'Copy From'!A:B,2,FALSE)=0,"""",VLOOKUP(A2,'Copy From'!A:B,2,FALSE)),"""")"
Set rDest = Intersect(ActiveSheet.UsedRange, Range("B2:B300").Cells.SpecialCells(xlCellTypeBlanks))
ws2.Range("B2").Copy rDest
End Sub
Please, try the next code:
Sub FillDownFormulaOnlyBlankCells()
Dim wb As Workbook, ws1 As Worksheet, rngBlanc As Range
Set wb = ThisWorkbook
Set ws1 = wb.Sheets("Copy From")
On Error Resume Next
Set rngBlanc = ws1.Range("B2:B" & ws1.rows.count.End(xlUp).row).SpecialCells(xlCellTypeBlanks)
On Error GoTo 0
If Not rngBlanc Is Nothing Then
rngBlanc.Formula = "=IFERROR(IF(VLOOKUP(A2,'Copy From'!A:B,2,FALSE)=0,"""",VLOOKUP(A2,'Copy From'!A:B,2,FALSE)),"""")"
Else
MsgBox "No blanc rows exist in B:B column..."
End If
End Sub
After running it once and do not create any empty cell, of course there will not be any blanc cells, anymore, at a second run...
Thanks to FaneDuru for his suggestion but I actually came up with an alternative solution to my problem which I though I would post as it might help others with a similar issue.
On a separate sheet, I created 3 columns, first column is names I already have, 2nd column are the new names and the 3rd column is there to combine the first 2 columns together, then use this code to combine first 2 columns :
Sub MergeColumns()
Dim wb As Workbook
Dim ws1 As Worksheet
Dim LastRow As Long, i As Long
Set ws1 = Sheets("Your Sheet Name")
LastRow = ws1.Range("F" & Rows.Count).End(xlUp).Row
For i = 2 To LastRow
If ws1.Range("G" & i) <> "" Then
ws1.Range("I" & i) = ws1.Range("H" & i).Text & "" & ws1.Range("G" & i).Text
Else: ws1.Range("I" & i) = ws1.Range("H" & i)
End If
Next i
End Sub
Obviously changing the sheet name and columns letter to suit your requirements.
I would highly appreciate someone's help with my case here. I'm aiming to write a macro that uses Vlookup to find Quantity and Price from Monthly reports to the Master File (35K Rows). However, I would like to apply the filter first (e.g. the Product File & Date column) before using the Vlookup. Is my approach so far correct? I'm able to apply the Autofilter function, but I'm struggling to:
Skip the header row
Create `For Each function´ to run the vlookup on the visible cells only
Please have a look at my code, and let me know how I could move forward with it.
Sub VolumeVlookup1()
'Source Workbook & Worksheet
Dim ConsWB As Workbook
Dim ConsWS As Worksheet
Dim ReportingFile As Range
Set ConsWB = Workbooks.Open("https://Sharepoint.xlsm")
Set ConsWS = Sheets("ConsolidatedReports")
ConsWS.Select
Set ConsTable = ConsWS.ListObjects("ConsolidatedReports")
Set ReportingFile = ConsWS.Range("I1")
'Master Data File
Dim MasterFile As Workbook
Dim Oiv1 As Worksheet
Dim tbl3 As ListObject
'Dim rng As Range, Ffr As Range
Set MasterFile= Workbooks.Open("C:\Users\O\Downloads\XYZ One.xlsx")
Set Oiv1 = Sheets("Oiv One")
Set tbl3 = Oiv1.ListObjects("Table3")
'Starting Point Quantity Column in the Master File
Set rng = Oiv1.Range("K1")
Dim Lastrow As Long
'Filtering Master Data table
With tbl3.Range
.AutoFilter Field:=9, Criteria1:=xlFilterLastMonth, Operator:=xlFilterDynamic
.AutoFilter Field:=38, Criteria1:=ReportingFile
End With
Lastrow = ActiveSheet.UsedRange.Rows.Count
' Trying to assign dynamic first cell in the quantity column *Note that 11 is the Column Index number of Quantity in the Master Data*
Set Ffr = Rng.SpecialCells(xlCellTypeVisible).Cells(2, 11)
'-9 is the Product ID the value I need to look it up
Ffr.Value = WorksheetFunction.VLookup(Ffr.Offset(0, -9), ConsWS.Range("A:D"), 2, 0)
With ffr
.AutoFill Destination:=Range(Cells(ffr.Row, 11), Cells(Lastrow, 11)), Type:=xlFillDefault
End With
End Sub
I have been stuck on a particular problem I am having with an excel visual basic macro.
I have written a script to simply paste a dynamic range of data from one worksheet to the other, The sheet that is being pasted to is a running sheet that updates to the next empty row each time new data is sent to it.
For certain ranges of data my code breaks and gives me the 1004 error.
I have looked at multiple similar questions and they don't seem to have given me any solutions.
I will first post my code then a data set that works and a data set that does not.
Private Sub RunningSheetClick_Click()
Dim lastrow As Long
Dim Vals As Range
Dim copyWS As Worksheet
Dim pasteWS As Worksheet
Set pasteWS = Sheets("Full List")
Set copyWS = Sheets("Macro Test")
Setlastrow = Sheets("Full List").Range("A" & Rows.count).End(xlUp).Row + 1
copyWS.Activate
' the following line is where my error appears
Set Vals = copyWS.Range("B3:S" & copyWS.Range("B3").End(xlDown))
Vals.Select
Vals.Copy _
Destination:=pasteWS.Range("A" & lastrow)
End Sub
This is a data set that works and exports correctly to the sheet "Full List"
This is a data set that does not, I believe it has to do with column B and the strings that are in those cells as compared to the integers in the first data set
Any help would be appreciated, thank you for taking the time
This is bad syntax that should never work. You just cannot concatenate a range object onto a string and expect a valid range object. I suspect it works with numbers since copyWS.Range("B3").End(xlDown) is returning its cell value by default. So if the value at the bottom of column B was 99, you would be resolved to Range("B3:S99").
Set Vals = copyWS.Range("B3:S" & copyWS.Range("B3").End(xlDown))
It should be something like,
with copyWS
Set Vals = .Range(.cells(3, "B"), .cells(rows.count, "B").end(xlup).offset(0, 17))
end with
I have the following worksheet called Data:
In the same workbook I have another worksheet called Employee Database.
In Excel, how can I color the "Employee E-mail Address" and the corresponding "Company" and "Company URL" cells red from the Data worksheet if the "Employee E-mail Address" is not in the Employee Database?
In otherwords, I am trying to make the Employee Database worksheet look like this:
I've just given an example and in reality I have over 10,000 cells worth of data to do this to. I started doing this manually and realized it will take me forever.
I'd love to know if there is a macro that can do this in Excel?
Help would be so much appreciated! I have the example workbook of the screenshots above available for download here:
http://www.mediafire.com/?dttztp66dvjkzn8
You can do this without VBA, but it will require a slight change to the data on your Data sheet.
I don't recommend the "Pivot Table" or "Subtotal"-style of data storage in Excel, where you enter a primary key in one column only once then fill down associated data next to it until the next primary key.
Like merged cells, this will only lead to problems later when you want to re-organize your data.
Here's what I did:
Fill in missing email addresses on Data sheet
Highlight cells A2 all the way down column A to the end of the data in column B. So if you had company names in cells B2:B100, but only had emails from A2:A98, you should highlight A2:A100. This is because we are filling in the email address in each row of available data.
Go to Editing » Find & Select » Go To Special, select Blanks and click OK.
Now with blanks selected, type = ↑ (up arrow) , then press Ctrl+Enter. The blank cells in column A will fill in with the missing email addresses. Highlight column A, copy and paste values.
Create Dynamic Named Range for Emails
On the Employee Database sheet, create a named range called "Emails" with the following formula in the "Refers to" box:
=OFFSET('Employee Database'!$C$1,1,0,COUNTA('Employee Database'!$C:$C)-1,1)
Add Conditional Formatting
On the Data sheet, highlight A2:C whatever (ex: A2:C20000), then go to Home » Styles » Conditional Formatting and use the following formula:
=ISNA(MATCH($A2,Emails,0))
Select the color scheme you want and click OK. Here's how it looks on my computer with some sample data:
There are a few minor constraints:
You cannot leave column A blank on the Data sheet any more.
You cannot have blank rows on the Employee Database sheet in between rows of data. This is due to the way the dynamic range works.
Benefits
The benefits of this approach are, IMO, huge.
You can add or remove rows from the Employee Database sheet, and the highlighting will automatically adjust. Ex: if I add d#gmail.com and remove c#nbc.com, the formatting on the Data sheet updates immediately.
You don't have to alter your existing worksheet structure (other than filling in the missing data and adding a range name). No need for additional worksheets.
Your workbook can stay VBA-free (if it didn't have any already).
Is this what you are trying? This will create a new sheet "Desired Result" with the output. Paste this in a module.
Option Explicit
Sub Sample()
Dim wsData As Worksheet, wsDB As Worksheet, wsO As Worksheet
Dim lRow As Long, i As Long
Dim clrRng As Range
Set wsData = Sheets("Data")
Set wsDB = Sheets("Employee Database")
Set wsO = Sheets.Add
On Error Resume Next
Application.DisplayAlerts = False
Sheets("Desired Result").Delete
Application.DisplayAlerts = True
On Error GoTo 0
With wsO
.Name = "Desired Result"
wsData.Cells.Copy .Cells
lRow = .Range("B" & .Rows.Count).End(xlUp).Row
For i = 2 To lRow
If .Range("A" & i).Value = "" Then .Range("A" & i).Value = .Range("A" & i - 1).Value
Next i
For i = 1 To lRow
If Application.WorksheetFunction.CountIf(wsDB.Columns(3), .Range("A" & i).Value) = 0 Then
If clrRng Is Nothing Then
Set clrRng = .Rows(i)
Else
Set clrRng = Union(clrRng, .Rows(i))
End If
End If
Next i
If Not clrRng Is Nothing Then clrRng.Interior.ColorIndex = 3
For i = lRow To 2 Step -1
If .Range("A" & i).Value = .Range("A" & i - 1).Value Then .Range("A" & i).ClearContents
Next i
End With
End Sub