VBA - Define range variables through looping - excel

I'm wondering if it's possible to define range variables through a for..next loop?
Something like that
Public Sub DefineRanges()
Dim i As Long
Dim rngLine1, rngLine2 As Range
For i = 1 To 2
Set Replace("rngLinex", "x", i) = ThisWorkbook.Sheets("Sheet1").Range("A" & i)
Next i
End Sub
or like that
Public Sub DefineRanges()
Dim i As Long
Dim rngLine1, rngLine2 As Range
For i = 1 To 2
Set rngLine & i = ThisWorkbook.Sheets("Sheet1").Range("A" & i)
Next i
End Sub
Thank you in advance.

As #JohnColeman stated use an array of ranges:
Sub defineranges()
Dim i As Long
Dim RngArr(1 To 2) As Range
For i = 1 To 2
Set RngArr(i) = ThisWorkbook.Worksheets("Sheet1").Range("A" & i)
Next i
For i = 1 To 2
Debug.Print RngArr(i).Value
Next i
End Sub

Related

Searchable combobox not working with collection

I have a dropdown that before was being populated with values from a second sheet in my workbook using the following code:
Private Sub UserForm_Initialize()
Dim cProd As Range
Dim ws As Worksheet
Dim i As Long
Set ws = ThisWorkbook.Worksheets("DO NOT DELETE")
For Each cProd In ws.Range("ProdList")
With Me.dropProd
.AddItem cProd.Value
End With
Next cProd
Me.dropProd.SetFocus
End Sub
Then, I added the code I found here to add the searchable functionality to it, and it was working just fine.
Then I had to tweak my code to add a second dropdown that would be dependent on the first one that I had previously. To do that, I deleted that DO NOT DELETE worksheet, and created two collections to store the values for the dropdowns.
Now, my first dropdown is being populated in this code:
Sub UpdateAll()
Dim ProdID As String
Dim Prod As String
Dim TF As Boolean
Dim lRow As Long
Dim i, t, s
dropProd.Clear
dropPromo.Clear
Set ws = ThisWorkbook.Worksheets("Table View")
Set cProd = New Collection
lRow = ws.Cells(Rows.Count, 1).End(-4162).Row
For i = 13 To lRow
ProdID = ws.Cells(i, 2).Value
Prod = ws.Cells(i, 3).Value
If ProdID <> "" Then
TF = False
If cProd.Count <> 0 Then
For t = 1 To cProd.Count
If cProd(t) = ProdID & " - " & Prod Then TF = True
Next
End If
If TF = False Then cProd.Add (ProdID & " - " & Prod)
End If
Next
For s = 1 To cProd.Count
dropProd.AddItem (cProd(s))
Next
End Sub
Private Sub UserForm_Initialize()
Me.dropProd.SetFocus
UpdateAll
End Sub
This part is also doing great, the below is where I'm having trouble with:
Private Sub dropProd_Change()
Dim ProdInfo As String
Dim Promo As String
Dim q, p
dropPromo.Clear
lRow = ws.Cells(Rows.Count, 1).End(-4162).Row
If dropProd.Value <> "" Then
ProdInfo = Mid(dropProd.Value, 1, InStr(1, dropProd.Value, " - ") - 1)
End If
'Populates Promo ComboBox
For q = 13 To lRow
Promo = ws.Cells(q, 9).Value
If ws.Cells(q, 2).Value = ProdInfo Then dropPromo.AddItem Promo
Next
End Sub
The above works fine if I just select the value from the dropdown, but it breaks every time I try to search anything, and the problem is in this line ProdInfo = Mid(dropProd.Value, 1, InStr(1, dropProd.Value, " - ") - 1)
I've tried to rewrite it in another way, but it's still throwing me an error. Also, I tried to incorporate the code from the link above to see if it would work, but then I didn't know what to reference on me.dropProd.List = ????. I've tried haing this equals to the Collection I have, and of course it didn't work, and now I'm stuck on how to fix it.
I couldn't reproduce the problem with your code line ProdInfo = Mid(dropProd.Value, 1, InStr(1, dropProd.Value, " - ") - 1), it might be data related. Try this alternative ProdInfo = Trim(Split(dropProd.Value, "-")(0)) and a dictionary rather than a collection.
Option Explicit
Dim ws
Sub UpdateAll()
Dim ProdID As String, Prod As String
Dim lastrow As Long, i As Long
dropProd.Clear
dropPromo.Clear
Dim dictProd As Object, k As String
Set dictProd = CreateObject("Scripting.DIctionary")
Set ws = ThisWorkbook.Worksheets("Table View")
With ws
lastrow = ws.Cells(.Rows.Count, 1).End(xlUp).Row
For i = 13 To lastrow
ProdID = Trim(.Cells(i, 2))
If Len(ProdID) > 0 Then
Prod = Trim(.Cells(i, 3))
k = ProdID & " - " & Prod
If Not dictProd.exists(k) Then
dictProd.Add k, 1
End If
End If
Next
dropProd.List = dictProd.keys
End With
End Sub
Private Sub dropProd_Change()
Dim ProdInfo As String, Promo As String
Dim lastrow As Long, i As Long
dropPromo.Clear
If dropProd.Value <> "" Then
ProdInfo = Trim(Split(dropProd.Value, "-")(0))
'Populates Promo ComboBox
With ws
lastrow = ws.Cells(.Rows.Count, 1).End(xlUp).Row
For i = 13 To lastrow
If .Cells(i, 2).Value = ProdInfo Then
Promo = ws.Cells(i, 9).Value
dropPromo.AddItem Promo
End If
Next
End With
End If
End Sub
Private Sub UserForm_Initialize()
Me.dropProd.SetFocus
UpdateAll
End Sub

Two Dependent Combo Boxes

**Edit:** Managed to find the solution to it thanks to fellow user #Tin Bum
I'm trying to make 2 Combo Box where the the first one (Cmb1) will show only unique values from Column 1 and then (Cmb2) will show a list of values from Column 2 that are related to Column 1.
Populating the Cmb1 has been successful however the problem lies with populating Cmb2.
Column 1 Column 2
1 a
1 b
1 c
2 d
2 e
The problem lies with populating Cmb2
Private Sub UserForm_Activate()
Dim wslk As Worksheet
Set wslk = Worksheets("w1")
With wslk
t1 = .Cells(Rows.Count, "B").End(xlUp).Offset(1, 0).row
On Error Resume Next
For y = 2 To t1
Set c = .Cells(y, 2)
Set t1rng = .Range(.Cells(2, 2), .Cells(y, 2))
x = Application.WorksheetFunction.CountIf(t1rng, c)
If x = 1 Then Cmb1.AddItem c
Next y
On Error GoTo 0
End With
End Sub
Private Sub Cmb1_Change()
Dim wslk As Worksheet
Set wslk = Worksheets("w1")
Private Sub Cmb1_Change()
Dim wslk As Worksheet
Set wslk = Worksheets("w1")
Cmb2.ListIndex = -1
If Cmb1.ListIndex > -1 Then
'Currently I am stuck over here
Cmb2.List =
**Solution:**
Dim i As Integer
Cmb2.Clear
For i = 2 To wslk.Range("B" & Application.Rows.Count).End(xlUp).row
If wslk.Range("B" & i).Value = Cmb1.Value Then
Cmb2.AddItem wslk.Range("C" & i)
End If
End If
End Sub
This the bones of a solution for the Exit Event Code.
It should be Ok for hundreds of rows but may be slow for thousands of rows, also you still have to workout the 2 ranges - I've arbitrarily assigned them to fixed ranges.
On the plus side it should be simple to follow
Dim Rng1 As Range, Rng2 As Range
Dim xCel As Range, List2 As String
Rng1 = Range("A10:A20") ' whatever Range covers your Col1 Data
Rng2 = Range("B10:B20") ' whatever Range covers your Col2 Data
List2 = ""
For Each xCel In Rng2.Cells
If xCel.Offset(0, -1).Value = Combobox1.Value Then
' Add this Value to a String using VbCrLf as a Separator
List2 = IIf(List2 = "", "", List2 & vbCrLf) & CStr(xCel.Value)
End If
Next xCel
' Split the String into an Array of Values for ComboBox2
ComboBox2.List = Split(List2, vbCrLf)
It also relies on NOT HAVING CHR(13) & CHR(10) (VbCrLF) in your data
You could use a Dictionary to get your unique values and also populate this on your Initialize Sub. Making this a Public variable in the scope of the Userform will allow you to then use it later on the Change event as well to get your list values
Option Explicit
Private Uniques As Object
Private Sub UserForm_Initialize()
Dim c As Range, InputRng As Range
Dim tmp As Variant
Dim k As String
Set Uniques = CreateObject("Scripting.Dictionary")
With Worksheets("w1")
Set InputRng = .Range(.Cells(2, 2), .Cells(.Cells(.Rows.Count, 2).End(xlUp).Row, 2))
For Each c In InputRng
k = c.Value2
If Uniques.exists(k) Then
tmp = Uniques(k)
ReDim Preserve tmp(LBound(tmp) To UBound(tmp) + 1)
tmp(UBound(tmp)) = c.Offset(0, 1).Value2
Uniques(k) = tmp
Else
ReDim tmp(0)
tmp(0) = c.Offset(0, 1).Value2
Uniques.Add Key:=k, Item:=tmp
End If
Next c
Cmb1.List = Uniques.keys
End With
End Sub
Private Sub Cmb1_Change()
Cmb2.ListIndex = -1
If Cmb1.ListIndex > -1 Then
Cmb2.List = Uniques(Cmb1.Value)
End If
End Sub
Private Sub Cmb1_Change()
Dim wslk As Worksheet
Set wslk = Worksheets("w1")
Dim i As Integer
Cmb2.Clear
For i = 2 To wslk.Range("B" & Application.Rows.Count).End(xlUp).row
If wslk.Range("B" & i).Value = Cmb1.Value Then
Cmb2.AddItem wslk.Range("C" & i)
End If

VBA. To see which Column A entries do not appear in Column B

I am trying to write VBA code to determine which entries in column A do not appear in column B, and then print these entries.
Sub Checker()
Dim endrow As Integer
endrow = 8
For i = 2 To endrow
Next i
End Sub
I think the next step is to use an if statement with the worksheet function match, to first take the "345A" and compare it with each entry in Column B in turn. But haven't any success implementing.
Any help would be appreciated, thanks.
A slow but thorough way:
Sub compareColumns()
Dim r As Range
Dim s As Range
Dim firstCol As Range
Dim secCol As Range
Dim match As Boolean
Set firstCol = Range("A1:A8")
Set secCol = Range("B1:B8")
For Each r In firstCol
match = False
For Each s In secCol
If r.Value = s.Value Then
match = True
Exit For
End If
Next s
Debug.Print r.Address & "has a match = " & match
Next r
End Sub
Option Explicit
Sub Test()
Dim LR As Long, i As Long
LR = 8
For i = 2 To LR
If WorksheetFunction.CountIf(Range("A" & i), Range("B:B")) = 0 Then
Debug.Print Range("A" & i)
End If
Next i
End Sub

Loop to find cell, then using that cell reference to clear a range

I am trying to write some code, that will search the first 30 columns and rows for the words Total and Area. I am looking to store the locations of these words in a variable and then use these variables to clear a range relative to them, this then loops across all worksheets.
I have tried to use a number to letter converter that I found online to store the column number, and I think this is where my problem is coming in.
Here is the code I found online:
Function Col_Letter(lngCol As Long) As String
Dim vArr
vArr = Split(Cells(1, lngCol).Address(True, False), "$")
Col_Letter = vArr(0)
End Function
and my code:
Private Sub Clear_Click()
Dim LastRowH As Integer
Dim ClearContent As Boolean
Dim ws As Worksheet
Dim testrange As Range
Dim Cell1 As Range
Dim Celln As Range
ClearContent = False
For Each ws In ActiveWorkbook.Worksheets
'FINDS RANGE
For i = 1 To 30
For j = 1 To 30
If ActiveWorkbook.Sheets(ws).Range(Col_Letter(CLng(i)) & j).Value = "Total" Then
Cell1 = ws.Range(Col_Letter(CLng(i + 1)) & j)
End If
If ActiveWorkbook.Sheets(ws).Range(Col_Letter(CLng(i)) & j).Value = "Area" Then
Celln = ws.Range(Col_Letter(CLng(i + 1)) & j - 1)
End If
Next
Next
'...<more code here>...
If ClearContent = True Then
'...<more code here>...
ws.Range(Cell1 & ":" & Celln).ClearContents
End If
Next ws
End Sub
When I run the code, I get the error message:
Run-time error '13': Type Mismatch
I have tried a couple of other methods but cannot get it to work.
Any help is appreciated, Thanks in advance :)
UPDATE
I have tried replacing the for loops in the code to use the "Cells" function, as follows:
For i = 1 To 30
For j = 1 To 30
If Sheets(ws).Cells(j, i).Value = "Total" Then
Set Cell1 = ws.Cells(j - 1, i + 1)
End If
If Sheets(ws).Cells(j, i).Value = "Area" Then
Set Celln = ws.Cells(j, i + 1)
End If
Next
Next
But I am still receiving the Type Mismatch
Your Type Mismatch is due ActiveWorkbook.Sheets(ws).Range ws is a worksheet, not an index or name. ws.range will scan the ranges of that worksheet. Few other modifications have been made see comments.
Function Col_Letter(lngCol As Long) As String
Dim vArr
vArr = Split(Cells(1, lngCol).Address(True, False), "$")
Col_Letter = vArr(0)
End Function
Sub test()
Dim LastRowH As Integer
Dim ClearContent As Boolean
Dim ws As Worksheet
Dim testrange As Range
Dim Cell1 As Range
Dim Celln As Range
ClearContent = False
For Each ws In ActiveWorkbook.Worksheets
'FINDS RANGE
For i = 1 To 30
For j = 1 To 30
If ws.Range(Col_Letter(CLng(i)) & j).Value = "Total" Then
Set Cell1 = ws.Range(Col_Letter(CLng(i + 1)) & j) ' Set This
End If
If ws.Range(Col_Letter(CLng(i)) & j).Value = "Area" Then
Set Celln = ws.Range(Col_Letter(CLng(i + 1)) & j - 1) ' Set This
End If
Next
Next
'...<more code here>...
'ClearContent = True ' Me Testing
If ClearContent = True Then
'...<more code here>...
Cell1.ClearContents
Celln.ClearContents
'ws.Range(Cell1 & ":" & Celln).ClearContents ' don't think this will work properly
End If
Next ws
End Sub

Excel 2013 Overflow due to lack of VBA optimization

I would like to export data from a consolidated sheet (DATA) to multiple sheets regarding criteria.
I have a total of 13 criteria, each criteria has to be exported in its dedicated sheet.
I'm trying to optimize this macro (only 2 criteria here) because it lag out
Sub copy()
Application.ScreenUpdating = False
Dim i As Long
Dim j As Long
Dim sh As Worksheet
Dim feuillePrincipale As Worksheet
Dim S01Sheet As Worksheet
Dim S02Sheet As Worksheet
Set feuillePrincipale = ThisWorkbook.Sheets("DATA")
Set S01Sheet = ThisWorkbook.Sheets("S01")
Set S02Sheet = ThisWorkbook.Sheets("S02")
For Each sh In ThisWorkbook.Worksheets
If sh.Name = "S01" Then
i = 2
j = 2
While Not IsEmpty(feuillePrincipale.Cells(i, 1))
If feuillePrincipale.Cells(i, 11).Value Like "S01*" Then
feuillePrincipale.Cells.Rows(i).EntireRow.copy S01Sheet.Rows(j)
j = j + 1
End If
i = i + 1
Wend
End If
If sh.Name = "S02" Then
i = 2
j = 2
While Not IsEmpty(feuillePrincipale.Cells(i, 1))
If feuillePrincipale.Cells(i, 11).Value Like "S02*" Then
feuillePrincipale.Cells.Rows(i).EntireRow.copy S02Sheet.Rows(j)
j = j + 1
End If
i = i + 1
Wend
End If
Next
Application.ScreenUpdating = True
End Sub
If you have any idea, I read I can use Advanced filter but as you guess I'm new in VBA so I'm listening any tips!
Here is the Advanced Filter method you asked for:
Public Sub Christophe()
Const FILTER_COLUMN = 11
Dim i&, rCrit As Range, rData As Range, aShts
aShts = ["SO"&row(1:13)]
Set rData = Sheets("DATA").[a1].CurrentRegion
Set rCrit = rData.Resize(2, 1).Offset(, rData.Columns.Count + 2)
rCrit(1) = rData(1, FILTER_COLUMN)
For i = 1 To UBound(aShts)
rCrit(2) = aShts(i, 1) & "*"
rData.AdvancedFilter xlFilterCopy, rCrit, Sheets(aShts(i, 1)).[a1].Resize(, rData.Columns.Count)
Next
rCrit.Clear
End Sub
The execution time should be instantaneous.
Note: this assumes that you do have 13 criteria, each starting with "SO" and that they occupy column 11 of the Data sheet. It also assumes that you already have 13 sheets named SO1... SO13 in the workbook.
UPDATE
Based on new information that the pattern of the criteria can change, please try this version instead. Note, that it assumes that the sheets already exist and that the sheet names match the criteria:
Public Sub Christophe()
Const FILTER_COLUMN = 11
Dim i&, rCrit As Range, rData As Range, aShts
aShts = Array("SO1", "SO2", "ADQ03", "LocS10")
Set rData = Sheets("DATA").[a1].CurrentRegion
Set rCrit = rData.Resize(2, 1).Offset(, rData.Columns.Count + 2)
rCrit(1) = rData(1, FILTER_COLUMN)
For i = 0 To UBound(aShts)
rCrit(2) = aShts(i) & "*"
rData.AdvancedFilter xlFilterCopy, rCrit, Sheets(aShts(i)).[a1].Resize(, rData.Columns.Count)
Next
rCrit.Clear
End Sub
Try using an array to set your criteria sheets:
Dim shArray As Variant
Dim shArrayString As String
Dim feuillePrincipale As Excel.Worksheet
Dim i As Long
Dim j As Long
Set feuillePrincipale = ThisWorkbook.Sheets("DATA")
j = 1
'// Create array and populate
shArray = Array("S01", "S02", "S03", "S04") '// add as required
'// Create string representation of array
shArrayString = "{"""
For i = LBound(shArray) To UBound(shArray)
shArrayString = shArrayString & shArray(i) & ""","""
Next
shArrayString = Left(shArrayString, Len(shArrayString) - 2) & "}"
'//Start loop
With feuillePrincipale
For i = 2 To .Cells(.Rows.Count, 1).End(xlUp).Row
If Not Evaluate("ISERROR(MATCH(" & Left(.Cells(i, 11), 3) & "," & shArrayString & ",0))") Then
.Rows(i).Copy Sheets(shArray(WorksheetFunction.Match(Left(.Cells(i, 11), 3), shArray, 0))).Cells(j, 1)
j = j + 1
End If
Next
End With
It's a bit unclear because if you follow the code you've posted - it's actually just copying and pasting data to the same sheet...
Yes, you should use an autofilter and use a special select to get only the visible cells.
If you want the loop method, you should loop through each row on sheets("DATA") and use a Select Case Statement to decide onto which sheet the data is placed.
By looping through each sheet you are adding loops that will slow it down.
Application.ScreenUpdating = False
Dim i As Long
Dim j As Long
Dim cel As Range
Dim sh As Worksheet
Dim feuillePrincipale As Worksheet
Dim S01Sheet As Worksheet
Dim S02Sheet As Worksheet
Set feuillePrincipale = ThisWorkbook.Sheets("DATA")
Set S01Sheet = ThisWorkbook.Sheets("S01")
Set S02Sheet = ThisWorkbook.Sheets("S02")
For Each cel In feuillePrincipale.Range(feuillePrincipale.Range("A1"), feuillePrincipale.Range("A1").End(xlDown))
Select Case Left(cel.offset(,10).value, 3)
Case "S01"
j = S01Sheet.Range("A" & Rows.count).End(xlUp).Offset(1).Row
feuillePrincipale.Cells.Rows(cel.Row).EntireRow.copy S01Sheet.Rows(j)
Case "S02"
j = S02Sheet.Range("A" & Rows.count).End(xlUp).Offset(1).Row
feuillePrincipale.Cells.Rows(cel.Row).EntireRow.copy S02Sheet.Rows(j)
'Case .... keep adding select statement till you get to the last condition
Case Else
End Select
Next cel
Application.ScreenUpdating = True

Resources