Excel - VBA Removing Duplicates from Comboboxes - excel

I am trying to create a subroutine to delete duplicates out of comboboxes. I input a number in place of X when I called the subroutine. I keep getting an error that tells me "Object Required" when i get to the subroutine. I know that means that something is not being properly initialized, but I cannot figure out how to fix my issue. Any help would be greatly appreciated. Thank you.
Private Sub UserForm_Initialize()
'ComboBox Populate
Dim rngNext As Range
Dim myRange As Range
Dim C As Integer
With Sheets("KEY")
Set rngNext = .Range("B500").End(xlUp).Offset(1, 0)
End With
rngNext.Select
Set myRange = Range("B2", rngNext)
With ComboBox1
For Each rngNext In myRange
If rngNext <> "" Then .AddItem rngNext
Next rngNext
End With
Call RemoveDuplicates(1)
End sub
Private Sub RemoveDuplicates(X)
'Remove Duplicates
Dim i As Long
Dim j As Long
With "ComboBox" & X
For i = 0 To .ListCount + 1 'Getting object required error in this line
For j = .ListCount To (i + 1) Step -1
If .List(j) = .List(i) Then
.RemoveItem j
End If
Next
Next
End With
End Sub
Final Code
Everything works great for removing duplicates.
Public allCBoxes As Collection
Private Sub UserForm_Initialize()
Set allCBoxes = New Collection
allCBoxes.Add ComboBox1
'ComboBox Populate
Dim rngNext As Range
Dim myRange As Range
Dim C As Integer
With Sheets("KEY")
Set rngNext = .Range("B500").End(xlUp).Offset(1, 0)
End With
rngNext.Select
Set myRange = Range("B2", rngNext)
With ComboBox1
For Each rngNext In myRange
If rngNext <> "" Then .AddItem rngNext
Next rngNext
End With
Call RemoveDuplicates(1)
End sub
Private Sub RemoveDuplicates(X)
'Remove Duplicates
Dim i As Long
Dim j As Long
With allCBoxes(X)
For i = 0 To .ListCount + 1
For j = .ListCount -1 To (i + 1) Step -1
If .List(j) = .List(i) Then
.RemoveItem j
End If
Next
Next
End With
End Sub

You get an error because you're passing a string, not an object.
Although intuitively you can think that:
"ComboBox" & X
will become, for example if x = 5,
ComboBox5
you're wrong because you're actually building a string:
"ComboBox5"
And, clearly, if you call a method of a ComboBox object on a String, you will be prompted of "Object Required".
What you want to do is impossible in VBA, where you cannot define variable names at run-time (i.e. ComboBox & X, even if not "as string", will not reference the variable ComboBox5). To reach what you want, I suggest to create a public collection:
Dim allCBoxes As Collection
then to populate it on the main procedure:
Set allCBoxes = New Collection
allCBoxes.Add ComboBox1
allCboxes.Add ComboBox2
'etc.
and finally recovering the "Xth" combobox like this:
With allCBoxes(X)
End With

If you want to reference a control using its string name, use the Controls function.
Such as:
With Controls("Combobox" & X)
Does that resolve the problem?

As mentioned in my comment above, here's a different approach towards solving the underlying problem: needing a combobox without duplicate values. This method uses a Dictionary object.
Let me know if you can adapt it to your needs, and if it works.
Private Sub UserForm_Initialize()
Dim oDictionary As Object
Dim strCellContent As String
Dim rngComboValues As Range
Dim rngCell As Range
Set rngComboValues = Range("A1:A26")
Set oDictionary = CreateObject("Scripting.Dictionary")
For Each rngCell In rngComboValues
strCellContent = rngCell.Value
If Not oDictionary.exists(strCellContent) Then
oDictionary.Add strCellContent, 0
End If
Next rngCell
For Each itm In oDictionary.keys
Me.ComboBox1.AddItem itm
Next itm
Set oDictionary = Nothing
End Sub

Related

VBA code to delete row in an Excel table (ListObject) if a specific cell (DataBodyRange) includes a specific substring

Summary. I am trying to loop through a table and delete each row if a particular substring is found in a specified column. I am specifically stuck on the line of code that finds the target text, which I know to be incorrect, but cannot find the proper syntax for what I'm trying to achieve: If tbl.DataBodyRange(rw, 10).Find(myString)
I have searched many websites and YouTube videos, and there are a few that address finding an exact value, but nothing I could find like the problem I'm trying to solve.
My code:
Sub removeTax()
Dim tbl As ListObject
Set tbl = ThisWorkbook.Sheets("Master").ListObjects("tblMaster")
Dim myString As String
myString = "Tax"
Dim rw
For rw = tbl.DataBodyRange.Rows.Count To 1 Step -1
If tbl.DataBodyRange(rw, 10).Find(myString) Then
tbl.ListRows.Delete
End If
Next
End Sub
Thank you very much for any assistance you can offer.
Delete Criteria Rows of an Excel Table (ListObject)
As an alternative, this uses a method that uses AutoFilter and SpecialCells.
Usage
Sub RemoveTax()
Const CritColumn As Long = 10
Const CritString As String = "*Tax*" ' contains
Dim tbl As ListObject
Set tbl = ThisWorkbook.Worksheets("Master").ListObjects("tblMaster")
DeleteTableCriteriaRows tbl, CritColumn, CritString
End Sub
The Method
Sub DeleteTableCriteriaRows( _
ByVal Table As ListObject, _
ByVal CriteriaColumn As String, _
ByVal CriteriaString As String)
With Table
If .ShowAutoFilter Then
If .AutoFilter.FilterMode Then .AutoFilter.ShowAllData
Else
.ShowAutoFilter = True
End If
.Range.AutoFilter CriteriaColumn, CriteriaString
Dim rg As Range
On Error Resume Next
Set rg = .DataBodyRange.SpecialCells(xlCellTypeVisible)
On Error GoTo 0
.AutoFilter.ShowAllData
If Not rg Is Nothing Then rg.Delete xlShiftUp
End With
End Sub
I've corrected your approach, it checks if myString is sub-string of values in column 10
With tbl.DataBodyRange.Columns(10)
For rw = .Rows.Count To 1 Step -1
If InStr(1, .Cells(rw).Value2, myString) > 0 Then
tbl.ListRows(rw).Delete
End If
Next rw
End With
Keep in mind, you should check if tbl.DataBodyRange is not Nothing, before doing anything with it, since deleting all rows of a table makes DataBodyRange be equal to Nothing
I've decided to make a bit more efficient solution, more to my liking
Sub RemoveTaxQuicker()
Const myString = "Tax"
Dim tbl As ListObject
Set tbl = ThisWorkbook.Sheets("Master").ListObjects("tblMaster")
If tbl.DataBodyRange Is Nothing Then: Exit Sub
Dim rowsRangeString As String
Dim i As Long
Dim C10 As Variant
C10 = tbl.DataBodyRange.Columns(10).Value2
Dim rng As Range
If IsArray(C10) Then
Set rng = Nothing
For i = LBound(C10) To UBound(C10)
If InStr(1, C10(i, 1), myString) > 0 Then
If rng Is Nothing Then
Set rng = tbl.DataBodyRange.Cells(i, 1)
Else
Set rng = Union(rng, tbl.DataBodyRange.Cells(i, 1))
End If
End If
Next i
If Not rng Is Nothing Then
rng.Delete xlUp
End If
ElseIf InStr(1, C10, myString) > 0 Then
tbl.ListRows(1).Delete
End If
End Sub
This is no longer true :) You should use #VBasic2008 approach, I've tested it on 500k rows and it takes around 10 sec or so. And I had to test mine as well (was painfully long), it took ~5 mins. :)
Okay VBasic2008's solution forced me to think about this in a different way. The following solution executes almost instantly.
'works with formulas as well with some exceptions, thanks VBasic for pointing that as a potential problem
Sub RemoveTaxQuicker2()
Const myString = "Tax"
Const COLUMN = 10
Dim tbl As ListObject
Set tbl = ThisWorkbook.Sheets("Master").ListObjects("tblMaster")
If tbl.DataBodyRange Is Nothing Then: Exit Sub
Dim i As Long, j As Long
Dim count As Long
Dim sDataBody As Variant
Dim sFormulas As Variant
sDataBody = tbl.DataBodyRange.Formula
sFormulas = tbl.ListRows(1).Range.Formula
If tbl.DataBodyRange.Rows.count > 1 Then
For i = LBound(sDataBody, 1) To UBound(sDataBody, 1)
If InStr(1, sDataBody(i, COLUMN), myString) < 1 Then
count = count + 1
For j = LBound(sDataBody, 2) To UBound(sDataBody, 2)
sDataBody(count, j) = sDataBody(i, j)
Next j
End If
Next i
If count > 0 Then
For i = LBound(sFormulas, 2) To UBound(sFormulas, 2)
If Left$(sFormulas(1, i), 1) = "=" Then
sDataBody(1, i) = sFormulas(1, i)
End If
Next i
tbl.DataBodyRange.Formula = sDataBody
If tbl.ListRows.count > count Then
tbl.ListRows(count + 1).Range.Resize(tbl.ListRows.count).ClearContents
tbl.Resize tbl.Range.Resize(count + 1)
End If
End If
ElseIf InStr(1, sDataBody(1, COLUMN), myString) > 0 Then
On Error Resume Next
tbl.DataBodyRange.SpecialCells(xlCellTypeConstants).ClearContents
On Error GoTo 0
End If
End Sub
Final note: I still prefer VBasic's method, if nothing else it's much cleaner and it works when the table is full of formulas that are not auto-filled :)

How to change value of iRow depending on object value?

I am looking for the iRow value to be dependent on whether the object value.
I have 2 buttons:
Private Sub OptionButton1_Click()
End Sub
and
Private Sub OptionButton2_Click()
End Sub
If the value of button 1 is True I would like for iRow value to be 2 - for example.
If the value of button 2 is True value would be 3 - for example.
I have tried the below code but it does not seem to work
Private Sub OptionButton1_Click()
If OptionButton1.Value = True Then
iRow = 2
Main code where iRow would need to be dependent on button value.
Private Sub TextBox1_AfterUpdate()
Debug.Print ">AfterUpdate"
Dim ws As Worksheet
Dim iRow As Integer
Dim iCol As Integer
Dim sDate As String
Dim oRange As Range
Set oRange = Nothing
Set ws = Worksheets.Item("Sheet1")
sDate = Format(Now(), "dd/mm/yyyy")
Debug.Print sDate, ws.Name
Set oRange = ws.Range("A:A").Find(DateValue(sDate), , xlValues)
If Not (oRange Is Nothing) Then
iRow = oRange.Row
iCol = 3
ws.Cells(iRow, iCol).Formula = TextBox1.Value
Debug.Print Now(), iRow, iCol, TextBox1.Value
End If
End Sub
Can anyone please help?
Following proposal to solve the issue.
In the Subs OptionButton1_Click()/OptionButton2_Click() rename the Variable, call it differently as the variable you use to check if you are in the correct row in reference to the date. Lets say the variable is called xRow.
Private Sub OptionButton1_Click()
If OptionButton1.Value = True Then
xRow = 1
To make it available in all other Subs, you need to declare it as global/public.
Therefore add into a Module, not in the Code of the forms, following line at the top:
Public xRow As Integer
Finally, just modify your TextBox1_AfterUpdate():
..
If Not (oRange.Row = iRow + xRow) Then
iRow = oRange.Row + xRow
Note: the xRow is the row offset to the 1st found row date match. In the example OptionButton1_Click(), it would enter the input content one row below the 1st date match.

How to filter data in combo Box vba

I got stuck into this problem wherein i have to filter data into combo box. The list should only have unique records.
here's the code for populating records into combo box:
Private Sub UserForm_Activate()
Dim myrng As Range
Dim cl As Range
Dim sh As Worksheet
Set sh = Worksheets("Product_Master")
Set myrng = sh.Range("C2:C100000")
With Me.comBox_Purchase_Product
.Clear
For Each cl In myrng.Cells
If cl.Value <> "" Then
.AddItem cl.Value
End If
Next cl
End With
End sub
Heres the Products I am getting...now i want only unique records and to remove all duplicate.
Thanks in Advance.
Add all the values into a dictionary first. While adding, test for uniqueness with myDictionary.Exists. Then grab the unique list from the dictionary to load into the combobox list.
Private Sub UserForm_Activate()
Dim sh As Worksheet
Set sh = Worksheets("Product_Master")
Dim myrng As Range
Set myrng = sh.Range("C2:C100000")
Dim Dict As Object
Set Dict = CreateObject("Scripting.Dictionary")
Dim cl As Range
For Each cl In myrng.Cells
If cl.Value <> "" And Not Dict.exists(cl.Value) Then
Dict.Add cl.Value, 0
End If
Next cl
Me.comBox_Purchase_Product.List = Dict.Keys
End Sub
I suggest changing the event from UserForm_Activate to UserForm_Initialize, because that will avoid re-running the script too many times, but it will work in both events.

Passing 2-dimensional arrays between subroutines Excel VBA

I cannot seem to get this to work.
I want to pass a 2D-array from a subroutine to other subroutines in which a data manipulation should take place.
I am calling below subroutine from a command button on Sheet1. In Module1 I have declared the variables as public. Inside the subroutine ReadData, at the End If statement, InputArray is populated with an array consisting of numbers that the user has marked following the inputbox. After the subroutine ReadData has completed, InputArray is empty.
What is the obvious and blatantly daft thing that am I doing wrong?
** Sheet1**:
Private Sub CommandButton1_Click()
Call ReadData
End Sub
**Module1**:
Option Explicit
Option Base 1
Public MyRange, InputArray As Variant
Sub ReadData()
Set MyRange = Application.InputBox("Mark the data array", Type:=8)
If Not MyRange Is Nothing Then
InputArray = MyRange
End If
End Sub
Populate the array as follows and inspect in the locals window. By using .Value you create a 2D array from the selected sheet range. MyRange I think can be local scope and declared as Range. InputArray should probably be a local scope as well and simply passed as an argument to other subs/functions.
Public InputArray As Variant
Sub ReadData()
Dim MyRange As Range
Set MyRange = Application.InputBox("Mark the data array", Type:=8)
If Not MyRange Is Nothing Then
InputArray = MyRange.Value
Stop '<==Delete me after inspection
End If
End Sub
No need for public variables at all.
option explicit
'** Sheet1**:
Private Sub CommandButton1_Click()
dim InputArray as variant, i as long, j as long
ReadData InputArray
for i = lbound(InputArray, 1) to ubound(InputArray, 1)
for j = lbound(InputArray, 2) to ubound(InputArray, 2)
debug.print InputArray(i, j)
next j
next i
End Sub
'**Module1**:
Option Explicit
Sub ReadData(byref arr as variant)
dim myrange as range
Set MyRange = Application.InputBox("Mark the data array", Type:=8)
If Not MyRange Is Nothing Then
arr = MyRange.value
End If
End Sub
1
4
7
10
2
5
8
11
3
6
9
12
along the lines of #Jeeped 's approach, you could simplify code by turning ReadData() to a Function, as follows:
Sheet1:
Function ReadData() As Variant
On Error Resume Next
ReadData = Application.InputBox("Mark the data array", Type:=8).Value
End Function
Module1:
Option Explicit
Private Sub CommandButton1_Click()
Dim InputArray As Variant
InputArray = ReadData
If IsEmpty(InputArray) Then
MsgBox "not a valid selection", vbCritical
Else
'do your stuff with InputArray
End If
End Sub
or you could have your ReadData() function return a Boolean to flag for a successful range selection :
Sheet1:
Function ReadData(arr As Variant) As Boolean
On Error Resume Next
arr = Application.InputBox("Mark the data array", Type:=8).Value
ReadData = Not IsEmpty(arr)
End Function
Module1:
Option Explicit
Private Sub CommandButton1_Click()
Dim InputArray As Variant
If ReadData(InputArray) Then
'do your stuff with InputArray
Else
MsgBox "not a valid selection", vbCritical
End If
End Sub

Remove duplicate form combobox

I am working on a sheet that have day to day sales data. I need to summaries the data between a specific date. for this I want to use a user form with 2 combo box (I have never worked with user forms & controls ever before). I added the items into combo box by using below codes -
Private Sub UserForm_Initialize()
ComboBox1.RowSource = "A2:A6724"
ComboBox2.RowSource = "A2:A6724"
End Sub
this worked fine. But here is a problem that it is repeating the same items many time as there are many transactions in same date in the sheet.
To solve this issue I search help in internet & found a procedure, I modify that and used in my code. that's working correctly but it also has a little problem that as I click on a date from drop down list of combo box it changes the date format (i.e. if I select 10/12/2016 it shows 12-oct-2016 but it should be 10-dec-2016)
here is the code I modify actually I don't know what it does but I think is will work for me-
Private Sub UserForm_Initialize()
'ComboBox1.RowSource = "A2:A6724"
'ComboBox2.RowSource = "A2:A6724"
Dim Coll As Collection, cell As Range, LastRow As Long
Dim blnUnsorted As Boolean, i As Integer, temp As Variant
Dim SourceSheet As Worksheet
Set SourceSheet = Worksheets("Sheet1")
LastRow = SourceSheet.Cells(Rows.Count, 1).End(xlUp).Row
On Error Resume Next
Set Coll = New Collection
With ComboBox1
.Clear
For Each cell In SourceSheet.Range("A2:A" & LastRow)
If Len(cell.Value) <> 0 Then
Err.Clear
Coll.Add cell.Text, cell.Text
If Err.Number = 0 Then .AddItem cell.Text
End If
Next cell
End With
Set SourceSheet = Worksheets("Sheet1")
LastRow = SourceSheet.Cells(Rows.Count, 1).End(xlUp).Row
On Error Resume Next
Set Coll = New Collection
With ComboBox2
.Clear
For Each cell In SourceSheet.Range("A2:A" & LastRow)
If Len(cell.Value) <> 0 Then
Err.Clear
Coll.Add cell.Text, cell.Text
If Err.Number = 0 Then .AddItem cell.Text
End If
Next cell
End With
Set Coll = Nothing
Set SourceSheet = Nothing
End Sub
I will be greatly Thankful for any help.
Try following code, that use a dictionary.
Public dU1 As Object, cU1 As Variant, iU1 As Long, lrU As Long
Private Sub UserForm_Initialize()
Dim i As Integer
Set dU1 = CreateObject("Scripting.Dictionary")
lrU = Worksheets("Sheet1").Cells(Rows.Count, 1).End(xlUp).Row
cU1 = Worksheets("Sheet1").Range("A2:A" & lrU) 'Starts in second row. First row left for titles
For iU1 = 1 To UBound(cU1, 1)
dU1(cU1(iU1, 1)) = 1
Next iU1
'now dU1 has unique values from column A
For i = 0 To dU1.Count - 1
ComboBox1.AddItem dU1.Keys()(i) 'Load Combobox1 with unique values from Column A
Next
End Sub
Private Sub ComboBox1_Change()
Dim lLastRow As Long
Dim i As Integer
ComboBox2.Clear
For i = 0 To dU1.Count - 1
If CDate(ComboBox1.Value) < CDate(dU1.Keys()(i)) Then
ComboBox2.AddItem dU1.Keys()(i) 'Load Combobox2
End If
Next
End Sub

Resources