Clear cascading ComboBox on reselection - vba

I am in the process of creating a user form in excel that uses several ComboBox'. The first ComboBox lists values from column 1 of a table and the following ComboBox' lists values from the following columns. ComboBox 2 onwards also only lists values depending on the preceding box. All ComboBox' show unique values only.
Here is the current code I am using:
Option Explicit
Private Sub ComboBox1_Change()
Call cValues(ComboBox1.Value, ComboBox2, 2)
End Sub
Private Sub ComboBox2_Change()
Call cValues(ComboBox2.Value, ComboBox3, 3)
End Sub
Private Sub ComboBox3_Change()
Call cValues(ComboBox3.Value, ComboBox4, 4)
End Sub
Private Sub ComboBox4_Change()
Call cValues(ComboBox4.Value, ComboBox5, 5)
End Sub
Private Sub ComboBox5_Change()
Call cValues(ComboBox5.Value, ComboBox6, 6)
End Sub
Private Sub UserForm_Initialize()
Dim Rng As Range
Dim Dn As Range
Dim Dic As Object
With Sheets("Listuni")
Set Rng = .Range(.Range("A2"), .Range("A" & Rows.Count).End(xlUp))
End With
Set Dic = CreateObject("scripting.dictionary")
Dic.CompareMode = vbTextCompare
For Each Dn In Rng: Dic(Dn.Value) = Empty: Next
Me.ComboBox1.List = Application.Transpose(Dic.keys)
End Sub
Sub cValues(txt As String, Obj As Object, col As Integer)
Dim Dn As Range
Dim Rng As Range
Dim Dic As Object
With Sheets("Listuni")
Set Rng = .Range(.Cells(2, col), .Cells(Rows.Count, col).End(xlUp))
End With
Set Dic = CreateObject("Scripting.Dictionary")
Dic.CompareMode = 1
For Each Dn In Rng
If Dn.Offset(, -1).Value = txt Then
If Not Dic.exists(Dn.Value) Then
Dic(Dn.Value) = Empty
End If
End If
Next Dn
Obj.List = Application.Transpose(Dic.keys)
End Sub
The problem I am having occurs when a user makes a reselection of a preceding ComboBox. Instead of clearing the subsequent boxes, all existing selections remain.
I am looking for a way to clear/default the values of subsequent ComboBox every time a reselection of a preceding ComboBox is made. For example if I make a selection in ComboBox 1 and 2 but then change my selection at ComboBox 1, I want ComboBox 2 to clear rather than show my previous selection. Note that the default position for the user form on launch shows no values in any ComboBox.
I have tried using the .clear method on change however this always gets hung up at:
Obj.List = Application.Transpose(Dic.keys)
I suspect this is because a clear is technically a change and therefore it cannot transpose the list of values to other boxes based on a null value.

This clears all subsequent ComboBoxes - if Combo1 changes, Combo2, 3, 4, 5, and 6 are cleared
Option Explicit
Private ws As Worksheet
Private d As Object
Private Sub UserForm_Initialize()
Dim cel As Range, txt As String, rng As Range
Set ws = Worksheets("Listuni")
Set d = CreateObject("Scripting.Dictionary"): d.CompareMode = vbTextCompare
Set rng = ws.Range(ws.Cells(2, 1), ws.Cells(ws.Rows.Count, 1).End(xlUp))
For Each cel In rng: d(cel.Value) = Empty: Next
ComboBox1.List = Application.Transpose(d.keys)
End Sub
Private Function setList(ByVal txt As String, ByRef cmb As ComboBox) As Object
Dim xID As Long, rng As Range, cel As Range, x As Control
xID = Right(cmb.Name, 1)
For Each x In Me.Controls
If TypeName(x) = "ComboBox" Then If Val(Right(x.Name, 1)) > xID - 1 Then x.Clear
Next
Set rng = ws.Range(ws.Cells(2, xID), ws.Cells(ws.Rows.Count, xID).End(xlUp))
d.RemoveAll
For Each cel In rng
If cel.Offset(, -1) = txt Then
If Not d.exists(cel.Value) Then
d(cel.Value) = Empty
End If
End If
Next
If d.Count > 0 Then cmb.List = Application.Transpose(d.keys) Else cmb.Clear
End Function
Private Sub ComboBox1_Change()
setList ComboBox1.Value, ComboBox2
End Sub
Private Sub ComboBox2_Change()
setList ComboBox2.Value, ComboBox3
End Sub
Private Sub ComboBox3_Change()
setList ComboBox3.Value, ComboBox4
End Sub
Private Sub ComboBox4_Change()
setList ComboBox4.Value, ComboBox5
End Sub
Private Sub ComboBox5_Change()
setList ComboBox5.Value, ComboBox6
End Sub

Related

How to hide/unhide columns added at the borders of the range

I'm trying to create a macro which will hide/unhide a specified range of columns.
Adding a column within the named range isn't problematic, but when adding a column at the borders of this range - macro doesn't work. For example, AM:BF is the named range ("Furniture") in my sheet. I need to add a column BG which will also be hidden by the macro. Same story when adding a new column on the left border. Could you guide me how to improve the code so that the columns added at the borders of the range will also be hidden/unhidden?
With ThisWorkbook.Sheets("Sheet1").Range("Furniture").EntireColumn
.Hidden = Not .Hidden
End With
I've added a variable RangeName (of type String) that equals to the name of the Name Range = "Furniture".
Code
Option Explicit
Sub DynamicNamedRanges()
Dim WBName As Name
Dim RangeName As String
Dim FurnitureNameRange As Name
Dim Col As Object
Dim i As Long
RangeName = "Furniture" ' <-- a String representing the name of the "Named Range"
' loop through all Names in Workbook
For Each WBName In ThisWorkbook.Names
If WBName.Name Like RangeName Then '<-- search for name "Furniture"
Set FurnitureNameRange = WBName
Exit For
End If
Next WBName
' adding a column to the right of the named range (Column BG)
If Not FurnitureNameRange Is Nothing Then '<-- verify that the Name range "Furnitue" was found in workbook
FurnitureNameRange.RefersTo = FurnitureNameRange.RefersToRange.Resize(Range(RangeName).Rows.Count, Range(RangeName).Columns.Count + 1)
End If
' loop through all columns of Named Range and Hide/Unhide them
For i = 1 To FurnitureNameRange.RefersToRange.Columns.Count
With FurnitureNameRange.RefersToRange.Range(Cells(1, i), Cells(1, i)).EntireColumn
.Hidden = Not .Hidden
End With
Next i
End Sub
place the following in your worksheet code pane:
Option Explicit
Dim FurnitureNameRange As Name
Dim adjacentRng As Range
Dim colOffset As Long
Private Sub Worksheet_Change(ByVal Target As Range)
Dim newRng As Range
If colOffset = 1 Then Exit Sub
On Error GoTo ExitSub
Set adjacentRng = Range(adjacentRng.Address)
With ActiveSheet.Names
With .Item("Furniture")
Set newRng = .RefersToRange
.Delete
End With
.Add Name:="Furniture", RefersTo:="=" & ActiveSheet.Name & "!" & newRng.Offset(, colOffset).Resize(, newRng.Columns.Count + 1).Address
End With
ExitSub:
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
On Error Resume Next
Set FurnitureNameRange = ActiveSheet.Names("Furniture") 'ThisWorkbook.Names("Furniture")
On Error GoTo 0
colOffset = 1
Set adjacentRng = Nothing
If FurnitureNameRange Is Nothing Then Exit Sub
Set adjacentRng = Target.EntireColumn
With FurnitureNameRange.RefersToRange
Select Case Target.EntireColumn.Column
Case .Columns(1).Column - 1
colOffset = -1
Case .Columns(.Columns.Count).Column + 1
colOffset = 0
End Select
End With
End Sub

How do I fire an Excel VBA Macro whenever a cell value is updated?

I have a Sub that I would like to run whenever cells are updated to contain a certain value.
Right now I'm using code like the following:
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
If Target.Cells.Count = 1 Then
If Target.Value = XYZ Then
my_sub a, b, c
End If
End If
End Sub
The issue right now is that the macro only fires when I edit these cells directly, not when changes in other cells force these cells to change.
Additionally, these cells are not well defined, so I can not hard code "when A5 changes", for example. I need this to fire every time any cell in my workbook is updated (manually or through formulas) to meet my condition.
Provided your target is only a single cell with a formula that needs to be monitored, this will work:
Option Explicit
Dim tarVal As Variant
Private Sub Worksheet_Activate()
tarVal = ActiveSheet.Range("A1").Value ' change range parameter to the address of the target formula
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
Dim tempVal As Variant
tempVal = ActiveSheet.Range("A1").Value
If tempVal <> tarVal Then
tarVal = tempVal
' your code here
MsgBox "The value of A1 has changed" ' for testing purposes only, delete later
End If
End Sub
Edit
The following code works for an entire range of cells, but only if automatic calculation is turned on. In case the monitored cells are non-contiguous, just use union statements when defining the target range. (The target range is A1:A10 in this example). This is under the assumption that only one of formulas in the target range can change its value at a time. If multiple target formulas can do that, then remove Exit for in the Worksheet_Change subroutine.
Option Explicit
Dim tarCellCount As Long
Dim tarRng As Range
Dim tarVals As Variant
Private Sub Worksheet_Activate()
Dim i As Long
Dim cll As Range
Set tarRng = ActiveSheet.Range("A1:A10") ' change range parameter to the addresses of the target formulas
tarCellCount = tarRng.Cells.count
ReDim tarVals(1 To tarCellCount) As Variant
For Each cll In tarRng
i = i + 1
tarVals(i) = cll.Value
Next cll
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
Dim changeBool As Boolean
Dim i As Long
Dim cll As Range
Dim tempVal As Variant
For Each cll In tarRng
tempVal = cll.Value
i = i + 1
If tempVal <> tarVals(i) Then
tarVals(i) = tempVal
changeBool = True
Exit For
End If
Next cll
If changeBool Then
' your code here
MsgBox "The value of one of the cells in the target range has changed" ' for testing purposes only, delete later
End If
End Sub
Add your cells to be tracked to a named formula (named range). I used rngValue
Use a static variable to track how many times the value you want to track occurs in this range
Use the Calculate event to check if the number of occurences changes
code
Private Sub Worksheet_Calculate()
Dim StrIn As String
Static lngCnt As Long
Dim lngCnt2 As Long
StrIn = "apples"
lngCnt2 = Application.WorksheetFunction.CountIf(Range("rngValue"), StrIn)
If lngCnt2 <> lngCnt Then
lngCnt = lngCnt2
Call mysub
End If
End Sub
Target is a range that CAN contain more cells. This code should work for you.
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
For Each cell In Target.Cells
If cell.Value = XYZ Then
my_sub a, b, c
End If
Next cell
End Sub
Edit: I see that you want to fire that also when formula is updated defined value. It can be slow if you will check every cell, but really depends on the size of your file. Here is some code to give you idea how to do it.
Private Sub Workbook_SheetCalculate(ByVal sh As Object)
For Each cell In sh.Cells.SpecialCells(xlCellTypeFormulas).Cells
If cell.Value = XYZ Then
my_sub a, b, c
End If
Next cell
End Sub

Error passing array to listbox as a parameter

Objective : Create a userform and take a user input, and then from user input put it in a list and when you click the list it automatically find it in the whole workbook.
Something Like this:
I saw this post: Match in whole workbook
And I created something out of that:
Public Sub CommandButton3_Click()
Dim TempArray As Variant
Dim RealArray()
TempArray = Application.InputBox("Select a range", "Obtain Range Object", Type:=64)
ArrayRows = UBound(TempArray)
ReDim RealArray(1 To ArrayRows)
For i = 1 To ArrayRows
RealArray(i) = TempArray(i, 1)
Next i
MsgBox "The number if rows selected are " & ArrayRows
ListBox1.List = RealArray
ListBox1 Arraay:=RealArray
End Sub
Public Sub ListBox1_Click(ByRef Arraay() As Variant)
Dim Sh As Worksheet
Dim something As Range
Dim ArrayRows As Long
For Each Sh In ThisWorkbook.Worksheets
With Sh.UsedRange
For i = 1 To ArrayRows
Set something = RealArray.Find(What:=RealArray(i))
If Not something Is Nothing Then
Do Until something Is Nothing
test = something.Value
Set something = .FindNext(something)
Loop
End If
Next i
End With
Set something = Nothing
Next
End Sub
After creating this, I get an error regarding the second sub.
procedure declaration does not match description of event or procedure having the same name
The Listbox click event doesn't take any parameters.
Private Sub ListBox1_Click()
End Sub
If you want to pass an array between sub then you can so it this way
Dim MyArray() As Variant
Public Sub CommandButton3_Click()
'~~> Initialize array
End Sub
Private Sub ListBox1_Click()
'~~> Use array here
'~~> Also put an error check if the array is initialized or not
End Sub

Refresh combo box after list update vba excel

I'm trying to auto update a combobox list. It updates correctly only when I close and then open the workbook, or when I press the stop button on VBA and run the macro again. I have the following VBA code.
Private Sub UserForm_Initialize()
Dim cod As Range
Dim pro As Range
Dim cli As Range
Dim ws As Worksheet
Dim ws5 As Worksheet
Set ws = Worksheets("ListaProductos")
Set ws5 = Worksheets("ListaClientes")
For Each cod In ws.Range("CodigoProductoLista")
With Me.codigo
.AddItem cod.Value
.List(.ListCount - 1, 1) = cod.Offset(0, 1).Value
End With
Next cod
For Each cli In ws5.Range("ClienteLista")
With Me.cliente
.AddItem cli.Value
.List(.ListCount - 1, 1) = cli.Offset(0, 1).Value
End With
Next cli
No.Value = True
calendario2.Visible = False
calendario2.Refresh
calendario = Date
Me.codigo.SetFocus
End Sub
Thanks!
You could call the UserForm_Initialize procedure again, but you will have to clear the lists first. You could use it in a commandbutton, or in an event for instance.
That Initialize event will only trigger when the form is loading. Add a button to your form called cmdRepopulate and use this code instead:
Option Explicit
Private Sub UserForm_Initialize()
PopulateCodigoProductoLista
PopulateClienteLista
FinishingOff
End Sub
Private Sub PopulateCodigoProductoLista()
Dim rngData As Range
With Worksheets("ListaProductos").Range("CodigoProductoLista")
Set rngData = .Resize(.Rows.Count, 2)
End With
PopulateComboUsingRange Me.codigo, rngData
End Sub
Private Sub PopulateClienteLista()
Dim rngData As Range
With Worksheets("ListaClientes").Range("ClienteLista")
Set rngData = .Resize(.Rows.Count, 2)
End With
PopulateComboUsingRange Me.cliente, rngData
End Sub
Private Sub FinishingOff()
No.Value = True
calendario2.Visible = False
calendario2.Refresh
calendario = Date
Me.codigo.SetFocus
End Sub
Private Sub PopulateComboUsingRange(cboDataDestination As MSForms.ComboBox, _
rngDataSource As Range)
Dim lngCounter As Long
With cboDataDestination
.Clear
For lngCounter = 1 To rngDataSource.Rows.Count
.AddItem rngDataSource.Cells(lngCounter, 1)
If rngDataSource.Columns.Count = 2 Then
.List(.ListCount - 1, 1) = rngDataSource.Cells(lngCounter, 2)
End If
Next
End With
End Sub
Private Sub cmdRepopulate_Click()
PopulateCodigoProductoLista
PopulateClienteLista
FinishingOff
End Sub
There are some limitations (because it's too late), notably the generic combo population routine but in its current for it should be good for you. The code will run on the form's Initialize event and whenever you click the repopulate button. I decided to work with your named ranges as they were - no changes.
Note the use of a generic proc to populate the combo. If you had other combos on other forms in this same workbook, you could move that proc to a separate module, change it to Public (rather than Private) and save yourself a lot of typing by reusing the code.

Using textboxes within userform to define variables?

I currently run a macro to compare the most recent sheet of data to the report immediately prior and highlight changes. It works fine on its own. Now, however, we would like to be able to compare selected sheets from any time period. My idea was to pop up a simple userform with two textboxes that the user can use to specify which two reports he wants to compare. I am quite lost though with the idea of trying to declare public variables; what I've got atm is:
Option Explicit
Public shtNew As String, shtOld As String, _
TextBox1 As TextBox, TextBox2 As TextBox
Sub SComparison()
Const ID_COL As Integer = 31 'ID is in this column
Const NUM_COLS As Integer = 31 'how many columns are being compared?
Dim rwNew As Range, rwOld As Range, f As Range
Dim X As Integer, Id
shtNew = CSManager.TextBox1
shtOld = CSManager.TextBox2
'Row location of the first employee on "CurrentMaster" sheet
Set rwNew = shtNew.Rows(5)
Do While rwNew.Cells(ID_COL).Value <> ""
Id = rwNew.Cells(ID_COL).Value
Set f = shtOld.UsedRange.Columns(ID_COL).Find(Id, , xlValues, xlWhole)
If Not f Is Nothing Then
Set rwOld = f.EntireRow
For X = 1 To NUM_COLS
If rwNew.Cells(X).Value <> rwOld.Cells(X).Value Then
rwNew.Cells(X).Interior.Color = vbYellow
rwNew.Cells(33) = "UPDATE"
Else
rwNew.Cells(X).Interior.ColorIndex = xlNone
End If
Next X
End If
Set rwNew = rwNew.Offset(1, 0) 'next row to compare
Loop
Call SUpdates
End Sub
My Suggestion would be to use Comboboxes instead of TextBoxes. Create a userform with two command buttons and two comboboxes and populate the comboboxes in the UserForm_Initialize() event using this code.
Private Sub UserForm_Initialize()
Dim ws As Worksheet
For Each ws In ActiveWorkbook.Sheets
ComboBox1.AddItem ws.Name: ComboBox2.AddItem ws.Name
Next
End Sub
And then use this code in the OK button to do the comparison.
Private Sub CommandButton1_Click()
Dim shtNew As Worksheet, shtOld As Worksheet
If ComboBox1.ListIndex = -1 Then
MsgBox "Please select the first sheet"
Exit Sub
End If
If ComboBox2.ListIndex = -1 Then
MsgBox "Please select the Second sheet"
Exit Sub
End If
Set shtNew = Sheets(ComboBox1.Value)
Set shtOld = Sheets(ComboBox2.Value)
'~~> REST OF THE CODE HERE NOW TO WORK WITH THE ABOVE SHEETS
End Sub
Private Sub CommandButton2_Click()
Unload Me
End Sub
HTH
Sid
For an easy fix, couldn't you just colour (sorry, I'm English!) the worksheets that you want to refer to, then do something like:
Sub ListSheets()
'lists only non-coloured sheets in immediate window
'(could amend to add to combo boxes)
Dim w As Worksheet
'loop over worksheets in active workbook
For Each w In Worksheets
If w.Tab.Color Then
'if tab color is set, print
Debug.Print w.Name
End If
Next w
Let me know if this solves your problem.