Copying filtered cells to powerpoint table - excel

Im relatively new to VBA. Im currently trying to run a code that copies filtered visible cells into powerpoint as a table. The dataset is rather huge and will continue to grow. How do I make the code dynamic and format the table that's being pasted into powerpoint?
Im getting an error Run time error '-2147188160 (80048240)': Shapes(unknown member) : Integer out of range. 2795 is not in the valid range of 1 to 75"
I would also like the data set to be formatted whereby the first and second column thats copied from the excel sheet gets transposed as the column headers in ppt.
The table looks like this in excel:
Product Code
Product Name
Keyword
Country
Status
Description
123456
Kobe Chicken
Chicken
Japan
Imported
NIL
643734
Hanwook Beef
Beef
Korea
Exported
NIL
The format i'd like in ppt:
123456 Kobe Chicken
643734 Hanwook Beef
(If the products list go on the products would be added via columns)
Country
Japan
Korea
NIL
Status
Imported
Exported
NIL
Description
NIL
NIL
Below is my code:
Also, is there anyway I can get the user to select from the dropdown menu of keyword to set the filtering criterea rather than entering it as a userinput for the code to filter out cells that match the criterea?
Sub Export_Range()
Dim userin As Variant
Dim userin2 As Variant
Dim pp As New PowerPoint.Application
Dim ppt As PowerPoint.Presentation
Dim sld As PowerPoint.Slide
Dim shpTable As PowerPoint.Shape
Dim i As Long, j As Long
Dim rng As Excel.Range
Dim sht As Excel.Worksheet
'To set range
userin = InputBox("Please enter the product you'd like to filter by: ")
userin2 = InputBox("Yes or No?: ")
set rng = Range("B$16:$AG$2810").Select
Selection.AutoFilter
ActiveSheet.Range("$B$16:$AG$2810").AutoFilter Field:=3, Criteria1:=userin
ActiveSheet.Range("$B$16:$AG$2810").AutoFilter Field:=4, Criteria1:=userin2
'This hides columns that are not needed in copying to ppt
Range("E16").EntireColumn.Hidden = True
Range("G16").EntireColumn.Hidden = True
Range("H16").EntireColumn.Hidden = True
Range("J16").EntireColumn.Hidden = True
Range("M16").EntireColumn.Hidden = True
Range("O16").EntireColumn.Hidden = True
Range("P16").EntireColumn.Hidden = True
Range("Q16").EntireColumn.Hidden = True
'Creates new ppt, and adds selected info into table
pp.Visible = True
If pp.Presentations.Count = 0 Then
Set ppt = pp.Presentations.Add
Else
Set ppt = pp.ActivePresentation
End If
Set sld = ppt.Slides.Add(1, ppLayoutTitleOnly)
Set shpTable = sld.Shapes.AddTable(rng.Rows.Count, rng.Columns.Count)
For i = 1 To rng.Rows.Count
For j = 1 To rng.Columns.Count
shpTable.Table.Cell(i, j).Shape.TextFrame.TextRange.Text = _
rng.Cells(i, j).Text
Next
Next
For i = 1 To rng.Rows.Count
For j = 1 To rng.Columns.Count
If (rng.Cells(i, j).MergeArea.Cells.Count > 1) And _
(rng.Cells(i, j).Text <> "") Then
shpTable.Table.Cell(i, j).Merge _
shpTable.Table.Cell(i + rng.Cells(i, j).MergeArea.Rows.Count - 1, _
j + rng.Cells(i, j).MergeArea.Columns.Count - 1)
End If
Next
Next
sld.Shapes.Title.TextFrame.TextRange.Text = _
rng.Worksheet.Name & " - " & rng.Address
End Sub

Here's an example of how to do this. I'm not sure if you simplfied your actual use case but I used this table for testing:
End Result:
Code:
Sub Tester()
'Add project reference to `Microsoft PowerPoint xx.x Object Library`
Dim loProds As ListObject, rngVis As Range, colKeyWord As ListColumn
Dim c As Range, rw As Range, visCount As Long, col As Long
Dim ppApp As PowerPoint.Application, ppPres As PowerPoint.Presentation
Dim ppSlide As PowerPoint.Slide, pptable As PowerPoint.Table
Dim indxProdCode As Long, indxProdName As Long
Dim indxCountry As Long, indxStatus As Long, indxDescr As Long
'info is in listobject "Products" on worksheet "Listing"
Set loProds = ThisWorkbook.Worksheets("Listing").ListObjects("Products")
'filter on Keyword = "Beef"
loProds.ShowAutoFilter = True
Set colKeyWord = loProds.ListColumns("Keyword")
loProds.Range.AutoFilter Field:=colKeyWord.Index, Criteria1:="Beef"
'get visible cells in keyword column
On Error Resume Next 'ignore error if no visible cells
Set rngVis = colKeyWord.DataBodyRange.SpecialCells(xlCellTypeVisible)
On Error GoTo 0 'stop ignoring errors
If rngVis Is Nothing Then 'no rows left after filtering?
MsgBox "No rows visible after applying filter...", vbExclamation
Exit Sub
End If
visCount = rngVis.Cells.Count 'how many visible rows left?
'start PPT, add a new presentation, and put a table on slide 1
Set ppApp = New PowerPoint.Application
ppApp.Visible = True
Set ppPres = ppApp.Presentations.Add()
Set ppSlide = ppPres.Slides.Add(1, ppLayoutTitleOnly)
Set pptable = ppSlide.Shapes.AddTable(4, visCount + 1).Table
SetText pptable.cell(2, 1), "Country" 'fixed row headers in column 1
SetText pptable.cell(3, 1), "Status"
SetText pptable.cell(4, 1), "Description"
'find the column indexes of the content we want to extract from `loProds`
indxProdCode = loProds.ListColumns("Product Code").Index
indxProdName = loProds.ListColumns("Product Name").Index
indxCountry = loProds.ListColumns("Country").Index
indxStatus = loProds.ListColumns("Status").Index
indxDescr = loProds.ListColumns("Description").Index
col = 1
'loop over each visible row and populate a new column in the table
For Each c In rngVis.Cells
Set rw = Application.Intersect(c.EntireRow, loProds.DataBodyRange) 'the table row for this cell
col = col + 1 'next ppt table column
SetText pptable.cell(1, col), rw.Cells(indxProdCode).Value & " " & rw.Cells(indxProdName).Value
SetText pptable.cell(2, col), rw.Cells(indxCountry).Value
SetText pptable.cell(3, col), rw.Cells(indxStatus).Value
SetText pptable.cell(4, col), rw.Cells(indxDescr).Value
Next c
End Sub
'helper method for setting table cell text
Sub SetText(cell As PowerPoint.cell, v)
cell.Shape.TextFrame.TextRange.Text = v
End Sub

Related

How to setup a conversion from Excel Table to PowerPoint Presentation

I would like to create a presentation based on data sorted in rows:
That, in the PowerPoint, would appear as this
But I am having difficulties understanding the correct syntax to properly have this done inside PowerPoint, rather than the Excel where the data comes from: how to declare/deal with the range of cells that become each a slide and in each of them of how to fill the relative placeholder, or "transposed/cut" place in table. I have looked everywhere, the very few pages/articles I found are not clear and overall short of explanation. I am stuck at the very beginning of this as I don't know how to set up the variable range of cells or if it should be an array to split later in different ranges, I would know how to continue, once I understood how to link Excel to PowerPoint Layouts/Slides/Objects.
I know I have to set a counter for each step, i.e. on for the change in Section #, in in the Slide # etc., mostly I find not clear how to translate the different parts of the table in rows and then section of rows.
EDIT: With code I have come up so far, this was the most helpful resource I could find. However I have the problem I was mentioning before: I do I swap the row I have left to columns in the table? How do I change them in a Range?
I referenced Excel for PowerPoint as from the original code, but the selection does no get recognized in POwerPoint (as said above, I would like to use it in PPT, not in Excel) . However, it works only in Excel, I have an error at For Each DataRow In DataRange.Rows variable not set when running as pptm. (reciprocal Libraries enabled in both programs).
From Excel, I can populate the title and the heading (so "AAA" and "aa") but I do not know how to progress in the cells, transposing the values for some columns and then restart.
Update:
I added a variable for the the columns of the source, but I am not sure how to deal with it. I'm so close but I don't know how to finish it. I introduced the variable FirstRowToColumn as range (columns E to the Excel screenshot below, that would become the first column of the tabel in PowerPoint), but I don't know how to declare this and the others range and paste them into the table. Could someone please teach me this or point to the solution, am I at least close ?
Edit2: added, with which I would copy the range and paste transposed, but it says the area is different.
Edit3: I can paste, I have to fix the counter to have the table in each slide, but the question of how to transpose remains and on top of that how to write the values in the Powerpoint table.
Edit 4:
I am trying a new way, by pasting in the Object placeholder id, but I saee nothing appearing in the table.
Sub General_Namer_For_Slides_And_Shapes()
Dim AnySlide As Slide
Dim AnyShape As Shape
Set AnySlide = Application.ActivePresentation.Slides(1)
For Each AnyShape In AnySlide.Shapes
Debug.Print "Application.ActivePresentation.Slides(1) AnySlide.Shapes AnyShape.Name " & AnyShape.Name & " AnyShape.Id "; AnyShape.Id '''names of each shape and their id '''removed " Slide " & AnySlide.SlideID&;
Next
Debug.Print "ActivePresentation.Slides(1).CustomLayout.Name " & ActivePresentation.Slides(1).CustomLayout.Name & " ActivePresentation.Slides(1).CustomLayout.Index " & ActivePresentation.Slides(1).CustomLayout.Index&;
Debug.Print " There are " & ActivePresentation.SlideMaster.Design.SlideMaster.CustomLayouts(4).Shapes.Count & " shapes in the Layout slide (SlideMaster View)"
'Debug.Print "ActivePresentation.Designs(4).Name = " & ActivePresentation.Designs(1).SlideMaster.CustomLayouts(4); ""
'Debug.Print " ActivePresentation.Designs.Name" & ActivePresentation.SlideMaster.Shapes.Placeholders. & ; ActivePresentation.Designs(4).Index; " & ActivePresentation.Designs(4).Index "
End Sub
Set NewTable = sld.Shapes.AddTable(12, 4)
FirstRowToColumn.Cells.PasteSpecial Paste:=-4163, Transpose:=True
to
Sub LoopRowsSelectedXCLToPPT()
Dim xlApp As Object
Dim xlWorkBook As Object
Dim xlSheet As Object
Dim DataRange As Range 'used
Dim DataRow As Range 'used
Dim DataCol As Range 'used
Dim PPTrng As Range ''cloning here the above to use in PowerPoint
Dim ShpRng As ShapeRange ''cloning here the data raw as range of shapes i could create later
Dim ShpCll As Shape
Dim AppPPT As PowerPoint.Application
Dim Pres As PowerPoint.Presentation
Dim sld As PowerPoint.Slide
Dim AppXCL As Excel.Application 'repeated the same as above with Excel as argument
Dim InputSheet As Excel.Worksheet
Set AppPPT = GetObject(, "PowerPoint.Application")
Set Pres = AppPPT.ActivePresentation
Set AppXCL = GetObject(, "Excel.Application")
Set InputSheet = AppXCL.ActiveSheet
Dim RowCounter As Integer
Dim ColCounter As Integer
Dim iRow As Integer
Dim iColumn As Integer
Dim FirstRowToColumn As Range
Dim SecondRowToColumn As Range
RowCounter = 0
ColCounter = 0
Set DataRange = Selection
For Each DataRow In DataRange.Rows
RowCounter = RowCounter + 1
Set sld = Pres.Slides.AddSlide(Pres.Slides.Count + 1, Pres.SlideMaster.CustomLayouts(4))
sld.Shapes.Title.TextFrame.TextRange.Text = DataRow.Cells(3, 3)
sld.Shapes.Placeholders(4).TextFrame.TextRange.Text = DataRow.Cells(3, 4)
' For Each DataCol In DataRange.Columns
ColCounter = ColCounter + 1
Set FirstRowToColumn = DataRange.Range(Cells(RowCounter + 1, 5), Cells(RowCounter + 1, 10))
FirstRowToColumn.Copy
Set NewTable = sld.Shapes.AddTable(12, 4)
sld.Shapes.Placeholders(4).TextFrame.TextRange.Text = FirstRowToColumn.Cells(1, 5)
' FirstRowToColumn.Cells(1, 10) =
' With sld.Shapes.Placeholders
' NewTable.Range(1,1)
'
'
' End With
' With sld.Shapes.Paste.SpecialPaste:=-4163, Transpose:=True
Next DataRow
Debug.Print RowCounter
Debug.Print ColCounter
End Sub
You may have to finesse the details but this works for me:
Sub LoopRowsSelectedXCLToPPT()
Const TABLE_COLS As Long = 5 '# of columns in PPT tables
Const BLOCK_SIZE As Long = 5
Dim Datarange As Range, rw As Range, rng As Range, i As Long, col As Long
Dim ppApp As PowerPoint.Application, pres As PowerPoint.Presentation
Dim sld As PowerPoint.Slide, newTable As PowerPoint.Table
On Error Resume Next
Set ppApp = GetObject(, "PowerPoint.Application")
Set pres = ppApp.ActivePresentation
On Error GoTo 0
If pres Is Nothing Then
MsgBox "Destination presentation must be open in PowerPoint", vbCritical
Exit Sub
End If
Set Datarange = Selection
For Each rw In Datarange.Rows
Set sld = pres.Slides.AddSlide(pres.Slides.Count + 1, _
pres.SlideMaster.CustomLayouts(2))
Set newTable = sld.Shapes.AddTable(BLOCK_SIZE, TABLE_COLS).Table
col = 0
Set rng = rw.Cells(5).Resize(1, BLOCK_SIZE) 'first BLOCK_SIZE cells starting from Col E
Do While Application.CountA(rng) > 0 'while have any data in `rng`
col = col + 1
If col > TABLE_COLS Then Exit Do 'ran out of columns in the PPT table...
For i = 1 To BLOCK_SIZE 'fill column # col
newTable.Cell(i, col).Shape.TextFrame2.TextRange.Text = rng.Cells(i).Value
Next i
Set rng = rng.Offset(0, BLOCK_SIZE) 'next block of cells to the right
Loop
Next rw
End Sub

Validation summary of mandatory cells in excel

I have got an excel workbook, it has 5 static tabs and more tabs can be created using a template tab.
In each tab there is a certain field or a range that is mandatory to be filled out also in the new created tabs (might be up to 60).
My question is how can I go about seeing in, lets say in mainsheet, a summary which shows me:
Which tab has missing fields
Which fields is missing (an address of a cell)
I tried naming the range "MyRange" and counting if the cells are non blank.
But this will not work for the newly created sheets.
I also tried a conditional formatting but again this will not give me a summary.
In the meantime I also bumped into a sort of solution but this is also not the thing I am looking for:
Sub listEmptyCells()
Dim emptyAddresses() As String
Dim i As Long
Dim ws As Worksheet
Dim rng As Range
Set ws = Sheets("1.Data Source") ' CHANGE AS NECESSARY
Set rng = ws.Range("B30:B32")
If WorksheetFunction.CountBlank(rng) = 0 Then
MsgBox ("No empty cells in the range")
Exit Sub
End If
emptyAddresses() = Split(rng.SpecialCells(xlCellTypeBlanks).Address, ",")
For i = LBound(emptyAddresses) To UBound(emptyAddresses)
ws.Cells(i + 1, 2).Value = emptyAddresses(i)
Next i
End Sub
Your help and guidance here would be highly appreciated
All the best
Jacek
Here's one approach:
Sub listEmptyCells()
Const CHECK_RANGE As String = "B30:B32" 'range to locate empty cells in
Dim i As Long, r As Long, rngCheck As Range, rngEmpty As Range
Dim ws As Worksheet, wb As Workbook, wsSummary As Worksheet
Dim rwSummary As Range, s As String, c As Range
Set wb = ThisWorkbook
Set wsSummary = wb.Worksheets("Summary")
Set rwSummary = wsSummary.Range("A2:B2") 'first row of results
rwSummary.Resize(wb.Worksheets.Count).Clear 'remove previous results
For i = 1 To wb.Worksheets.Count
Set ws = wb.Worksheets(i)
If ws.Name <> wsSummary.Name Then 'exclude specific sheet(s)
s = ""
Set rngEmpty = Nothing
'which range to check - special case or use default?
Select Case ws.Name
Case "Sheet One": Set rngCheck = ws.Range("A1:A10")
Case "Sheet Two": Set rngCheck = ws.Range("G34:G56,H10")
Case Else: Set rngCheck = ws.Range(CHECK_RANGE) 'default range
End Select
'loop cells in check range
For Each c In rngCheck.Cells
If Len(c.Value) = 0 Then
If rngEmpty Is Nothing Then
Set rngEmpty = c
Else
Set rngEmpty = Application.Union(rngEmpty, c)
End If
End If
Next c
If Not rngEmpty Is Nothing Then
s = rngEmpty.Count & " required cell(s) not filled:" & _
rngEmpty.Address(False, False)
End If
With rwSummary 'record results
.Cells(1).Value = ws.Name
.Cells(2).Value = IIf(s <> "", s, "OK")
.Font.Color = IIf(s <> "", vbRed, vbGreen)
End With
Set rwSummary = rwSummary.Offset(1, 0) 'next summary row
End If
Next i
End Sub

How can I create a chart from unique values in a range

I have items that are being populated on a worksheet via userform. When I open the workbook I'm trying to get the tool to go to the sheet grab the data and generate a chart/dashboard on the main landing sheet.
In the range of data contains statuses. I want VBA to look through one column of data and create a chart that counts each different status and put that in a bar chart.
yaxis = the different statuses
xaxis = count
my code so far
Sub populatecharts()
Dim ws As Worksheet
Dim ch As Chart
Dim tablerng As Range
Dim rng1 As Range
Dim rng2 As Range
Dim rng3 As Range
Dim sh As String
Set ws = ActiveSheet
'When the workbook opens it should always check the data and populate the BA Dashboard
'I need to check for sheets and if they exist generate a chart from the data
sh = "Action"
On Error Resume Next
Worksheets("Action").Visible = True
If CheckSheetExist(sh) = False Then
GoTo nextchart1
Else
Worksheets(sh).Activate
'Set ws = ActiveSheet
Set rng1 = Range("G4", Range("G4", "G4").End(xlDown))
rng1.Select
'Set rng2 = Range("B2")
'Set rng3 = Range("C3")
'Set tablerng = rng1 '& rng2 & rng3
Set ch = ws.Shapes.AddChart2(Width:=200, Height:=200, Left:=Range("B4").Left, Top:=Range("B4").Top).chart
With ch
.SetSourceData Source:=rng1
.ChartType = xlBarClustered
.ChartTitle.Text = "Action Items by Status"
End With
ws.Activate
Worksheets("Action").Visible = False
End If
Seems easy but I'm not able to think through it, also the location is hit or miss even though I define the top and bottom and size. Sometimes it's to the right of the cell I chose to be the left.
Try the next way, please. It uses a dictionary to extract the unique values and their count and array to feed the necessary series. Try running it on active sheet and adapt it to your situation only after having the confirmation that what it returns is what you need:
Sub populatecharts()
Dim shT As Worksheet, ch As Chart, lastRow As Long
Dim arrY, arrX, i As Long, dict As Object
Set shT = ActiveSheet 'use here the sheet you need
lastRow = shT.Range("G" & shT.Rows.count).End(xlUp).row
arrX = shT.Range("G4:G" & lastRow).Value 'put the range in a array
Set dict = CreateObject("Scripting.Dictionary") 'needed for the next step
On Error Resume Next
shT.ChartObjects("MyChartXY").Delete 'for the case of re running need
On Error GoTo 0
For i = 1 To UBound(arrX)
If Not dict.Exists(arrX(i, 1)) Then
dict(arrX(i, 1)) = 1 'create the unique keys
Else
dict(arrX(i, 1)) = dict(arrX(i, 1)) + 1 'increment the key next occurrrence
End If
Next i
arrX = dict.Keys: arrY = dict.Items 'extract the necessary arrays
Set ch = shT.ChartObjects.Add(left:=shT.Range("B4").left, _
top:=shT.Range("B4").top, width:=200, height:=200).Chart
With ch
.ChartType = xlBarClustered
.HasTitle = True
.ChartTitle.Text = "Action Items by Status"
.SeriesCollection.NewSeries.Values = arrY 'feed it with the array elements
.SeriesCollection(1).XValues = arrX 'feed it with the array elements
End With
End Sub
Please, test it and send some feedback.

Creating a named range based on EXACT string values in a cell

I have the following code that goes through values that I have in column C. When it finds the word "Search", the code creates a named range called "Search" for columns D to F.
Dim featuresRng As Range
Dim rng As Range
Dim sht As Worksheet
Dim counter As Long
Dim cell As Range
Set sht = ThisWorkbook.Worksheets("Features")
Set featuresRng = sht.Range(sht.Range("C1"), sht.Range("C" & sht.Rows.Count).End(xlUp)) 'dynamically set the range of features
counter = 0 'this counter will help us avoid Union(Nothing, some range), which would give an error
For Each cell In featuresRng 'loop through the range of features
If cell.Value = "Query Builder" Then
counter = counter + 1
If counter = 1 Then
Set rng = sht.Range(cell.Offset(0, 1), cell.Offset(0, 3))
Else
Set rng = Union(rng, sht.Range(cell.Offset(0, 1), cell.Offset(0, 3))) 'build the range
End If
End If
Next cell
Debug.Print rng.Address
ThisWorkbook.Names.Add "QuBuild", rng
However, whilst it appears fine on the Name Manager, when I use VBA to transition the range into a table in word, it seems to paste ALL values where the word "Search" is in column C.
Note, in column C I have a variety of cell values such as "Reporting", "Search", "Search and Filter", "Search and Analyse" etc. So, the range that is pasted into word appears to include the rows from the "Search and.." cells too.
This is the code I am using to export to a bookmarked position in word...
On Error Resume Next
Set WordApp = GetObject(class:="Word.Application")
'Clear the error between errors
Err.Clear
If WordApp Is Nothing Then Set WordApp = CreateObject(class:="Word.Application")
'Handle if the Word Application is not found
If Err.Number = 429 Then
MsgBox "Microsoft Word could not be found, aborting."
GoTo EndRoutine
End If
On Error GoTo 0
'Make MS Word Visible and Active
WordApp.Visible = True
WordApp.Activate
'Create a New Document
Set myDoc = WordApp.Documents.Open("Doc1")
'Copy Excel Table Range
'Copy and Paste Search into MS Word
If DoesNameRangeExist("Search") = True Then
Search.Copy
myDoc.Bookmarks("Search").Range.PasteExcelTable _
LinkedToExcel:=False, _
WordFormatting:=False, _
RTF:=False
End If
Any help is, as always, much much appreciated!
The issues observed in the code
for opening New document in the word try something like `Set myDoc = Wordapp.Documents.Add'
Don't know what is DoesNameRangeExist, if is some sub routine the that is not produced in the code
The name of the NamedRange in question is "QuBuild" but you have used Name "Search"
Try following modification to make it work (as tested by me to work).
Add reference to Microsoft Word XX Object Library
in declaration section
Dim Nm As Name
Dim Wordapp As Word.Application
Dim myDoc As Word.Document
Finally in last section
Set myDoc = Wordapp.Documents.Add
For Each Nm In ThisWorkbook.Names
If Nm.Name = "QuBuild" Then
Nm.RefersToRange.Copy
Debug.Print Nm.RefersToRange.Address
myDoc.Bookmarks.Add "Search", myDoc.Range
myDoc.Range.PasteExcelTable False, False, False
Exit For
End If
Next Nm
Edit: However on trial it was observed that PasteExcelTable method is copying all the adjacent rows in between union ranges non-adjacent rows. May refer to the SO Post however answer in the post does not solve the problem and finally resorted to lengthy way below for correct operation. Invite & eager to learn more Simple Solutions to directly copy paste Union range from excel into Word from experts.
Code:
Set myDoc = Wordapp.Documents.Add
Dim RngtoCopy As Range
Dim xArea As Range, Rw As Range, col As Long, Tbl As Table
Dim TotalRow As Long
'If both the section of code are in the same procedure
'then it is not be necessary to use next loop to find NamedRange
'And can be directly refer as Rng.Areas instead of RngtoCopy.Areas
For Each Nm In ThisWorkbook.Names
If Nm.Name = "QuBuild" Then
Set RngtoCopy = Nm.RefersToRange
Exit For
End If
Next Nm
Set Tbl = myDoc.Tables.Add(myDoc.Range, 1, 3)
TotalRow = 0
For Each xArea In RngtoCopy.Areas
For Each Rw In xArea.Rows
TotalRow = TotalRow + 1
If TotalRow > Tbl.Rows.Count Then Tbl.Rows.Add
col = 0
For Each cell In Rw.Columns
col = col + 1
Tbl.cell(TotalRow, col).Range.Text = cell.Value
Next
Next
Next

Select the first filtered cell then move onto the next filtered cell down

I have an Excel spreadsheet that has contact details, for example:
A B C D E
1 Select who you would to like to email: * Drop down list *
2 Name: Company: Role: Email Address1: Email Address2:
3 Michael Jackson Jackson 5 Singer MJ#J5.com Michael#J5.com
4 Brian May Queen Guitarist BM#Queen.com Brian#Queen.com
5 Kurt Cobain Nirvana Singer KC#Nirvana.com Kurt#Nirvana.com
6 Freddie Mercury Queen Singer FM#Queen.co.uk Freddie#Queen.com
7 Pat Smear Nirvana Guitarist PS#Foo.com Pat#Foo.com
A user selects an email address using the drop down list in D1 then runs a macro that gets the email addreses in that column.
The problem is when a user applies a filter, say all guitarists, it will select the first filtered row (C4) and then go to the next row rather than the next filtered row, so it would go to C5.
This is an adaption of the code:
Sub SendEmail()
Dim objOutlook As Object
Dim objMail As Object
Dim RowsCount As Integer
Dim Index As Integer
Dim Recipients As String
Dim Category As String
Dim CellReference As Integer
Set objOutlook = CreateObject("Outlook.Application")
Set objMail = objOutlook.CreateItem(0)
RowsCount = ActiveSheet.AutoFilter.Range.Columns(1).SpecialCells(xlCellTypeVisible).Cells.Count - 1
Category = Range("D1")
Dim RowLimit As String
If Category = "Email Address1" Then
CellReference = 4
ElseIf Category = "Email Address2" Then
CellReference = 5
End If
Index = 0
While Index < RowsCount
Set EmailAdrs = ActiveSheet.AutoFilter.Range.Offset(1).SpecialCells(xlCellTypeVisible).Cells(1, CellReference).Offset(0 + Index, 0)
Recipients = Recipients & EmailAdrs.Value & ";"
Index = Index + 1
Wend
With objMail
.To = Recipients
.Subject = "This is the subject"
.Display
End With
Set objOutlook = Nothing
Set objMail = Nothing
End Sub
I tried looping through rows that are hidden:
While Index < RowsCount
Do While Rows(ActiveCell.Row).Hidden = True
'ActiveCell.Offset(1).Select
Set EmailAdrs = ActiveSheet.AutoFilter.Range.Offset(1).SpecialCells(xlCellTypeVisible).Cells(1, CellReference).Offset(0 + Index, 0)
Recipients = Recipients & EmailAdrs.Value & ";"
Index = Index + 1
ActiveCell = ActiveCell.Offset(0 + Index, 0).Select
Loop
Wend
I tried going through only cells that are visible.
I tried ideas from VBA Go to the next filtered cell:
If ActiveSheet.FilterMode = True Then
With ActiveSheet.AutoFilter.Range
For Each a In .Offset(1).Resize(.Rows.Count).SpecialCells(xlCellTypeVisible).Areas
Recipients = Recipients & a(1, CellReference) & ";"
Next
End With
MsgBox Replace(Recipients, ";;", vbNullString)
End If
And:
Dim Rng As Range
If Category = Range("S2") Then
CellReference = 10
'Set your range
Set Rng = Range("A1:B2")
ElseIf Category = Range("S3") Then
CellReference = 14
'Set your range
Set Rng = Range("C1:D2")
ElseIf Category = Range("S4") Then
CellReference = 18
'Set your range
Set Rng = Range("F1:G2")
ElseIf Category = Range("S5") Then
CellReference = 16
'Set your range
Set Rng = Range("H1:J2")
End If
For Each mCell In ThisWorkbook.Sheets("YourSheetName").Range(Rng).SpecialCells(xlCellTypeVisible)
'Get cell address
mAddr = mCell.Address
'Get the address of the cell on the column you need
NewCellAddr = mCell.Offset(0, ColumnsOffset).Address
'Do everything you need
Next mCell
Try this code:
Sub SendEmail()
Dim objOutlook As Object
Dim objMail As Object
'Dim RowsCount As Integer
'Dim Index As Integer
Dim Recipients As String
Dim Category As String
Dim CellReference As Integer
Dim RowLimit As String
'New variables.
Dim firstRow As Long
Dim lastRow As Long
Dim cell As Excel.Range
Dim row As Long
Set objOutlook = CreateObject("Outlook.Application")
Set objMail = objOutlook.CreateItem(0)
Category = Range("D1")
If Category = "Email Address1" Then
CellReference = 4
ElseIf Category = "Email Address2" Then
CellReference = 5
End If
With ActiveSheet
'Find the first and last index of the visible range.
firstRow = .AutoFilter.Range.Offset(1).SpecialCells(xlCellTypeVisible).row
lastRow = .Cells(.Rows.Count, 1).End(xlUp).row
'Iterate through all the rows between [firstRow] and [lastRow] established before.
'Some of those rows are hidden, but we will check it inside this loop.
For row = firstRow To lastRow
Set cell = .Cells(row, CellReference)
'We are checking here if this row is hidden or visible.
'Note that we cannot check the value of property Hidden of a single cell,
'since it will generate Run-time error '1004' because a single cell cannot be
'hidden/visible - only a whole row/column can be hidden/visible.
'That is why we need to refer to its .EntireRow property first and after that we
'can check its .Hidden property.
If Not cell.EntireRow.Hidden Then
'If the row where [cell] is placed is not hidden, we append the value of [cell]
'to variable Recipients.
Recipients = Recipients & cell.Value & ";"
End If
Next row
End With
With objMail
.To = Recipients
.Subject = "This is the subject"
.Display
End With
Set objOutlook = Nothing
Set objMail = Nothing
End Sub
I believe the Hidden property of a range is what you want. The following code worked for me:
Dim row As Range
For Each row In Range("MyTable").Rows
If not row.EntireRow.Hidden Then
''' DO STUFF '''
End If
Next
I have always found that using a For Each loop is a much cleaner way to iterate through data in an excel sheet. "MyTable" was the name I gave to the range of interest but if you prefer you can just enter a the limits of the range like Range("A1:D4"). Though I think it is a better practice to use named ranges as it makes your code more readable.
EDIT: To address your comment...
If you insert a row into the middle of a named range the limits of the range automatically expand. Though if your table is going to be the only data in the worksheet you can also use the UsedRange property of a worksheet object. For instance:
Dim row As Range
For Each row In Worksheets("MySheet").UsedRange.Rows
If not row.EntireRow.Hidden Then
''' DO STUFF '''
End If
Next
If all you have is the first row of the table you can expand this range to the full table using:
dim FirstRow as Range
dim LastRow as Range
dim myTable as Range
set FirstRow = Range("A1:B1")
set LastRow = FirstRow.End(xlDown)
set myTable = Range(FirstRow, LastRow)
And then use the same For Each loop as before. Hope this helps!
For any interested in this solution, i realized that it is much more faster to test the logic of the filter in the cell value, instead of checking if the filter has the column hidden or not (in sheets with more than 10.000 rows), hence not requiring to select an entire row each time, just a single cell.
Of course, you need to know beforehand the expression for the filter, which is not dealt in this code.
For example if the filter test values less than 0.5, it is better to try:
Range("U1").Select 'The column where the filter is being applied
ActiveCell.Offset(1, 0).Select
Do Until CDbl(ActiveCell.Formula) < 0.5 'The condition applied in the filter
ActiveCell.Offset(1, 0).Select
Loop

Resources