Unique rows in Listview (Userform) VBA - excel

I have a table in excel and I want to have the records displayed in a userform using the listview control. The challange I'm facing is that I only want 5 columns to be displayed and only unique rows.
The code I have so far is as follows:
Private Sub LoadListView()
Dim wksSource As Worksheet
Dim rngData, rngCell As Range
Dim LstItem As ListItem
Dim RowCount, ColCount, i, j As Long
Dim CountryCol, ShippingWay, SortCode, FirstException, LastException, Performance_OK_NOK, Container,
CountSimilar, a As Integer
Set wksSource = Worksheets("Sheet3")
Set rngData = wksSource.Range("A1").CurrentRegion
Me.ListView1.ColumnHeaders.Add Text:="RowNr", Width:=70
For Each rngCell In rngData.Rows(1).Cells
If rngCell = "service_def_code" Or rngCell = "package_sort" Or rngCell = "ship_to_country_id" Or
rngCell = "first_tracking_exception_message" _
Or rngCell = "last_tracking_exception_message" Then
Me.ListView1.ColumnHeaders.Add Text:=rngCell.Value, Width:=80
End If
Next rngCell
RowCount = rngData.Rows.Count
ColCount = rngData.Columns.Count
For i = 1 To ColCount
If wksSource.Cells(1, i) = "ship_to_country_id" Then
CountryCol = i
ElseIf wksSource.Cells(1, i) = "service_def_code" Then
ShippingWay = i
ElseIf wksSource.Cells(1, i) = "package_sort" Then
SortCode = i
ElseIf wksSource.Cells(1, i) = "first_tracking_exception_message" Then
FirstException = i
ElseIf wksSource.Cells(1, i) = "last_tracking_exception_message" Then
LastException = i
ElseIf wksSource.Cells(1, i) = "performance_result" Then
Performance_OK_NOK = i
End If
Next i
j = 1
For i = 2 To RowCount
If wksSource.Cells(i, Performance_OK_NOK) = "NOK" then
Set LstItem = Me.ListView1.ListItems.Add(Text:=j)
LstItem.ListSubItems.Add Text:=rngData(i, CountryCol)
LstItem.ListSubItems.Add Text:=rngData(i, ShippingWay)
LstItem.ListSubItems.Add Text:=rngData(i, SortCode)
LstItem.ListSubItems.Add Text:=rngData(i, FirstException)
LstItem.ListSubItems.Add Text:=rngData(i, LastException)
j = j + 1
end if
next i
end sub
So what I want to do is to have only unique rows displayed and the subitems represents a row. I checked and searched for a solution, but couldn't find one which I understand. Can someone please help?

you can use a dictionary. For each row create a key with the values of the five columns. if it is not in the dictionary, add it to the dictionary, add it to the listview.
The below example creates a key from columns a, b. Adapt it so you create your key based on your five columns. below i only get "b2" once even though it appears twice in table(cols a,b)
Public Sub sAddToList()
'REQUIRES MICROSOFT SCRIPTING RUNTIME LIB, (Add using Tools->References from the VB menu)
Dim d As Dictionary
Dim rowKey As String
Dim i As Integer
Set d = New Dictionary
For i = 1 To 4
rowKey = CStr(Sheet1.Cells(i, 1).Value) + CStr(Sheet1.Cells(i, 2).Value)
If Not d.Exists(rowKey) Then
d.Add rowKey, rowKey
'add to your list view
End If
Next
End Sub
IF YOUR EXCEL SUPPORTS the UNIQUE function then there is no need for VBA.

Related

Index/Matching Multiple Line Items on to One Page with Loop

I've got a price list [Data] with up to a thousand multiple items from numerous suppliers. These are output regularly from a database to be sorted (by supplier) for easy updating of pricing and other assorted tasks.
The search criteria are selected from a list box created from the [Data].
This is then to be matched to all the line items in the [Data] and a [Catalogue] created, returning multiple cells from each line. A lot of cells on each row need to be ignored in order to re-enter the system correctly.
I've got it creating a list so far however, the matching starts at the first supplier item and continues to the end of the list, as though it doesn't match the items following after that first initial match.
It needs to pick out only the data from the selected supplier and return the required results.
The raw data is not sorted by supplier, and I am hoping to be able to do this without doing a data sort first. But even with a data sort, it runs to the end of the list.
Private Sub SupplierData_Click()
ListBoxValue = SupplierData.Text
Sheets("Catalogue").Cells(2, 27).Value = ListBoxValue
Unload Me
Dim ws1 As Worksheet
Dim ws2 As Worksheet
Dim oCell As range
Dim Match As range
Dim i As Long
Dim j As Long
i = 2
j = 0
Set ws1 = ThisWorkbook.Sheets("Catalogue")
Set ws2 = ThisWorkbook.Sheets("Data")
Set Match = ws1.Cells(2, 27)
Do While ws2.Cells(i, 1).Value <> ""
Set oCell = ws2.range("A:A").Find(What:=Match)
If Not oCell Is Nothing Then ws1.Cells(i, 2) = oCell.Offset(j, 0)
If Not oCell Is Nothing Then ws1.Cells(i, 3) = oCell.Offset(j, 1)
If Not oCell Is Nothing Then ws1.Cells(i, 4) = oCell.Offset(j, 9)
i = i + 1
j = j + 1
Loop
End Sub
Solved.
Went a different route.
Private Sub SupplierData_Click()
ListBoxValue = SupplierData.Text
Sheets("Catalogue").Cells(2, 27).Value = ListBoxValue
Unload Me
Dim ws1 As Worksheet
Dim ws2 As Worksheet
Dim oCell As range
Dim opt3 as range
Set ws1 = ThisWorkbook.Sheets("Catalogue")
Set ws2 = ThisWorkbook.Sheets("Data")
Set opt3 = ws1.Cells(1, 29)
oCell = 2
While Len(ws2.range("A" & CStr(oCell)).Value) > 0
If ws2.Cells(oCell, 1).Value = opt3 Then
ws1.Cells(oCell, 2).Value = ws2.Cells(oCell, 1)
ws1.Cells(oCell, 4).Value = ws2.Cells(oCell, 2) & " " & ws2.Cells(oCell, 8) & " " & ws2.Cells(oCell, 4)
ws1.Cells(oCell, 5).Value = ws2.Cells(oCell, 3)
ws1.Cells(oCell, 6).Value = ws2.Cells(oCell, 10)
oCell = oCell + 1
Wend
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

Excel VBA Current Range in Collection

Using the code below I have been able to obtain the indented BOM for any parent item (specified in cell D1). Screen shot below shows the indented bom in columns D, E & F obtained for item A based of the Parent / Child relationships listed in columns A and B. I would like to expand this slightly so that the associated qty of each child item is shown in column G. I was trying to obtain the address corresponding to vChild and then offset by 1 column but I have had no success.
Any ideas appreciated
Public collRoot As Collection
Sub DisplayTree()
Dim coll As Collection
Dim rParents As Range, rNode As Range
Dim rOut As Range, sRootNode As String, lRow As Long
Dim rLevels As Range, rLevel As Range
Dim level As Integer, maxLevels As Integer, cur As Integer, i As Integer
Dim h As String, counts() As Integer
Set collRoot = Nothing
Set collRoot = New Collection
Set rParents = Range("A2", Range("A2").End(xlDown))
' Store the tree in a collection
On Error Resume Next
For Each rNode In rParents
Set coll = Nothing
Set coll = collRoot(rNode.Value)
If coll Is Nothing Then collRoot.Add New Collection, rNode.Value
collRoot(rNode.Value).Add rNode.Offset(, 1).Value
Next rNode
sRootNode = Range("D1")
Range("D2") = 0
Range("F2") = sRootNode
Set rOut = Range("D2")
Call DisplayTree1(sRootNode, rOut, lRow, 1)
' Calculate Levels
Set rLevels = Range("D3:D" & Range("D3").End(xlDown).Row)
maxLevels = WorksheetFunction.Max(rLevels)
ReDim counts(1 To maxLevels)
cur = 1
For Each rLevel In rLevels
level = rLevel.Value
h = ""
counts(level) = counts(level) + 1
For i = 1 To level
h = h & "." & counts(i)
Next
h = Mid(h, 2)
For i = level + 1 To UBound(counts)
counts(i) = 0
Next
rLevel.Offset(, 1).Value = h
cur = level
Next
End Sub
Sub DisplayTree1(ByVal sParent As String, rOut As Range, _
ByRef lRow As Long, ByVal lLevel As Long)
Dim vChild, coll As Collection
On Error Resume Next
For Each vChild In collRoot(sParent)
lRow = lRow + 1
rOut.Offset(lRow, 2) = vChild
rOut.Offset(lRow, 0) = lLevel
Set coll = Nothing
Set coll = collRoot(vChild)
If Not coll Is Nothing Then Call DisplayTree1(vChild, rOut, lRow, lLevel + 1)
Next vChild
End Sub
I have elected to use a workaround using vlookups to obtain the qty values

excel vba search cell

Is it possible for excel to search strings and compare it against another array of strings on another columns
For example,
I have a series of users in A1. I want to search and check all users
in cell A1 against "list of names" in Column C.
I would like to see results something that look like this. Possible?
Regards,
Terry
I have come out with my own answers and it worked though its a bit lengthy.
Sub search()
Dim users As Variant
Dim search As Boolean
Dim j As Integer
Dim found As Integer
Dim ArraySize As Integer
Dim listSize As Integer
Dim listOfUsers As Integer
listSize = 2
search = False
'Count number of teachers in List Of Users column
listOfUsers = Range("C2:C1000").Cells.SpecialCells(xlCellTypeConstants).Count
While Cells(listSize, 1).Value <> ""
found = 0
users = Split(Cells(listSize, 1).Value, ",")
ArraySize = UBound(users, 1) ' Find array size
'Loop until each cell string is done
For i = 0 To ArraySize
j = 2
While search = False
If Trim(users(i)) = Cells(j, 3).Value Then
Cells(listSize, 2).Value = Trim(users(i))
found = found + 1
search = True
ElseIf j > listOfUsers Then
search = True
Else
j = j + 1
End If
Wend
search = False
Next i
If found <> ArraySize + 1 Then
Cells(listSize, 2).Value = "Users not found"
Else
Cells(listSize, 2).Value = "All users found"
End If
listSize = listSize + 1
Wend
End Sub
Regards,
Terry
you could use Dictionary object
Option Explicit
Sub search()
Dim usersRng As Range, cell As Range
Dim elem As Variant
Dim SearchResults As String
Dim searchResultsArray As Variant
Dim iCell As Long
Set usersRng = Range("A2", Cells(Rows.COUNT, "A").End(xlUp)) '<-- set usersRng in column "A" from row 2 down to last not empty row
ReDim searchResultsArray(1 To usersRng.COUNT) '<--| size the search result array to the actual number of cells to be processed
With CreateObject("Scripting.Dictionary") 'create and reference a 'dictionary'
'store all values from "list of users" column in reference dictionary
For Each cell In Range("C2", Cells(Rows.COUNT, "C").End(xlUp))
.Add cell.Value, Null
Next cell
For Each cell In usersRng '<--| loop through "users" column cells
SearchResults = "" '<--| initialize search results
For Each elem In Split(Replace(cell.Value, " ", ""), ",") '<--| loop through current cell users
If Not .Exists(elem) Then SearchResults = SearchResults & elem & "," '<--| if current user is not in the dictionary then update 'searchResults' string
Next elem
If SearchResults = "" Then '<--| if all users have been found...
SearchResults = "All users found" '<--| ... then set 'searchResults' accordingly
Else '<--| otherwise...
SearchResults = Left(SearchResults, Len(SearchResults) - 1) & " not found" '<--| ... add " not found" to the already built list of not found users
End If
iCell = iCell + 1 '<--| update 'searchResultsArray' index
searchResultsArray(iCell) = SearchResults '<--| update 'searchResultsArray'
Next cell
Range("B2").Resize(usersRng.COUNT).Value = Application.Transpose(searchResultsArray) '<--| write down 'searchResultsArray' from cell "B2" downwards
End With
End Sub

Normalizing Excel Grid Intersection data into a flat list

I am trying to get Excel data, which was mapped using a grid/matrix mapping into a de-normalized for so that i can enter the data into a database.
How do you copy data in a grid from one excel sheet to the other as follow illustrated below.
I was trying something like this... but as you can see, i am far off!
Sub NormaliseList(mySelection As Range)
Dim cell As Range
Dim i As Long
i = 1
For Each cell In mySelection
If cell <> "" Then
Sheets(2).Range("A" & i).Value = cell(cell.Row, 1).Value
Sheets(2).Range("B" & i).Value = cell.Value
Sheets(2).Range("C" & i).Value = cell(1, cell.Column).Value
i = i + 1
Next cell
End Sub
For Reference. I Updated my code..
Simply add the code, assign macro shortcut to the function
Select the range that contains the intersection data (not the row and column data)
Run macro (Beware, sheet 2 will have data added in normalised form)
If there are multiple headings that are needed i figured i would consolidate into one column then perform a "text to columns" after processing.
Sub NormaliseList()
' to run - assign macro shortcut to sub - Select Intersection data (not row and column headings and run)
Dim Rowname, ColumnName, IntValue As String
Dim x, cntr As Integer
Dim test As Boolean
cntr = 0
For x = 1 To Selection.Count
If Selection(x).Value <> "" Then
cntr = cntr + 1
Rowname = ActiveSheet.Cells(Selection.Cells(x).Row, Selection.Column - 1)
ColumnName = ActiveSheet.Cells(Selection.Row - 1, Selection.Cells(x).Column)
IntValue = Selection(x).Value
test = addrecord(Rowname, ColumnName, IntValue, cntr)
End If
Next x
End Sub
Function addrecord(vA, vB, vC As String, rec As Integer) As Boolean
'Make sure that you have a worksheet called "Sheet2"
Sheets("Sheet2").Cells(rec, 1) = vA
Sheets("Sheet2").Cells(rec, 2) = vB
Sheets("Sheet2").Cells(rec, 3) = vC
End Function
I've got two posts, with usable code and downloadable workbook, on doing this in Excel/VBA on my blog:
http://yoursumbuddy.com/data-normalizer
http://yoursumbuddy.com/data-normalizer-the-sql/
Here's the code:
'Arguments
'List: The range to be normalized.
'RepeatingColsCount: The number of columns, starting with the leftmost,
' whose headings remain the same.
'NormalizedColHeader: The column header for the rolled-up category.
'DataColHeader: The column header for the normalized data.
'NewWorkbook: Put the sheet with the data in a new workbook?
'
'NOTE: The data must be in a contiguous range and the
'rows that will be repeated must be to the left,
'with the rows to be normalized to the right.
Sub NormalizeList(List As Excel.Range, RepeatingColsCount As Long, _
NormalizedColHeader As String, DataColHeader As String, _
Optional NewWorkbook As Boolean = False)
Dim FirstNormalizingCol As Long, NormalizingColsCount As Long
Dim ColsToRepeat As Excel.Range, ColsToNormalize As Excel.Range
Dim NormalizedRowsCount As Long
Dim RepeatingList() As String
Dim NormalizedList() As Variant
Dim ListIndex As Long, i As Long, j As Long
Dim wbSource As Excel.Workbook, wbTarget As Excel.Workbook
Dim wsTarget As Excel.Worksheet
With List
'If the normalized list won't fit, you must quit.
If .Rows.Count * (.Columns.Count - RepeatingColsCount) > .Parent.Rows.Count Then
MsgBox "The normalized list will be too many rows.", _
vbExclamation + vbOKOnly, "Sorry"
Exit Sub
End If
'You have the range to be normalized and the count of leftmost rows to be repeated.
'This section uses those arguments to set the two ranges to parse
'and the two corresponding arrays to fill
FirstNormalizingCol = RepeatingColsCount + 1
NormalizingColsCount = .Columns.Count - RepeatingColsCount
Set ColsToRepeat = .Cells(1).Resize(.Rows.Count, RepeatingColsCount)
Set ColsToNormalize = .Cells(1, FirstNormalizingCol).Resize(.Rows.Count, NormalizingColsCount)
NormalizedRowsCount = ColsToNormalize.Columns.Count * .Rows.Count
ReDim RepeatingList(1 To NormalizedRowsCount, 1 To RepeatingColsCount)
ReDim NormalizedList(1 To NormalizedRowsCount, 1 To 2)
End With
'Fill in every i elements of the repeating array with the repeating row labels.
For i = 1 To NormalizedRowsCount Step NormalizingColsCount
ListIndex = ListIndex + 1
For j = 1 To RepeatingColsCount
RepeatingList(i, j) = List.Cells(ListIndex, j).Value2
Next j
Next i
'We stepped over most rows above, so fill in other repeating array elements.
For i = 1 To NormalizedRowsCount
For j = 1 To RepeatingColsCount
If RepeatingList(i, j) = "" Then
RepeatingList(i, j) = RepeatingList(i - 1, j)
End If
Next j
Next i
'Fill in each element of the first dimension of the normalizing array
'with the former column header (which is now another row label) and the data.
With ColsToNormalize
For i = 1 To .Rows.Count
For j = 1 To .Columns.Count
NormalizedList(((i - 1) * NormalizingColsCount) + j, 1) = .Cells(1, j)
NormalizedList(((i - 1) * NormalizingColsCount) + j, 2) = .Cells(i, j)
Next j
Next i
End With
'Put the normal data in the same workbook, or a new one.
If NewWorkbook Then
Set wbTarget = Workbooks.Add
Set wsTarget = wbTarget.Worksheets(1)
Else
Set wbSource = List.Parent.Parent
With wbSource.Worksheets
Set wsTarget = .Add(after:=.Item(.Count))
End With
End If
With wsTarget
'Put the data from the two arrays in the new worksheet.
.Range("A1").Resize(NormalizedRowsCount, RepeatingColsCount) = RepeatingList
.Cells(1, FirstNormalizingCol).Resize(NormalizedRowsCount, 2) = NormalizedList
'At this point there will be repeated header rows, so delete all but one.
.Range("1:" & NormalizingColsCount - 1).EntireRow.Delete
'Add the headers for the new label column and the data column.
.Cells(1, FirstNormalizingCol).Value = NormalizedColHeader
.Cells(1, FirstNormalizingCol + 1).Value = DataColHeader
End With
End Sub
You’d call it like this:
Sub TestIt()
NormalizeList ActiveSheet.UsedRange, 1, "Name", "Count", False
End Sub

Resources