VBA: Ignoring Conditional in For-Each Loop - vba

Problem Statement
I have a couple of dependent combo boxes for some countries and states of those countries. I am using VBA to populate unique values in the first combo box and then dynamically populate unique values in the second combo box. The code seems to be ignoring the conditional in the initial pass.
For example the code works for the first country:
But following countries incorrectly retain the first State value:
Data
This is the data set, with the Names "Country" and "State". These Names correspond dynamically to the range below each heading:
Name references use formulas in this format:
=OFFSET(Sheet1!$A$2,0,0,COUNTA(Sheet1!$A:$A),1)
Combo boxes are ActiveX objects with the names "countries" and "states" respectively.
Code
Code snippet:
Private Sub Worksheet_Activate()
'Populate combo box with unique countries.
Dim arr() As String
Dim tmp As String
Dim rng As Range
Dim ws As Worksheet
Set ws = Worksheets("Sheet1")
Me.countries.Clear
For Each rng In ws.Range("Country")
If (rng <> "") And (InStr(tmp, rng) = 0) Then
tmp = tmp & rng & "|"
End If
Next rng
If Len(tmp) > 0 Then tmp = Left(tmp, Len(tmp) - 1)
arr = Split(tmp, "|")
Me.countries.List = arr
End Sub
Private Sub countries_lostfocus()
'Populate dependent combo box with unique states
'according to selection in countries combo box.
Dim rng As Range
Dim ws As Worksheet
Dim str As String
Set ws = Worksheets("Sheet1")
str = countries.Value
Me.states.Clear
On Error Resume Next
For Each rng In ws.Range("State")
If ((rng.Offset(, -1).Value) = str) And (IsNotInArray(rng.Value, Me.states.List)) Then
Me.states.AddItem rng.Value
End If
Next rng
End Sub
Function IsNotInArray(stringToBeFound As String, arr As Variant) As Boolean
IsNotInArray = IsError(Application.Match(stringToBeFound, arr, 0))
End Function
Miscellaneous
The NSW state value will be stored in the combo box for all following countries that are added.
Using MsgBox to debug inside the loop as such:
For Each rng In ws.Range("State")
If ((rng.Offset(, -1).Value) = str) And (IsNotInArray(rng.Value, Me.states.List)) Then
MsgBox ("Country: " & str & "; check: " & rng.Offset(, -1).Value)
Me.states.AddItem rng.Value
End If
Next rng
Seems to show that the first portion of the conditional is failing to operate as expected when selecting a country other than Australia:

As much as I don't want to see NSW being left out of any lists, you can fix your problem by testing whether your arr variable is empty prior to trying to do a Match:
Function IsNotInArray(stringToBeFound As String, arr As Variant) As Boolean
If UBound(Arr) = -1 Then
IsNotInArray = True
Else
IsNotInArray = IsError(Application.Match(stringToBeFound, arr, 0))
End If
End Function
If arr is passed to that function as the cleared list of a ComboBox, it will have a LBound of 0 and an UBound of -1, so the test on the UBound will prevent the Match from crashing.

You could have use the same approach as in Country. And why don't you use the countries_Change event?
Option Explicit
Private Sub countries_Change()
Dim sCountry As String
Dim sList As String
Dim rng As Range
sCountry = Me.countries.Value
Me.states.Clear
With ThisWorkbook.Names("State")
For Each rng In .RefersToRange
If Not IsEmpty(rng) Then
If rng.Offset(0, -1).Value = sCountry Then
If InStr(1, sList, rng.Value, vbTextCompare) = 0 Then
If Len(sList) > 0 Then sList = sList & "|"
sList = sList & rng.Value
End If
End If
End If
Next
End With
Me.states.List = Split(sList, "|")
End Sub
Private Sub Worksheet_Activate()
Dim sList As String
Dim rng As Range
With ThisWorkbook.Names("Country")
For Each rng In .RefersToRange
If Not IsEmpty(rng) Then
If InStr(1, sList, rng.Value, vbTextCompare) = 0 Then
If Len(sList) > 0 Then sList = sList & "|"
sList = sList & rng.Value
End If
End If
Next
End With
Me.countries.List = Split(sList, "|")
countries_Change ' <-- This is better User experience
End Sub

Related

How to check for 2 different values and delete the text where either of these values are found?

I want to find "Ext" and "/" in a column of data and delete all the text after and including those characters
If it doesn't find those characters in my data then exit the sub
I can do them separately but I definitely over complicated it, there must be an easier way
The data column will also have blanks in so I have to avoid blank cells and check the whole range of data
Code
Sub DeleteAfterText()
Dim rngFoundCell As Range
Set rngFoundCell = Sheets("User Load").Range("E1:E3000").Find(What:="Ext")
'This is checking to see if the range contains EXT, if not it exits the sub'
If rngFoundCell Is Nothing Then 'If no cell in the range has an ' then exist sub
Exit Sub
Else
Worksheets("User Load").Range("E1000").Select 'Start from bottom'
Selection.End(xlUp).Select 'This selects the bottom to the top'
Do Until ActiveCell.Value = "Phone Number" 'This does the change until it reaches the header name'
If ActiveCell.Value = "" Then 'If the cell is blank it skips it as there is no action after the then'
Else
ActiveCell = Split(ActiveCell.Value, "Ext")(0)
'ActiveCell = Split(ActiveCell.Value, "/")(0)
End If
ActiveCell.Offset(-1, 0).Select
Loop
End If
End Sub
Sub DeleteAfterText2()
Dim rngFoundCell As Range
Set rngFoundCell = Sheets("User Load").Range("E1:E3000").Find(What:="/")
'This is checking to see if the range contains EXT, if not it exits the sub'
If rngFoundCell Is Nothing Then 'If no cell in the range has an ' then exist sub
Exit Sub
Else
Worksheets("User Load").Range("E1000").Select 'Start from bottom'
Selection.End(xlUp).Select 'This selects the bottom to the top'
Do Until ActiveCell.Value = "Phone Number" 'This does the change until it reaches the header name'
If ActiveCell.Value = "" Then 'If the cell is blank it skips it as there is no action after the then'
Else
ActiveCell = Split(ActiveCell.Value, "/")(0)
End If
ActiveCell.Offset(-1, 0).Select
Loop
End If
End Sub
This code should work. It is simple to read and easy to understand.
Option Explicit
'The calling Sub
Sub main()
DeleteTextFromColumn ActiveSheet.Range("E1:E3000")
End Sub
Sub DeleteTextFromColumn(ByRef inRange As Range)
Dim cCell As Range
Dim intPos1 As Integer
Dim intPos2 As Integer
Dim strTemp As String
Dim strOut As String
'You can specify which column if more than one column is provided to the
' subroutine. Ex: Range("E1:F3000")
For Each cCell In inRange.Columns(1).Cells
strTemp = cCell.Value
'gets the position of "ext" (case insensitive)
intPos1 = InStr(LCase(strTemp), "ext")
'gets the position of "/"
intPos2 = InStr(strTemp, "/")
strOut = strTemp
If intPos1 > 1 Then
strOut = Mid(strTemp, 1, intPos1 - 1)
ElseIf intPos2 > 1 Then
strOut = Mid(strTemp, 1, intPos2 - 1)
End If
'Outputs the results
cCell.Value = strOut
Next
End Sub
It's best to break out repeated code into a sub which has parameters for the variable parts of the operation.
You can do something like this:
Sub Tester()
Dim theRange As Range
Set theRange = Sheets("User Load").Range("E1:E3000")
RemoveTextAfter theRange, "Ext"
RemoveTextAfter theRange, "/"
End Sub
Sub RemoveTextAfter(rng As Range, findWhat As String)
Dim f As Range
If Len(findWhat) = 0 Then Exit Sub
Set f = rng.Find(What:="Ext", lookat:=xlPart)
Do While Not f Is Nothing
f.Value = Split(f.Value, findWhat)(0)
Set f = rng.Find(What:="Ext", lookat:=xlPart)
Loop
End Sub
I'm going to give you two answers for the price of one. :)
At its root, the basic logic you need to figure out if a substring exists in a given string is a standard part of VBA in the InStr function. Using this, you can break out your logic to check a cell's value and (conditionally) delete the remainder of the string into a function like this:
Private Function DeleteTextAfter(ByVal contents As String, _
ByVal token As String) As String
'--- searches the given string contents and if it finds the given token
' it deletes the token and all following characters
DeleteTextAfter = contents
Dim pos1 As Long
pos1 = InStr(1, contents, token, vbTextCompare)
If pos1 > 0 Then
DeleteTextAfter = Left(contents, pos1 - 1)
End If
End Function
Notice here that using the function created above, we don't need to use Range.Find at all.
Once you have that, your top-level logic consists of setting up the range to search. In all of my code, I explicitly create objects to reference the workbook and worksheet so that I can keep things straight. In a simple example like this, it may seem like overkill, but the habit comes in handy when your code gets more involved. So I set up the range like this
Dim thisWB As Workbook
Dim userLoadWS As Worksheet
Set thisWB = ThisWorkbook
Set userLoadWS = thisWB.Sheets("User Load")
Dim searchRange As Range
Set searchRange = userLoadWS.Range("E1:E3000")
Now the loop just goes through each cell and gets a (potentially) updated value.
Dim cell As Variant
For Each cell In searchRange
If Not cell.value = vbNullString Then
Debug.Print cell.Address & " = " & cell.value
cell.value = DeleteTextAfter(cell.value, "Ext")
cell.value = DeleteTextAfter(cell.value, "/")
End If
Next cell
So your whole solution looks like this:
Option Explicit
Public Sub TestDirectlyFromRange()
Dim thisWB As Workbook
Dim userLoadWS As Worksheet
Set thisWB = ThisWorkbook
Set userLoadWS = thisWB.Sheets("User Load")
Dim searchRange As Range
Set searchRange = userLoadWS.Range("E1:E3000")
Dim cell As Variant
For Each cell In searchRange
If Not cell.value = vbNullString Then
Debug.Print cell.Address & " = " & cell.value
cell.value = DeleteTextAfter(cell.value, "Ext")
cell.value = DeleteTextAfter(cell.value, "/")
End If
Next cell
End Sub
Private Function DeleteTextAfter(ByVal contents As String, _
ByVal token As String) As String
'--- searches the given string contents and if it finds the given token
' it deletes the token and all following characters
DeleteTextAfter = contents
Dim pos1 As Long
pos1 = InStr(1, contents, token, vbTextCompare)
If pos1 > 0 Then
DeleteTextAfter = Left(contents, pos1 - 1)
End If
End Function
But wait, there's more!!
You're iterating over 3,000 rows of data. That can get to be slow if all those rows are filled or if you increase the number of rows to search. To speed up the search, the answer is to copy the data in the range to a memory-based array first, modify any of the data, then copy the results back. This example uses the same Function DeleteTextAfter as above and is much quicker. Use whichever one fits your situation best.
Public Sub TestRangeInArray()
Dim thisWB As Workbook
Dim userLoadWS As Worksheet
Set thisWB = ThisWorkbook
Set userLoadWS = thisWB.Sheets("User Load")
'--- create the range and copy into a memory array
Dim searchRange As Range
Dim searchData As Variant
Set searchRange = userLoadWS.Range("E1:E3000")
searchData = searchRange.value
Dim i As Long
For i = LBound(searchData, 1) To UBound(searchData, 1)
If Not searchData(i, 1) = vbNullString Then
searchData(i, 1) = DeleteTextAfter(searchData(i, 1), "Ext")
searchData(i, 1) = DeleteTextAfter(searchData(i, 1), "/")
End If
Next i
'--- now copy the modified array back to the worksheet range
searchRange.value = searchData
End Sub

Check whether value exists in collection or array, and if not, add it [duplicate]

This question already has answers here:
Does VBA have Dictionary Structure?
(11 answers)
Closed 4 years ago.
I want to add a list of items to a collection and avoid adding duplicates.
Here's my list in Column A
Apple
Orange
Pear
Orange
Orange
Apple
Carrot
I only want to add
Apple
Orange
Pear
Carrot
Here's what I came up with, and it works, but it's not pretty.
dim coll as New Collection
ln = Cells(Rows.Count, 1).End(xlUp).Row
coll.Add (Cells(1, 1).Value) 'Add first item manually to get it started
For i = 1 To ln
addItem = True 'Assume it's going to be added until proven otherwise
For j = 1 To coll.Count 'Loop through the collection
'If we ever find the item in the collection
If InStr(1, Cells(i, 1), coll(j), vbTextCompare) > 0 Then
addItem = False 'set this bool false
End If
Next j
If addItem = True Then 'It never got set to false, so add it
coll.Add (Cells(i, "A").Value)
End If
Next i
Is there a less convoluted way to do it? Preferably something like
If Not coll.Contains(someValue) Then
coll.Add (someValue)
End If
I would strongly recommend using dictionaries, as they have a lot of features that collections do not, including Exists function.
With that said, it would be quite easy to create a function that first checks to see if a value exists within a collection, and then another function that will only add a value if it doesn't already exist.
Check if value exists
To see if it already exists, just use a simple for loop. If the value exists, return true and exit the function.
' Check to see if a value is in a collection.
' Functional approcah to mimic dicitonary `exists` method.
Public Function CollectionValueExists(ByRef target As Collection, value As Variant) As Boolean
Dim index As Long
For index = 1 To target.Count
If target(index) = value Then
CollectionValueExists = True
Exit For
End If
Next index
End Function
Add unique values
Using the new function CollectionValueExists it is as simple as a if conditional statement to see if it should be added or not.
To make this even more dynamic, you could also use a ParamArray to allow multiple values to be added with one call. Simply loop each value and see if it needs to be added. This does not apply to your example, but is flexible for other uses.
' Adds unique values to a collection.
' #note this mutates the origianal collection.
Public Function CollectionAddUnique(ByRef target As Collection, ParamArray values() As Variant) As Boolean
Dim index As Long
For index = LBound(values) To UBound(values)
If Not CollectionValueExists(target, values(index)) Then
CollectionAddUnique = True
target.Add values(index)
End If
Next index
End Function
Demo
Putting it all together, you can simply loop your range and call the new function.
Private Sub demoAddingUniqueValuesToCollection()
Dim fruits As Collection
Set fruits = New Collection
Dim cell As Range
For Each cell In Range("A1", Range("A" & Rows.Count).End(xlUp))
CollectionAddUnique fruits, cell.value
Next cell
End Sub
this will fill a collection of only unique:
Dim coll As New Collection
Dim ln As Long
ln = Cells(Rows.count, 1).End(xlUp).Row
Dim i As Long
For i = 1 To ln
On Error Resume Next
coll.Add Cells(i, 1).Value, Cells(i, 1).Value
On Error GoTo 0
Next i
Dim ech
For Each ech In coll
Debug.Print ech
Next ech
Here is mine
Option Explicit
Sub Test()
Dim Ln
Ln = Cells(Rows.Count, 1).End(xlUp).Row
Dim rngInput As Excel.Range
Set rngInput = Range(Cells(1, 1), Cells(Ln, 1)) '* really should qualify with a sheet otherwise you're at the mercy of activesheet
Dim dicUnique As Scripting.Dictionary '* requires Tools->Reference : Microsoft Scripting Runtime
Set dicUnique = UniqueCellContents(rngInput)
Dim vOutput As Variant
vOutput = dicUnique.Keys
Dim rngOutput As Excel.Range
Set rngOutput = Range(Cells(1, 3), Cells(dicUnique.Count, 3)) '* really should qualify with a sheet otherwise you're at the mercy of activesheet
rngOutput.Value = Application.Transpose(vOutput)
'
' Dim coll As New Collection
'
' Ln = Cells(Rows.Count, 1).End(xlUp).Row
'
' coll.Add (Cells(1, 1).Value) 'Add first item manually to get it started
' For i = 1 To Ln
'
' AddItem = True 'Assume it's going to be added until proven otherwise
'
' For j = 1 To coll.Count 'Loop through the collection
'
' 'If we ever find the item in the collection
' If InStr(1, Cells(i, 1), coll(j), vbTextCompare) > 0 Then
'
' AddItem = False 'set this bool false
'
' End If
'
' Next j
'
' If AddItem = True Then 'It never got set to false, so add it
'
' coll.Add (Cells(i, "A").Value)
'
' End If
'
' Next i
End Sub
Function UniqueCellContents(ByVal rngInput As Excel.Range) As Scripting.Dictionary
Dim dic As Scripting.Dictionary '* requires Tools->Reference : Microsoft Scripting Runtime
Set dic = New Scripting.Dictionary
Dim vValues As Variant
vValues = (rngInput)
If Not IsArray(vValues) Then
dic.Add vValues, 0
Else
Dim vLoop As Variant
For Each vLoop In vValues
If Not dic.Exists(vLoop) Then
dic.Add vLoop, 0
End If
Next vLoop
End If
Set UniqueCellContents = dic
End Function
Another method is to use a Scripting Dictionary. This does have an Exists method - the code below actually bypasses this and will overwrite an existing item if the key already exists.
Sub x()
Dim oDic As Object, r As Range
Set oDic = CreateObject("Scripting.Dictionary")
For Each r In Range("A1:A7")
oDic(r.Value) = r.Row
' if not odic.exists(r.value) then ...
Next r
MsgBox Join(oDic.keys, ",")
End Sub
If you want to check for the existence of an item in a collection (as they don't have the exist functionality of dictionaries) then I use the following snippet
Public Function InCollection(Col As Collection, key As String) As Boolean
Dim var As Variant
Dim errNumber As Long
InCollection = False
Set var = Nothing
Err.clear
On Error Resume Next
var = Col.Item(key)
errNumber = CLng(Err.Number)
On Error GoTo 0
'5 is not in, 0 and 438 represent incollection
If errNumber = 5 Then ' it is 5 if not in collection
InCollection = False
Else
InCollection = True
End If
End Function
Used such as:
If InCollection(CollectionName,IDKey) Then
Else
End If
Another way
Dim coll As New Collection
Dim i As Long
For i = 1 To Cells(Rows.count, 1).End(xlUp).Row
If Worksheetfunction.CountIf(Cells(1,1).Resize(i), Cells(i, 1).Value) = 1 Then coll.Add Cells(i, 1).Value, Cells(i, 1).Value
Next
Or
Dim coll As New Collection
Dim oldValues As Variant
Dim cell As Range
With Range(Cells(1, 1), Cells(Rows.count, 1).End(xlUp))
oldValues = .Value
.RemoveDuplicates Columns:=1, Header:=xlNo
For Each cell In .SpecialCells(xlCellTypeConstants)
coll.Add cell.Value, cell.Value
Next
.Value = oldValues
End With

Getting dynamic dropdown list in VBA validation

I have the following case:
1.Column D populated with about 100 values,
2. Using these I create a validation in the Column A cells
3. If I have a value in Cell "A1", this particular value should not appear
in Cell "A2" dropdown list, now the values in "A1" and "A2" should not appear in "A3" and so on.
What should be the thought process to write the VBA code for this?
I found this one interesting, so check this out... Should work as you expect it...
Post this code into your Worksheet and adapt it for your needs (if necessary). Hope it helps.
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim ws As Worksheet
Dim dict As Object
Dim dictAlreadyTaken As Object
Dim valueRange As Range
Dim targetRange As Range
Dim cell As Object
Dim Key As Variant
Dim currentList() As Variant
Dim i As Integer
If Target.Column = 1 Then
Set ws = Worksheets(1)
Set dict = CreateObject("Scripting.Dictionary")
Set dictAlreadyTaken = CreateObject("Scripting.Dictionary")
Set valueRange = ws.Range("D:D")
Set targetRange = ws.Range("A:A")
For Each cell In valueRange
If cell.Value <> "" Then
dict.Add cell.Value, cell.Row
Else
Exit For
End If
Next cell
For Each cell In targetRange
If cell.Row <= dict.Count Then
If cell.Value <> "" Then
'ad the value taken
dictAlreadyTaken.Add cell.Value, cell.Row
End If
Else
Exit For
End If
Next cell
For Each cell In targetRange
If cell.Row <= dict.Count Then
'add this list
Erase currentList
ReDim currentList(0)
i = 0
ws.Cells(cell.Row, 1).Validation.Delete
For Each Key In dict.keys
If Not dictAlreadyTaken.exists(Key) Then
i = i + 1
ReDim Preserve currentList(i) As Variant
currentList(i) = Key
End If
Next Key
If UBound(currentList) > 0 Then
ws.Cells(cell.Row, 1).Validation.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:=xlBetween, Formula1:=Join(currentList, ",")
End If
Else
Exit For
End If
Next cell
End If
End Sub
My thought process would be:
First loop to list all the ranges we need to compare:
Cells(1,1) should not appear in Range(Cells(1,4),Cells(1,4))
Cells(2,1) should not appear in Range(Cells(1,4),Cells(2,4))
Cells(3,1) should not appear in Range(Cells(1,4),Cells(3,4)) ...etc...
Easy enough. Now that we know what ranges to compare, loop through the comparisons:
re: Cells(3,1) should not appear in Range(Cells(1,4),Cells(3,4)) :
.
Dim c as range
For Each c in Range(Cells(1,4),Cells(3,4))
If c.Value = Cells(1,4).Value then
'it's a match! Delete it (or whatever)
c.Value = ""
End If
Next c
Finally, put the two loops together...
From what I understand of your description, I came up with this:
Sub compareCells()
Dim c As Range, x As Integer
For x = 1 To 10
Debug.Print "Cells(" & x & ",1) should not appear in Range(Cells(1,4),Cells(" & x & ",4))"
For Each c In Range(Cells(1, 4), Cells(x, 4))
Debug.Print "compare " & Cells(x, 1).Address & " to " & c.Address
If Cells(x, 1).Value = c.Value Then
Cells(x, 1).Cells.Font.Color = vbBlue
End If
Next c
Next x
End Sub
It should be easily adaptable to your needs, or if not, there are plenty of existing solutions & resources, even a Stack Overflow tag: cascadingdropdown
Here is an approach:
Select a column in your sheet that you can use for a named range (this column can be hidden). For the purpose of example below, I've used column J and my named range is called ValidationRange. I have also assumed that the values in your worksheet start from row 2.
Now in a module, add the following sub:
Sub SetDropDownRange()
Dim oNa As Name: Set oNa = ThisWorkbook.Names.Item("ValidationRange")
Dim iLR&, iC&, iLRJ&
Dim aDRange As Variant
Dim aVRRange As Variant
With ThisWorkbook.Worksheets("Sheet12")
iLR = .Range("D" & .Rows.count).End(xlUp).Row
iLRJ = .Range("J" & .Rows.count).End(xlUp).Row
aDRange = Range("D2:D" & iLR)
For iC = LBound(aDRange) To UBound(aDRange)
If Len(Trim(aDRange(iC, 1))) <> 0 Then
If Application.WorksheetFunction.CountIf(Range("A:A"), aDRange(iC, 1)) = 0 Then
If IsArray(aVRRange) Then
ReDim Preserve aVRRange(UBound(aVRRange) + 1)
Else
ReDim aVRRange(0)
End If
aVRRange(UBound(aVRRange)) = aDRange(iC, 1)
End If
End If
Next
End With
Range("J2:J" & iLRJ).Value = ""
Range("J2:J" & UBound(aVRRange) + 2).Value = Application.Transpose(aVRRange)
oNa.RefersTo = oNa.RefersToRange.Resize(UBound(aVRRange) + 1, 1)
End Sub
Now call this function when something changes in your worksheet.. like so:
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Column = 1 Or Target.Column = 4 Then
SetDropDownRange
End If
End Sub
Set Data Validation for the cells in column A using the named range (which is ValidationRange for this example)
Now everytime your select a value in column A, it will remove that value from the named range and hence from your dropdown box

EXCEL VBA | Cell equals selection

I've a question about showing a Selection value inside a specific cell in my sheet.(let's call it J1 for now)
So, If the user drag-selected (by mouse) A1,A2,A3,A4. J1 value will show "A1:A4", after then with some VBA code I concatenate these cells to show cells values separated by ";".
The problem is, when the user selects cells which is not in order (by holding CTRL), Like A1,A5,A11. J1 value will shows "A1,A5,A11" when I concatenate, it gives "#VALUE" error.
Can we just replace every cell reference here with cell value?
and leave the "comma" in between as is.
then later we can Subtitute comma with ";"
Excuse me if my question seems a little bit ignorant :)
my code for selection:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim rng As Range
Dim s As String
Set rng = Application.Selection
If rng.Count < 2 Then
Range("H1").Value = Cells(Target.Row, Target.Column).Value
Else
Range("H1").Value = rng.Address
End If
End Sub
Code for Concatenation:
Function ConcatenateRange(ByVal cell_range As Range, _
Optional ByVal seperator As String) As String
Dim cell As Range
Dim lastrow
Dim choice
Dim lastrowmodified
Dim rangy
Dim newString As String
Dim cellArray As Variant
Dim i As Long, j As Long
cellArray = cell_range.Value
For i = 1 To UBound(cellArray, 1)
For j = 1 To UBound(cellArray, 2)
If Len(cellArray(i, j)) <> 0 Then
newString = newString & (seperator & cellArray(i, j)) & ";"
End If
Next
Next
If Len(newString) <> 0 Then
newString = Right$(newString, (Len(newString) - Len(seperator)))
End If
ConcatenateRange = newString
End Function
If I understand correctly, you want the one cell, say J1 to contain all values of selected cells, separated by a semi colon? If so, you can just modify your first sub,
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim rng As Range
Set rng = Application.Selection
Dim vCell as Range
Range("J1").Value = ""
' Cycle through cells in range
For each vCell in rng
' Use if so that J1 doesn't start with a semi colon
If Range("J1").Value = "" Then
Range("J1").Value = vCell.Value
Else
Range("J1").Value = Range("J1").Value & ";" & vCell.Value
End If
Next vCell
End Sub
Another method would be to use a string array in conjunction with a JOIN function. This works for non-contiguous selections:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim c as Range, i as Integer
Dim arr() As String
ReDim arr(0 To Selection.Count - 1) As String
If Selection.Count < 2 Then
Range("J1").Value = Selection.Value
Else
For Each c In Selection.Cells
arr(i) = c.Value
i = i + 1
Next c
Range("J1").Value = Join(arr, ";")
End if
End Sub

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