VBA case insensitive sorting within listbox - vba

I have the code below which is used to remove duplicates and sort the values alphabetically into a listbox within a userform but it is prioritizing uppercase over alphabetical and I would like it to ignore the case of the text
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("Groups")
'///////////////////////////////////////////////////////
'Populate the ListBox with unique Make items from column A.
LastRow = SourceSheet.Cells(Rows.Count, 1).End(xlUp).Row
On Error Resume Next
Set Coll = New Collection
'Open a With structure for the ListBox control.
With ClientInput
.Clear
For Each cell In SourceSheet.Range("A2:A" & LastRow)
'Only attempt to populate cells containing a text or value.
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
blnUnsorted = True
Do
blnUnsorted = False
For i = 0 To UBound(.List) - 1
If .List(i) > .List(i + 1) Then
temp = .List(i)
.List(i) = .List(i + 1)
.List(i + 1) = temp
blnUnsorted = True
Exit For
End If
Next i
Loop While blnUnsorted = True
'Close the With structure for the ListBox control.
End With
Current
AC
AZ
ab
Desired
ab
AC
AZ

Instead of
If .List(i) > .List(i + 1) Then
use
If LCase(.List(i)) > LCase(.List(i + 1)) Then

You can use the worksheetfunction sort
This is a sample code of how to use it
Sub sortRange()
Dim rg As Range: Set rg = Selection
Dim arrValues As Variant
arrValues = WorksheetFunction.Sort(rg)
rg.Offset(, 2).Resize(3).Value = arrValues
End Sub
If A1:A3 are selected the above code will write the sorted values to C1:C3
You can iterate over the array to add the items to the list

Please, try the next code. It firstly places the existing range (from A:A) in an array, sorts the range in place, places the sorted range in another array, extract unique strings (case sensitive) using a Dictionary and load the list box directly from the array using its List property. Then, places back the initial extracted array before sorting:
Sub UniqueSortLoadListBox()
Dim sh As Worksheet, lastR As Long, arr, arrSort, i As Long, dict As Object
Set sh = Worksheets("Groups")
lastR = sh.Range("A" & sh.rows.count).End(xlUp).row
arr = sh.Range("A2:A" & lastR).Value
With sh.Sort 'the fastest sorting way
.SortFields.Clear
.SortFields.Add2 Key:=sh.Range("A1:A" & lastR), _
SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
.SetRange sh.Range("A1:A" & lastR)
.Header = xlYes
.MatchCase = True
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
lastR = sh.Range("A" & sh.rows.count).End(xlUp).row ' recalculate to eliminate the empty cells
arrSort = sh.Range("A2:A" & lastR).Value 'place the sorted range in an array, for faster iteration/processing
'extract unique strings (case sensitive)
Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = BinaryCompare 'case sensitive for keys creation
For i = 1 To UBound(arrSort)
dict(arrSort(i, 1)) = 1
Next i
'load the listbox directly from an array (dictionary keys array)
With clientInput
.Clear
.List = dict.Keys
End With
'place back the array as it was before sorting and unique extracting:
sh.Range("A2").Resize(UBound(arr), 1).Value = arr
End Sub
Please, send some feedback after testing it.

Sorted Column Values to a List Box
If You Don't Have 365
In a nutshell, the code will write the values from the criteria column to a helper column, the column adjacent to the right of the used range, sort it, retrieve its values, clear its contents, and populate a list box with the retrieved unique (sorted) values.
Sub PopulateClientInput()
Const ProcTitle As String = "Populate Client Input"
' Define constants.
Const wsName As String = "Groups"
Const cCol As Long = 1
' Reference the workbook ('wb').
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Reference the worksheet ('ws').
Dim ws As Worksheet: Set ws = wb.Worksheets(wsName)
' It is assumed that the worksheet's used range consists of a valid table
' (not an Excel table) i.e. one row of headers and contiguous data below.
' Reference the expanded data range ('rg') i.e. the data range (no headers)
' and an extra helper column to the right. Retrieve the range's
' number of rows ('rCount') and columns ('cCount').
Dim rg As Range
Dim rCount As Long
Dim cCount As Long
With ws.UsedRange
rCount = .Rows.Count - 1 ' shrink
If rCount = 0 Then ' only headers or empty worksheet
MsgBox "Not enough rows.", vbExclamation, ProcTitle
Exit Sub
End If
cCount = .Columns.Count + 1 ' expand
' Note that the following cannot happen if 'cCol = 1'.
If cCount < cCol + 1 Then ' criteria column not in used range
MsgBox "Not enough columns.", vbExclamation, ProcTitle
Exit Sub
End If
' Reference the range.
Set rg = .Resize(rCount, cCount).Offset(1)
End With
' Reference the criteria column range ('crg').
Dim crg As Range: Set crg = rg.Columns(cCol)
' Store the sorted values from the criteria column range
' in a 2D one-based (one-column) array, the criteria array ('cData').
Application.ScreenUpdating = False
Dim cData() As Variant
If rCount > 1 Then ' multiple cells
' Reference the helper column range ('hrg').
Dim hrg As Range: Set hrg = rg.Columns(cCount)
' Write the values from the criteria column range
' to the helper column range.
hrg.Value = crg.Value
' Sort the helper column range.
hrg.Sort hrg, xlAscending, , , , , , xlNo
' Store the sorted values from the sorted helper column range
' in the criteria array.
cData = hrg.Value
' Clear the contents of the helper column range.
hrg.ClearContents
Else ' one cell
' Store the single value in the single element of the criteria array.
ReDim cData(1 To 1, 1 To 1): cData(1, 1) = crg.Value
End If
' Store the unique values from the criteria array in the 'keys'
' of a dictionary ('dict').
' The 'items' are irrelevant but will hold 'Empty'.
' Error values and blanks are excluded.
' Define the dictionary.
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare ' case-insensitive i.e. 'A = a'
Dim cKey As Variant ' Current Value in the Criteria Array
Dim r As Long ' Current Row in the Criteria Array
' Store the unique valus in the dictionary.
For r = 1 To rCount
cKey = cData(r, 1) ' retrieve the current value
If Not IsError(cKey) Then ' exclude error values
If Len(CStr(cKey)) > 0 Then ' exclude blanks
' Check if the current value exists in the dictionary.
' This is not necessary but will ensure that the first occuring
' string's case is used. Otherwise, the last would be used.
If Not dict.Exists(cKey) Then
dict(cKey) = Empty ' store the unique value in a 'key'
End If
End If
End If
Next r
' Populate the list box with the sorted unique values
' from the dictionary and inform.
With ClientInput
' Validate the dictionary.
If dict.Count = 0 Then
.Clear ' or not?
Application.ScreenUpdating = True
MsgBox "No valid data.", vbExclamation, ProcTitle
Exit Sub
End If
.List = dict.Keys ' 'dict.Keys' is a zero-based (1D) array
Application.ScreenUpdating = True
MsgBox "Client input populated.", vbInformation, ProcTitle
End With
End Sub

Related

VBA: Check if value from first sheet column a exists in second shift column A. If yes, then copy whole row

I'm new in VBA and actually don't know how to deal with that task. Maybe you can help me.
I have two tables in two sheets.
Table from sheet 1 is updated daily.
What I need to do is check if any value in column A (sheet 1) is in column A (sheet 2).
If yes, then do nothing.
If no, then copy whole row into the table in sheet 2.
Basing on google results I started to write some code but I stuck.
Dim source As Worksheet
Dim finaltbl As Worksheet
Dim rngsource As Range
Dim rngfinaltbl As Range
'Set Workbook
Set source = ThisWorkbook.Worksheets("Sheet 1")
Set finaltbl = ThisWorkbook.Worksheets("Sheet 2")
'Set Column
Set rngsource = source.Columns("A")
Set rngfinaltbl = finaltbl.Columns("A")
I assume that next I need to write some loop but I really don't know how it works.
Update Worksheet With Missing (Unique) Rows (Dictionary)
Adjust the values in the constants section.
Sub UpdateData()
' Source
Const sName As String = "Sheet1"
Const sFirstCellAddress As String = "A2"
' Destination
Const dName As String = "Sheet2"
Const dFirstCellAddress As String = "A2"
' Reference the destination worksheet.
Dim dws As Worksheet: Set dws = ThisWorkbook.Worksheets(dName)
Dim drg As Range
Dim dCell As Range
Dim drCount As Long
' Reference the destination data range.
With dws.Range(dFirstCellAddress)
Set dCell = .Resize(dws.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
If dCell Is Nothing Then Exit Sub ' no data in column range
drCount = dCell.Row - .Row + 1
Set drg = .Resize(drCount)
End With
Dim Data As Variant
' Write the values from the destination range to an array.
If drCount = 1 Then
ReDim Data(1 To 1, 1 To 1): Data(1, 1) = drg.Value
Else
Data = drg.Value
End If
' Write the unique values from the array to a dictionary.
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare
Dim Key As Variant
Dim dr As Long
For dr = 1 To drCount
Key = Data(dr, 1)
If Not IsError(Key) Then ' exclude errors
If Len(Key) > 0 Then ' exclude blanks
dict(Key) = Empty
End If
End If
Next dr
' Reference the source worksheet.
Dim sws As Worksheet: Set sws = ThisWorkbook.Worksheets(sName)
Dim srg As Range
Dim sCell As Range
Dim srCount As Long
' Reference the source data range.
With sws.Range(sFirstCellAddress)
Set sCell = .Resize(sws.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
If sCell Is Nothing Then Exit Sub ' no data in column range
srCount = sCell.Row - .Row + 1
Set srg = .Resize(srCount)
End With
' Write the values from the source range to an array.
If srCount = 1 Then
ReDim Data(1 To 1, 1 To 1): Data(1, 1) = srg.Value
Else
Data = srg.Value
End If
Dim surg As Range
Dim sr As Long
' Loop through the source values...
For sr = 1 To srCount
Key = Data(sr, 1)
If Not IsError(Key) Then ' exclude errors
If Len(Key) > 0 Then ' exclude blanks
If Not dict.Exists(Key) Then ' if source value doesn't exist...
dict(Key) = Empty ' ... add it (to the dictionary)...
If surg Is Nothing Then ' and combine the cell into a range.
Set surg = srg.Cells(sr)
Else
Set surg = Union(surg, srg.Cells(sr))
End If
End If
End If
End If
Next sr
' Copy all source rows in one go below ('.Offset(1)') the last cell.
If Not surg Is Nothing Then
surg.EntireRow.Copy dCell.Offset(1).EntireRow
End If
MsgBox "Data updated.", vbInformation
End Sub
No you don't need a loop. You need the Find function for a Range
See Documentation for Find Method (Excel)
also Excel VBA Find A Complete Guide

Get Filtered records into Array Variant without looping VBA

I have 10 records in excel of which i have edited 3rd and 7th records and placing a flag/string "modified" in certain column belongs to same rows to filter while processing
Below is the code that i am working with which is fetching only the first record(3rd) and not the 7th record into array using VBA
Dim RecordsArray() As Variant
Set sht = ThisWorkbook.Sheets("RMData")
sht.Range("M1:M100").AutoFilter Field:=1, Criteria1:="Modified"
sht.Range("A2:A100").Rows.SpecialCells (xlCellTypeVisible)
col = [a2].CurrentRegion.Columns.count
lw = [a2].End(xlDown).Row
RecordsArray = Range(Cells(2, 1), Cells(lw,col)).SpecialCells(xlCellTypeVisible)
Idea is I want to get those two records without looping and searching for
"Modified" string for the edited row
When reading a Filtered Range, most likely there will be splits ranges, the rows will not be continuous, so you need to loop through the Areas of the Filtered Range.
Also, you might have a few Rows in each Area, so you should loop through the Area.Rows.
More detailed comments in my code below.
Code
Option Explicit
Sub Populated2DArrayfromFilterRange()
Dim RecordsArray() As Variant
Dim sht As Worksheet
Dim col As Long, lw As Long, i As Long
Dim FiltRng As Range, myArea As Range, myRow As Range
ReDim RecordsArray(0 To 1000) ' redim size of array to high number >> will optimize later
' set the worksheet object
Set sht = ThisWorkbook.Sheets("RMData")
i = 0 ' reset array element index
' use With statement to fully qualify all Range and Cells objects nested inside
With sht
.Range("M1:M100").AutoFilter Field:=1, Criteria1:="Modified"
.Range("A2:A100").Rows.SpecialCells (xlCellTypeVisible)
col = .Range("A2").CurrentRegion.Columns.Count
lw = .Range("A2").End(xlDown).Row
' set the filtered range
Set FiltRng = .Range(.Cells(2, 1), .Cells(lw, col)).SpecialCells(xlCellTypeVisible)
' Debug.Print FiltRng.Address(0, 0)
For Each myArea In FiltRng.Areas ' <-- loop through areas
For Each myRow In myArea.Rows ' <-- loop through rows in area
RecordsArray(i) = Application.Transpose(Application.Transpose(myRow))
i = i + 1 ' raise array index by 1
Next myRow
Next myArea
ReDim Preserve RecordsArray(0 To i - 1) ' optimize array size to actual populated size
End With
End Sub
If you have a hidden row in the middle, then .SpecialCells(xlCellTypeVisible) will return multiple Areas. Assigning a range to an Array only assigns the first Area. (At also always makes the array 2D)
Instead of looping & searching for "Modified", you could just loop For Each cell in the SpecialCells range and assign that to the array instead - if you plan was "no loops at all" then this is not what you want. (But, I would then have to ask you "why not?"!)
Dim RecordsArray() As Variant, rFiltered As Range, rCell As Range, lCount As Long
Set sht = ThisWorkbook.Sheets("RMData")
sht.Range("M1:M100").AutoFilter Field:=1, Criteria1:="Modified"
sht.Range("A2:A100").Rows.SpecialCells (xlCellTypeVisible)
col = [a2].CurrentRegion.Columns.Count 'This will act on ActiveSheet, not sht - is that intended?
lw = [a2].End(xlDown).Row 'In case of gaps, would "lw=sht.Cells(sht.Rows.Count,1).End(xlUp).Row" be better?
'RecordsArray = Range(Cells(2, 1), Cells(lw, col)).SpecialCells(xlCellTypeVisible)
Set rFiltered = Range(Cells(2, 1), Cells(lw, col)).SpecialCells(xlCellTypeVisible)
ReDim RecordsArray(1 To rFiltered.Cells.Count, 1) 'Mimic default assignment
lCount = 1
For Each rCell In rFiltered
RecordsArray(lCount, 1) = rCell.Value
lCount = lCount + 1
Next rTMP
Set rCell = Nothing
Set rFiltered = Nothing
If you want to avoid dealing with the visible areas mentioned already, you can try something like this
Option Explicit
Public Sub CopyVisibleToArray()
Dim recordsArray As Variant, ws As Worksheet, nextAvailable As Range
Set ws = ThisWorkbook.Worksheets("RMData")
Set nextAvailable = ws.Cells(ws.Rows.Count, "A").End(xlUp).Offset(2)
With ws.Range("M1:M100")
Application.ScreenUpdating = False
.AutoFilter Field:=1, Criteria1:="Modified"
If .Rows.SpecialCells(xlCellTypeVisible).Cells.Count > 1 Then
'copy - paste visibles in col A, under all data
ws.UsedRange.Columns("A:M").SpecialCells(xlCellTypeVisible).Copy nextAvailable
Set nextAvailable = nextAvailable.Offset(1)
nextAvailable.Offset(-1).EntireRow.Delete 'Delete the (visible) header
recordsArray = nextAvailable.CurrentRegion 'Get the cells as array
nextAvailable.CurrentRegion.EntireRow.Delete 'Delete the temporary range
End If
.AutoFilter
Application.ScreenUpdating = True
End With
End Sub
To copy just column A to array use this: ws.UsedRange.Columns("A")
To copy columns A to M use this: ws.UsedRange.Columns("A:M")

Exel VBA: Run-Time Error 13 Type Mismatch

I have the following list on Sheet1:
COLUMN A COLUMNB COLUMN C
1 ADDRESS Services(s) USED VEHICLE(S) USED
2 Address1 Service1, Service3 Vehicle1, Vehicle3, Vehicle4
3 Address2 Service1, Service4 Vehicle1, Vehicle3, Vehicle4
4 Address3 Service2, Service5 Vehicle1, Vehicle2, Vehicle5
5 Address4 Service2, Service3 Vehicle1, Vehicle6
6 Address1 Service5, Service6 Vehicle2, Vehicle5, Vehicle6
7 Address2 Service2, Service3 Vehicle2, Vehicle3
8 Address4 Service4, Service6 Vehicle1, Vehicle2, Vehicle3, Vehicle4, Vehicle5, Vehicle6
On Sheet2, I would like the following output in Column B when I enter "Address1" in cell B4
COLUMN A COLUMN B
4 Address1
12 Service1
13 Service3
14 Service5
15 Service6
16
17
50 Vehicle1
51 Vehicle2
52 Vehicle3
53 Vehicle4
54 Vehicle5
56 Vehicle6
The following is the code I am using:
Worksheet_Change Code ("Sheet2" module)
Private Sub Worksheet_Change(ByVal Target As Range)
' call Function only if modifed cell is in Column "B"
If Not IsError(Application.Match(Range("B4"), Worksheets("Google Data").Range("E1:E" & LastRow(Worksheets("Google Data"))), 0)) Then
If Not Intersect(Target, Range("B4")) Is Nothing Then
If (Target.Value <> "") Then
Application.EnableEvents = False
Call FilterAddress(Target.Value)
Else
On Error Resume Next
MsgBox Target.Address & "Cell can't be blank, Input a value first."
Err.Clear
Exit Sub
End If
End If
Else
On Error Resume Next
MsgBox "The Appointment # you entered is incorrect or does not exist. Please try again."
Err.Clear
Exit Sub
End If
Application.EnableEvents = True
End Sub
Sub FilterAddress Code (Regular module)
Option Explicit
Sub FilterAddress(FilterVal As String)
Dim LastRow As Long
Dim FilterRng As Range, cell As Range
Dim Dict As Object
'Dim ID
Dim Vehicle As Variant
Dim VehicleArr As Variant
Dim i As Long, j As Long
Dim Service As Variant
Dim ServiceArr As Variant
Dim x As Long, y As Long
Dim My_Range As Range
With Sheets("Sheet1")
' find last row with data in column "A" (Adress)
LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
Set FilterRng = .Range("A1:C" & LastRow)
.Range("A1").AutoFilter
' AutoFilter "Sheet1" according to value in "Sheet2" in Column B
FilterRng.AutoFilter Field:=1, Criteria1:=FilterVal
Set Dict = CreateObject("Scripting.Dictionary")
' create an array with size up to number of rows >> will resize it later
ReDim ServiceArr(1 To LastRow)
j = 1 ' init array counter
For Each cell In .Range("B2:B" & LastRow).SpecialCells(xlCellTypeVisible)
' read values from cell to array using the Split function
Service = Split(cell.Value, ",")
For i = LBound(Service) To UBound(Service)
Service(i) = Trim(Service(i)) ' remove extra spaces from string
If Not Dict.exists(Service(i)) Then
Dict.Add Service(i), Service(i)
' save Service Name to array >> will use it later for "Bubble-sort" and paste in "Sheet2"
ServiceArr(j) = Service(i)
j = j + 1 ' increment ServiceArr counter
End If
Next i
Next cell
' resize array up to number of actual Service
ReDim Preserve ServiceArr(1 To j - 1)
End With
Dim ServiceTmp As Variant
' Bubble-sort Service Array >> sorts the Service array from smallest to largest
For i = 1 To UBound(ServiceArr) - 1
For j = i + 1 To UBound(ServiceArr)
If ServiceArr(j) < ServiceArr(i) Then
ServiceTmp = ServiceArr(j)
ServiceArr(j) = ServiceArr(i)
ServiceArr(i) = ServiceTmp
End If
Next j
Next i
' now the "fun" part >> paste to "Sheet2"
With Sheets("Sheet2")
.Range("A1").Value = "ADDRESS"
.Range("B4").Value = FilterVal
.Range("C1").Value = "VEHICLE(S) USED"
' clear contents from previous run
.Range("B12:B17").ClearContents
.Range("B12:B" & UBound(ServiceArr) + 11) = WorksheetFunction.Transpose(ServiceArr)
End With
FilterRng.Parent.AutoFilterMode = False
With Sheets("Sheet1")
' find last row with data in column "A" (Adress)
LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
Set FilterRng = .Range("A1:C" & LastRow)
.Range("A1").AutoFilter
' AutoFilter "Sheet1" according to value in "Sheet2" in Column B
FilterRng.AutoFilter Field:=1, Criteria1:=FilterVal
Set Dict = CreateObject("Scripting.Dictionary")
' create an array with size up to number of rows >> will resize it later
ReDim VehicleArr(1 To LastRow)
y = 1 ' init array counter
For Each cell In .Range("C2:C" & LastRow).SpecialCells(xlCellTypeVisible)
' read values from cell to array using the Split function
Vehicle = Split(cell.Value, ",")
For x = LBound(Vehicle) To UBound(Vehicle)
Vehicle(x) = Trim(Vehicle(x)) ' remove extra spaces from string
If Not Dict.exists(Vehicle(x)) Then
Dict.Add Vehicle(x), Vehicle(x)
' save Vehicle Name to array >> will use it later for "Bubble-sort" and paste in "Sheet2"
VehicleArr(y) = Vehicle(x)
y = y + 1 ' increment VehicleArr counter
End If
Next x
Next cell
' resize array up to number of actual Vehicle
ReDim Preserve VehicleArr(1 To y - 1)
End With
Dim VehicleTmp As Variant
' Bubble-sort Vehicle Array >> sorts the Vehicle array from smallest to largest
For x = 1 To UBound(VehicleArr) - 1
For y = x + 1 To UBound(VehicleArr)
If VehicleArr(y) < VehicleArr(x) Then
VehicleTmp = VehicleArr(y)
VehicleArr(y) = VehicleArr(x)
VehicleArr(x) = VehicleTmp
End If
Next y
Next x
' now the "fun" part >> paste to "Sheet2"
With Sheets("Sheet2")
.Range("A1").Value = "ADDRESS"
.Range("B4").Value = FilterVal
.Range("C1").Value = "VEHICLE(S) USED"
' clear contents from previous run
.Range("B50:B55").ClearContents
.Range("B50:B" & UBound(VehicleArr) + 49) = WorksheetFunction.Transpose(VehicleArr)
End With
FilterRng.Parent.AutoFilterMode = False
End Sub
I have found that if I enter an address it will give me the desired output. If I edit B4 to change the address to another, it also works. However, when I delete cell B4, I get a message that says "Runtime error 13 Type Mismatch.
When I Debug, it brings me to the line
Call FilterAddress(Target.Value)
How can I change the code so that when cell B4 is deleted, no action is taken and a message appears asking the user to enter an address?
Something like this to include an additional check for B4's value should be enough.
If Not Intersect(Target, Range("B4")) Is Nothing Then
If (Target.Value <> "") Then
Application.EnableEvents = False
Call FilterAddress(Target.Value)
Else
MsgBox Target.Address & " can't be blank, Input a value first."
End If
End If
Just in case you like to do things in the detailed way ....
Private Sub Worksheet_Change(ByVal Target As Range)
Dim strErr As String
If Not Intersect(Target, Range("B4")) Is Nothing Then
If IsTargetValid(Target, strErr) Then
Application.EnableEvents = False
Call FilterAddress(Target.Value)
Else
MsgBox strErr
End If
End If
End Sub
Public Function IsTargetValid(rng As Range, ByRef strErr As String) As Boolean
Dim bResult As Boolean
bResult = True
If bResult And IsError(rng) Then
bResult = False
strErr = rng.Address & " contains error value."
End If
If bResult And rng.Cells.Count <> 1 Then
bResult = False
strErr = rng.Address & " contains invalid number of cells."
End If
If bResult And rng <> "" Then
bResult = False
strErr = rng.Address & " can't be blank, input a value first."
End If
'// Keep adding any other condition you want to check.
IsTargetValid = bResult
End Function
actually your Worksheet_Change() event handler works for me: if I delete cell B4, I just get the "The Appointment # you entered is incorrect or does not exist. Please try again" message. Which is fine.
maybe refactoring your code could help you debugging it
for instance you could
demand array ordering to a specific Sub like the following:
Sub OrderArray(arrayToOrder As Variant)
Dim ServiceTmp As Variant
Dim iRow As Long, iRow2 As Long
' Bubble-sort Service Array >> sorts the passed array from smallest to largest
For iRow = LBound(arrayToOrder) To UBound(arrayToOrder) - 1
For iRow2 = iRow + 1 To UBound(arrayToOrder)
If arrayToOrder(iRow2) < arrayToOrder(iRow) Then
ServiceTmp = arrayToOrder(iRow2)
arrayToOrder(iRow2) = arrayToOrder(iRow)
arrayToOrder(iRow) = ServiceTmp
End If
Next
Next
End Sub
demand the getting of unique and ordered values out of a range to a Function like the following
Function GetOrderedUniqueValuesArrayFromRange(filteredRng As Range) As Variant
Dim cell As Range
Dim arr As Variant
Dim iArr As Variant
With CreateObject("Scripting.Dictionary") '<--| create a late binded 'Dictionary' object "on the fly" - no need for adding any library references to the project
For Each cell In filteredRng
' read values from cell to array using the Split function
arr = Split(cell.value, ",")
For iArr = LBound(arr) To UBound(arr)
arr(iArr) = Trim(arr(iArr)) ' remove extra spaces from string
.item(arr(iArr)) = .item(arr(iArr)) + 1
Next
Next cell
GetOrderedUniqueValuesArrayFromRange = .Keys '<--| the dictionary keys is the wanted array, though not ordered
OrderArray GetOrderedUniqueValuesArrayFromRange '<--| order it
End With '<--| release the no more necessary 'Dictionary' object
End Function
then you can collapse your FilterAddress() sub code as follows:
Sub FilterAddress(FilterVal As String)
Dim FilterRng As Range
Dim VehicleArr As Variant
Dim ServiceArr As Variant
With Sheets("Sheet1") '<--| reference your "data" sheet
With .Range("C1", .Cells(.Rows.Count, "A").End(xlUp)) '<--| reference its columns A:C cells from row 1 down to column A last not empty one
.AutoFilter '<--| remove any previuous filter
.AutoFilter Field:=1, Criteria1:=FilterVal 'filter referenced range on its 1st column with 'FilterVal' value
With .Resize(.Rows.Count - 1).Offset(1).SpecialCells(xlCellTypeVisible) '<--| reference filtered cells skipping header row
ServiceArr = GetOrderedUniqueValuesArrayFromRange(Intersect(.Cells, .Columns(2).EntireColumn)) '<--| fill ServiceArr with unique ordered values from 2nd column of referenced range
VehicleArr = GetOrderedUniqueValuesArrayFromRange(Intersect(.Cells, .Columns(3).EntireColumn)) '<--| fill VehicleArr with unique ordered values from 3nd column of referenced range
End With
End With
.AutoFilterMode = False '<--| show all rows back
End With
' now the "fun" part >> paste to "Sheet2"
With Sheets("Sheet2")
.Range("A1").value = "ADDRESS"
.Range("B4").value = FilterVal
.Range("C1").value = "VEHICLE(S) USED"
.Range("B12:B17").ClearContents ' clear service contents from previous run
.Range("B12").Resize(UBound(ServiceArr) - LBound(ServiceArr) + 1) = WorksheetFunction.Transpose(ServiceArr)
.Range("B50:B55").ClearContents ' clear vehicle contents from previous run
.Range("B50").Resize(UBound(VehicleArr) - LBound(VehicleArr) + 1) = WorksheetFunction.Transpose(VehicleArr)
End With
End Sub
hope this could help you
let me know if you will

Use a listbox selection to select and go to a cell

enter image description hereI have a workbook that contains a different worksheet for each industry sector in the S&P 500 i.e Tech, Energy, Ect. and I created a userform with two listboxes that allow the user to first select a sector then a sub sector that is unique to that sector. The listboxes are working just fine, but now I want to create a command button that takes whatever sub sector the user selects and makes the first row of data on the active sheet containing that sub sector the active cell.
Private Sub GoToSectorButton_Click()
'Declare variables
Dim SubIndustry As String
Dim IntRow As Integer
'Set list box value equal to the variable
SubIndustry = lstSubIndustry.Value
'Locate the first occurance of the Sub Industry
IntRow = 3
'Select the row that contains
ActiveSheet.cell(SubIndustry).Select
End Sub
Private Sub UserForm_Initialize()
'declare variable
Dim shtIndustry As Worksheet
'shows Industries in lstIndustry that aren't the first set of sets
For Each shtIndustry In Application.Workbooks("VBA_Finance_Project_KEZE6983.xlsm").Worksheets
If shtIndustry.Name <> "Welcome" And shtIndustry.Name <> "Name Or Sector" And shtIndustry.Name <> "Name" And shtIndustry.Name <> "Sector" And shtIndustry.Name <> "Filter" And shtIndustry.Name <> "Master" Then
lstIndustry.AddItem (shtIndustry.Name)
End If
Next shtIndustry
'select default list box item
lstIndustry.ListIndex = 0
End Sub
Private Sub lstIndustry_Click()
'declare variables
Dim strSI As String, rngData As Range, rngCell As Range, shtSubIndustry As Worksheet
'clear list box
lstSubIndustry.Clear
'Save relevant worksheets to a vaiable so that we can use the vaiable in the rest of the program as a shortcut
Set shtSubIndustry = Application.Workbooks("VBA_Finance_Project_KEZE6983.xlsm").Worksheets(lstIndustry.ListIndex + 5)
'activate worksheet clicked
shtSubIndustry.Activate
'assign address of Industry data to rngData variable
Set rngData = Application.Workbooks("VBA_Finance_Project_KEZE6983.xlsm").ActiveSheet.Range("A3").CurrentRegion
'assign Column heading to srtSI variable
strSI = "GICS Sub Industry"
'Add the Sub Industry
For Each rngCell In rngData.Columns(14).Cells
If rngCell.Value <> strSI And rngCell.Value <> "" Then
lstSubIndustry.AddItem rngCell.Value
strSI = rngCell.Value
End If
Next rngCell
'select default list box item
lstSubIndustry.ListIndex = 0
End Sub
You should iterate through the rows that contain the subIndustry value. If the subIndustry names are in column 'A'.
Something like (warning: untested)
Dim c as Range
LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
For Each c In Range("A1:A" + LastRow).Cells
If c.Value == subIndustry Then
c.parent.activate 'Activate worksheet
c.select
Exit
End If
Next
Below function I use for my joining logic, it help identify the Row Number for matched record.
This Function is flexible can match multiple criteria.
In your case,
ActiveWindow.ScrollRow = getRowMultiMatch(Array(Range("M:M"), Range("N:N")), 1, Array(Sector,Subsector))
Function getRowMultiMatch(ByVal arrRange As Variant, ByVal startMatchOnRow As Single, ByVal arrMatchValue As Variant) As Single
'Return 0 if unable to match
'arrRange = Array of Source Range
'startMatchOnRow = 1
'arrMatchValue = Array of Value need to Match
Dim i, nRow, nStartRow, nLastRow As Single
Dim nRng, dataRng, nColRng As Range
Dim nWSD As Worksheet
Dim nValue As Variant
Set nColRng = arrRange(0)
Set nWSD = nColRng.Parent
'Start and Last (Row Number) Help define when to stop looping
nStartRow = nColRng.Cells(1).Row
If startMatchOnRow > nStartRow Then nStartRow = startMatchOnRow
nLastRow = nColRng.Cells(nColRng.Cells.Count).Row
Retry:
'Sizing nRng
Set nRng = Intersect(nColRng.EntireColumn, nWSD.Range(nWSD.Rows(nStartRow), nWSD.Rows(nLastRow)))
nValue = arrMatchValue(0)
If IsNumeric(nValue) = False Then
nValue = CStr(nValue)
nValue = Replace(nValue, "*", "~*")
End If
'Matching First Item
If IsError(Application.Match(nValue, nRng, 0)) Then
getRowMultiMatch = 0
Exit Function
Else
nRow = Application.Match(nValue, nRng, 0)
'Looping to Check if all values are match
For i = 1 To UBound(arrMatchValue) 'Start loop from 2nd Item
Set dataRng = Intersect(nWSD.Rows(nStartRow + nRow - 1), arrRange(i).EntireColumn)
If StrComp(dataRng.Value, arrMatchValue(i)) <> 0 Then
'Not Match
'Resize nRng then Retry
GoTo NotMatch
Else
'Matched
End If
Next i
'All Matched
getRowMultiMatch = nStartRow + nRow - 1
Exit Function
NotMatch:
nStartRow = nStartRow + nRow
If nStartRow > nLastRow Then
Exit Function
Else
GoTo Retry
End If
End If
End Function

Excel Moving duplicate values to new sheet

I have compiled this code from bit and pieces I have found - I am by no means an expert - more of an eager student - This code works for me but now I need to keep the first occurrence of the duplicate row to stay on the original worksheet and move only the subsequent occurrence(s) to the newly created sheet.
I am willing to redo all the code if needed but would prefer to modify the existing vba for the sake of time
Sub moveduplicates
'***************************************************************
'** This proc expects you to select all the cells in a single **
'** column that you want to check for duplicates in. If dup- **
'** licates are found, the entire row will be copied to the **
'** predetermined sheet. **
'***************************************************************
Set Rng = ActiveCell
'Sticky_Selection()
Dim s As Range
Set s = Selection
Cells.EntireColumn.Hidden = False
Worksheets.Add(After:=Worksheets(Worksheets.Count)).Name = "Duplicate Values"
Sheets("Data").Select
Range("A2").Select
Range(Selection, Selection.End(xlToRight)).Select
Selection.Copy
Sheets("Duplicate Values").Select
Range("A1").Select
ActiveSheet.Paste
s.Parent.Activate
s.Select 'NOT Activate - possibly more than one cell!
Dim ShO As Worksheet
Dim Rng1 As Range
Dim pRow As Integer
Dim c As Range, cTmp As Range
Dim found
Dim Addresses() As String
Dim a() As String
Dim p2 As Integer
Dim tfFlag As Boolean, sTmp As Variant
Set ShO = Worksheets("Duplicate Values") 'You can change this to whatever worksheet name you want the duplicates in Set Rng1 = Application.InputBox("Select a range", "Obtain Range Object", Type:=8)
MsgBox "The cells selected were " & Rng.Address 'Rng1 is all the currently selected cells
pRow = 2 'This is the first row in our output sheet that will be used to record duplicates
ReDim a(0) 'Initialize our array that holds found values
For Each c In Rng1.Cells 'Cycle through each cell in our selected range
ReDim Addresses(0) 'This array holds the cell address for our duplicates.
'We will reset the array each time we move to the next cell
Now check the array of already found duplicates.
If the current value is already there skip to next value
tfFlag = False
For Each sTmp In a
If CStr(c.Value) = sTmp Or CStr(c.Value) = "xXDeleteXx" Then 'We've already done this value, move on
tfFlag = True
Exit For
End If
Next
If Not tfFlag Then 'Remember the flag is true when we have already located the
'duplicates for this value, so skip to next value
With Rng1
Set found = .Find(c.Value, LookIn:=xlValues) 'Search entire selected range for value
If Not found Is Nothing Then 'Found it
Addresses(0) = found.Address 'Record the address we found it
Do 'Now keep finding occurances of it
Set found = .FindNext(found)
If found.Address <> Addresses(0) Then
ReDim Preserve Addresses(UBound(Addresses) + 1)
Addresses(UBound(Addresses)) = found.Address
End If
Loop While Not found Is Nothing And found.Address <> Addresses(0) 'Until we get back to the original address
If UBound(Addresses) > 0 Then 'We Found Duplicates
a(UBound(a)) = c.Value 'Record the value we found a duplicate for in an array
'ReDim Preserve a(UBound(a) + 1) 'add an empty spot to the array for next value
'ShO.Range("A" & pRow).Value = "Duplicate Rows for Value " & c.Value & _
" in Column " & c.Column & " on original sheet" 'Add a label row
'pRow = pRow + 1 'Increment to the next row
For p2 = UBound(Addresses) To 0 Step -1 'Cycle through the duplicate addresses
Set cTmp = Rng1.Worksheet.Range(Addresses(p2)) 'we just want to easily get the correct row to copy
Rng1.Worksheet.Rows(cTmp.Row).Copy ShO.Rows(pRow) 'Copy form orig to duplicates sheet
cTmp.Value = "xXDeleteXx" 'Mark for Delete the original row
pRow = pRow + 1 'Increment row counter
Next p2
'Row = pRow + 1 'This increment will give us a blank row between sets of duplicates
End If
End If
End With
End If
Next
'Now go delete all the marked rows
Do
tfFlag = False
For Each c In Rng1
If c.Value = "xXDeleteXx" Then
Rng1.Worksheet.Rows(c.Row).Delete (xlShiftUp)
tfFlag = True
End If
Next
Loop Until tfFlag = False
'AutoFit Every Worksheet Column in a Workbook
For Each sht In ThisWorkbook.Worksheets
sht.Cells.EntireColumn.AutoFit
Next sht
Application.Goto Rng
End
End Sub
Thank you very much for your time and consideration
You can use a scripting Dictionary object to keep track of duplicates:
Sub RemoveDups()
Dim c As Range, dict, rngDel As Range, rw As Long
Dim wb As Workbook
Dim shtDups As Worksheet
Dim rng1 As Range
Set rng1 = Selection 'assuming you've selected a single column of values
' from which you want to remove dups
Set wb = ActiveWorkbook
Set shtDups = wb.Worksheets.Add( _
after:=wb.Worksheets(wb.Worksheets.Count))
shtDups.Name = "Duplicate Values"
With rng1.Parent
.Range(.Range("A2"), .Range("A2").End(xlToRight)).Copy _
shtDups.Range("A1")
End With
rw = 2
Set dict = CreateObject("scripting.dictionary")
For Each c In rng1.Cells
'already seen this value?
If dict.exists(c.Value) Then
c.EntireRow.Copy shtDups.Cells(rw, 1)
rw = rw + 1
'add row to "delete" range
If rngDel Is Nothing Then
Set rngDel = c
Else
Set rngDel = Application.Union(c, rngDel)
End If
Else
'first time for this value - add to dictionary
dict.Add c.Value, 1
End If
Next c
'delete all duplicate rows (if found)
If Not rngDel Is Nothing Then
rngDel.EntireRow.Delete
End If
End Sub
Another enthusiastic amateur here!
Not really answering your question, but here is a little function I use for removing duplicate rows:
Sub RemoveDupes(TempWB As Workbook, TargetSheet As String, ConcatCols As String, DeleteTF As Boolean)
Dim Counter As Integer
Dim Formula As String
Dim RowCount As Integer
Dim StartingCol As String
Dim CurrentRow As Integer
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Remove duplicate rows on a worksheet '
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Prerequisites:
' - Data needs to start # A1
' - Data has headings in row 1
' determine number of rows to be processed
RowCount = TempWB.Sheets(TargetSheet).Cells(TempWB.Sheets(TargetSheet).Rows.Count, "A").End(xlUp).Row
' insert a column to hold the calculate unique key
TempWB.Sheets(TargetSheet).Columns("A:A").Insert Shift:=xlToRight, CopyOrigin:=xlFormatFromLeftOrAbove
' add a heading
TempWB.Sheets(TargetSheet).Cells(1, 1).Value = "Duplication Check"
' insert the unique key formula
For CurrentRow = 2 To RowCount
' start the formula string
Formula = "="
' construct the formula
For Counter = 1 To Len(ConcatCols)
' if we are on the last element, dont add another '&'
If Counter = Len(ConcatCols) Then
Formula = Formula & AddLetter(Mid(ConcatCols, Counter, 1)) & CurrentRow
Else
Formula = Formula & AddLetter(Mid(ConcatCols, Counter, 1)) & CurrentRow & "&"
End If
' Debug.Print Mid(ConcatCols, Counter, 1)'Next
' next element!
Next
' insert the newly constructed formula
TempWB.Sheets(TargetSheet).Cells(CurrentRow, "A").Formula = Formula
' next row
Next
' unfortunately we need to use explicit selection here *sigh*
TempWB.Sheets(TargetSheet).Activate
' to select the range we are going to test
TempWB.Sheets(TargetSheet).Range("A2:A" & TempWB.Sheets(TargetSheet).Cells(Rows.Count, "A").End(xlUp).Row).Select
' clock down the list flagging each dupe by changing the text color
Dim d As Object, e
Set d = CreateObject("scripting.dictionary")
For Each e In Intersect(Columns(ActiveCell.Column), ActiveSheet.UsedRange)
If e.Value <> vbNullString Then
If Not d.exists(e.Value) Then d(e.Value) = 1 Else _
e.Font.ColorIndex = 4
End If
Next
' if the delete flag is set...
If DeleteTF Then
' then go down the list deleting rows...
For CurrentRow = RowCount To 2 Step -1
' if the row has been highlighted, its time to go...
If TempWB.Sheets(TargetSheet).Cells(CurrentRow, "A").Font.ColorIndex = 4 Then
TempWB.Sheets(TargetSheet).Cells(CurrentRow, "A").EntireRow.Delete
End If
Next
' If we are deleting rows, remove the column just like we were never here
TempWB.Sheets(TargetSheet).Cells(1, "A").EntireColumn.Delete
End If
End Sub
Function AddLetter(Letter As String)
' gives you the next letter
AddLetter = Split(Cells(, Range(Letter & 1).Column + 1).Address, "$")(1)
End Function
When I get a sec I will have a go adapting this to your requirements...
This will search a specified column for duplicates, copying subsequent duplicates entries to Sheet2 and then remove them from Sheet1.
I've used the Scripting Dictionary too but you will need to add a reference to "Microsoft Scripting Runtime" for the code to work as-is. (Adding the reference will help if you want to learn about dictionaries since it adds the Dictionary to Intellitype code completion stuff)
Sub Main()
Dim SearchColumn As Integer: SearchColumn = 2 ' column to search for duplicates
Dim Source As Worksheet: Set Source = ThisWorkbook.Worksheets("Sheet1")
Dim Duplicates As Worksheet: Set Duplicates = ThisWorkbook.Worksheets("Sheet2")
Dim List As Dictionary: Set List = New Dictionary ' used to hold the first instance of unique items
Dim Data As Variant ' holds a copy of the column you want to search
Dim Count As Integer ' hold the size of said column
Dim Index As Integer ' iterator for data
Dim Item As String ' holds the current item
Count = Source.Cells(Source.Rows.Count, SearchColumn).End(xlUp).Row
Set Data = Source.Range(Source.Cells(1, SearchColumn).Address, Source.Cells(Count, SearchColumn).Address)
Application.ScreenUpdating = False
' first loop, find unique items and copy duplicates
For Index = 1 To Count
Item = Data(Index, 1)
If List.Exists(Item) = False Then
' add the item to our dictionary of items
List.Add Item, Index
Else
' add item to duplicates sheet as its a duplicate
Source.Rows(Index).Copy
Duplicates.Rows(1).Insert xlShiftDown
End If
Next Index
' second loop, remove duplicates from original sheet
For Index = Count To 1 Step -1
Item = Data(Index, 1)
If List.Exists(Item) Then
If Not List(Item) = Index Then
' the item is a duplicate and needs to be removed
Source.Rows(Index).Delete
End If
End If
Next Index
Application.ScreenUpdating = True
End Sub