Stop dropdown selection from autofilling combobox - vba

I have an ActiveX combobox that has a dropdown which is populated and filtered when a user types characters into the combobox. The dropdown items are from cLst. So the dropdown will be open, but as soon as the user hits the arrow down, the combobox populates with the first dropdown item and all of the other items in the dropdown disappear, because it then tries to filter the dropdown by the item in the combobox, which is an exact match for one item in the dropdown (the one that was highlighted upon arrow down).
How can I avoid this autofilling behavior when arrowing down through the dropdown, and have the user hit enter on the selection they want to populate the combobox instead?
If the user avoids using the keyboard, the mouse works fine to scroll through and highlight, then click, and only populates the combobox upon the click. I would like the scroll wheel to work if possible to scroll through the dropdown.
Private Sub newCmb_Change()
filterComboList Tool.newCmb, cLst
End Sub
Private Sub newCmb_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
Tool.newCmb.DropDown
End Sub
Private Sub newCmb_GotFocus() 'or _MouseDown()
Tool.newCmb.DropDown
End Sub
Public Sub filterComboList(ByRef cmb As ComboBox, ByRef dLst As Variant)
Dim itm As Variant, lst As String, sel As String, rng As Range
With Worksheets("Database")
Set rng = Application.Intersect(.UsedRange.Rows(2), .Cells.Resize(.Columns.Count - 1).Offset(1))
End With
Application.EnableEvents = False
With cmb
sel = .Value
If IsEmpty(cLst) Then cLst = rng
For Each itm In cLst
If Len(itm) > 1 Then If InStr(1, itm, sel, 1) Then lst = lst & itm & "||"
Next
If Len(lst) > 1 Then .List = Split(Left(lst, Len(lst) - 2), "||") Else .List = dLst
End With
Application.EnableEvents = True
End Sub

I was dealing with the same issue, and ended up finding some info on a Microsoft Help-site thread that let me play around with it. I posted an answer here which seems to work for me. It is a stripped down version of what I use in a sheet for this same concept.
The basic idea involves the newCmb_KeyDown() event (though should be similar to KeyPress in overall behavior) in the sheet that the combobox is located in, which snags the arrow key presses and sets a flag. The keys' actions are canceled by setting the KeyCode value to 0 and changing the newCmb.ListIndex value by +/-1 to change the selection, and using a flag in the newCmb_Change() event you can prevent the ComboBox from changing the linked cell value due to the up and down arrows. Once you get to the end of your KeyDown or KeyPress event, you can reset the flag for when you want changes to occur.
Hope those help, there is a link in that answer to the thread I found, which has some general ideas (though focused on UserForms instead of spreadsheets). Good luck!
**Edit
Note: Those parts of code were what seemed to control this behavior in my sheet, but if you have an issue with getting it to work, I can look at it again.

Related

Question on multiple checkboxes launching code

I have a user form and a frame with 35 checkboxes in it, numbered 1 to 35. They represent 35 Named Ranges. I test to see if any of the name ranges are not set, if set correctly the checkbox value is set to TRUE.
I found some code that would allow me to trigger a sub if one of the checkboxes is clicked. That code seems to work, but my check code above also triggers the checkbox events, which I do not want. I only want the sub to run when the checkbox is clicked with the mouse? I can post the code I'm using, but though I'd first ask the question to see if what I would like to do is possible.
Thanks,
Jim
Code in class module:
Public WithEvents ChkBox As MSForms.CheckBox
Public Sub AssignClicks(ctrl As Control)
Set ChkBox = ctrl
End Sub
Public Sub chkBox_Click()
If chkBoxProcess = "Y" Then
'ThisWorkbook.Worksheets("Sheet1").Range(ChkBox.Name).Value = Format(Now, "dd.mm.yyyy")
'MsgBox ("check box number = " & ChkBox.Name & " " & ChkBox.Value)
' Else
End If
End Sub
Code in Forms:
Public Sub UserForm_Initialize()
Dim SheetCount, i As Integer
Dim sh As Worksheet
'Public SheetName, SheetName2, StartOldNewTimeA, OldNewTimeAdd As String
'Initialize the form frmChgNameRng
'Set array values of the day options
'Set array values for 12:00 timeframes
'Set array values for 12:30 timeframes
'Set colors used in Checkboxes
'Set array for Checkboxes (boxes are numbered accross the page, 1 corressponds to Mon_1200/Mon_1230, 8 corresponds to Mon_200/Mon_230, etc.)
'Formulas are placed in the time cells on the left of the page, the macro will add the appropriate value into the Mon_1200 time slot and all other cells update off that cell
chkBoxProcess = "N"
Dim ChkBoxes As cls_ChkBox
Dim ctrl As Control
Set colTickBoxes = New Collection
For Each ctrl In Me.Controls
If TypeName(ctrl) = "CheckBox" Then
Set ChkBoxes = New cls_ChkBox
ChkBoxes.AssignClicks ctrl
colTickBoxes.Add ChkBoxes
End If
Next ctrl
'..... lots of code for Range Name Checks, etc.
End Sub
Your code is conflating control state with model data, and so the only way to tell it "named range 32 is ON", or "named range 13 is OFF", is to alter a checkbox' state, which fires that control's Change event.
There's no way around that, it's just how controls work: they fire a Change event whenever their value changes, regardless of how that's done.
Instead of having controls' state be the data, make the controls' state alter the data.
This requires conceptualizing this data, first: looks like you need to associate a number/index to some Boolean value. An array can do this.
Private namedRangeStates(1 To 35) As Boolean
Note that depending on what you're doing, initializing the state should be feasible by iterating the workbook's Names collection in the UserForm_Initialize handler. Or better, the form could expose a method that takes an array of Boolean values, and copies that state into namedRangeStates.
Now, when a checkbox is modified, make it alter the state:
Private Sub Checkbox31_Change()
namedRangeStates(31) = Checkbox31.Value
End Sub
Your form can expose that state as a property:
Public Property Get NamedRangeState(ByVal index As Long) As Boolean
NamedRangeState = namedRangeStates(index)
End Property
Public Property Let NamedRangeState(ByVal index As Long, ByVal value As Boolean)
namedRangeStates(index) = value
End Property
And now you can modify the enapsulated state independently of the combobox values.

VBA How to programaticly select an item in a listbox without triggering the on click event

I am using Excel 2010, Windows 10, with VBA. I have a function which runs upon clicking an item in an ActiveX ListBox control. The issue is that if you click the list box I ask if they are sure if they want to change the selection. If you click "yes" I continue, but if you say "no" I set the selection back to what it previously was.
So the issue is that when I programmatically set the list box selection back to the previous selection my function will re-run the code that runs if a user clicks an item in the list box ...which is what I don't want.
Does anyone have a better way to stop a list box selection and change it back to the old one without causing the on list box selection event to trigger?
Function prototype for on click of the list box
lsQuestions_Click()
Code for setting the list box selection
'Prototype: setListBoxSelection(query As String, listBoxName As String) As Boolean
' Purpose: Set listbox selection based on text
Public Function setListBoxSelection(query As String, listBoxName As String) As Boolean
Dim lsBox As MSForms.listBox
Set lsBox = Workbooks(mainFile).Worksheets(entrySheet).OLEObjects(listBoxName).Object
Dim I As Integer
For I = 0 To lsBox.ListCount - 1
If lsBox.List(I) = query Then
lsBox.Selected(I) = True
setListBoxSelection = True
Exit Function
End If
Next I
setListBoxSelection = False
End Function
Please note that I think the line of code below is what is triggering my click event which is what I don't want.
lsBox.Selected(I) = True
The way I do this with my VB6 projects is to define a module-scope variable
Private blnChangingInCode As Boolean
Then, when I need to utilize it, I set it to true, call the even/sub, set it back to false.
blnChangingInCode = True
btnLogin_Click()
blnChangingInCode = False
Inside the affected subs/events I start with
If blnChangingInCode Then
Exit Sub ' or Exit Function
End if
This might not be elegant, but it works, and I don't need to do it very often.

VBA ActiveX dynamic ComboBox reduces ListRows to 1

I am trying to get a VBA ComboBox to dropdown and display only those items which match or partially match the typed string.
For this purpose, I have set up a ComboBox KeyUp event manager, as follows:
Public Sub TempCombo_KeyUp(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
Select Case KeyCode
Case 9
'If TAB is pressed, then move one place right
ActiveCell.Offset(0, 1).Activate
Case 13
'If Enter is pressed, then move one place down
ActiveCell.Offset(1, 0).Activate
Case Else
'Otherwise, filter the list from the already entered text
Dim x As Long
OriginalValue = Me.TempCombo.Value
'Remove items from the ComboBox list
If Me.TempCombo.ListCount > 0 Then
For i = 1 To Me.TempCombo.ListCount
Me.TempCombo.RemoveItem 0
Next
End If
'If any part of any element from the 'FullSource' array matches the so far typed ComboBox value, then include it in the list for dropdown
For x = 1 To UBound(FullSource)
Typed_Value = "*" & LCase(OriginalValue) & "*"
If LCase(FullSource(x)) Like Typed_Value Then
Me.TempCombo.Object.AddItem FullSource(x)
End If
Next
Me.TempCombo.Value = OriginalValue
Me.TempCombo.ListRows = 12
Me.TempCombo.DropDown
End Select
End Sub
The code seems to do the filtering fine. But the dropdown list height is only one unit tall. I have to scroll through this small box, using the mouse buttons.
Why the dropdown list reduces in size is a mystery to me, and I'd appreciate if any light can be thrown on this. Perhaps there is some setting that I am overlooking.
Thanks
You can use Me.TempCombo.Height = 15 to set the height.
If it doesn't work, you are probably running into ActiveX control instability issues. Refer to Excel VBA ComboBox DropDown Button Size--changed itself to use form controls instead of ActiveX.
Dynamically adjusting the width of a combobox in Excel VBA for more details on setting this dynamically.

Updating Word Form Field

I have a Word form that I am working on.
It has one field that is supposed to be calculated from other fields.
In the prior iteration, you could click the cell in the table and hit F9 and the field would update.
I have since added some other buttons and VBA and now you can no longer click the cell when "Restrict Editing" is on.
I have tried a button tied to VBA that will update all fields, but when you click that button, you cannot edit any of the fields.
How can I update this field, and still be able to manually update my other fields?
The problem was that Content Controls and ActiveX buttons are not altogether compatible. Also, the programmer I had inherited the form from was using a simple field calculation based on the table in the document instead of VBA. I was able to come up with a better solution. I used the sub:
Private Sub Document_ContentControlOnExit(ByVal thisControl As ContentControl, Cancel As Boolean)
End Sub
to execute code onExit from the controls. This function executes on ALL Content Controls as the user exits the Content Control. Another tool I developed was the following function which will find the index of the control with the given title:
'Function to get control index given the control title
'PARAMETER Control Title as String
'RETURN Control Index as Integer
Public Function GetControlIndex(ccTitle As String) As Integer
'Function Variable Declaration
Dim objCC As ContentControl
'look at each ContentControl
For i = 1 To ActiveDocument.ContentControls.count
Set objCC = ActiveDocument.ContentControls.Item(i)
With objCC
If .Title = ccTitle Then
GetControlIndex = i
End If
End With
Next i
End Function

Detecting changes to checkboxes via VBA

Following on from my previous question.
A requirement from the customer is to have checkboxes on a report to disable rows of information on another sheet. The rows are defined as named ranges, formated by P_XXXXXX. The XXXXXX is a unique identifier that is also a field on the row so I can easily generate the range names on the fly.
The problem I am having is:
After clicking on the items and then closing the form Excel asks if we want to save. This is undersirable.
I need someway of registering a change event happening on my generated checkboxes. So if one or more changes I can run through and hide/unhide the relevant ranges.
My code for adding the checkboxes looks like:
' For each row...
' check box in column 17(=Q).
Dim lCenter As Long
lCenter = rngCurrent.Width / 4 ' not actual centre but close enough
With ActiveSheet.CheckBoxes.Add(rngCurrent.Left + lCenter, rngCurrent.Top - 2, rngCurrent.Width, rngCurrent.Height)
.Interior.ColorIndex = xlNone
.Caption = ""
End With
So how do you link a change in a checkbox with a sub/function?
Set the OnAction property of the Checkboxes object to the name of a sub you want to run whenever the checkbox is checked or unchecked.
Sub MakeCB()
With ActiveSheet.CheckBoxes.Add(ActiveCell.Left + 0, ActiveCell.Top - 2, ActiveCell.Width, ActiveCell.Height)
.Interior.ColorIndex = xlNone
.Caption = ""
.OnAction = "CheckboxChange"
End With
End Sub
Sub CheckboxChange()
MsgBox "change"
End Sub
I don't think there are any events available with the Excel.Checkbox control. Try using the MSForms checkbox instead. You'll need a reference to 'Microsoft Forms 2.0 Object Library' - it's not redistributeable, but if you're using VBA, then that's fine.
You can then do something like this, and handle the event in the usual way:
''class level
Private WithEvents m_Checkbox as MSForms.CheckBox
Public Sub MakeCheckbox()
Set m_Checkbox = Activesheet.OLEObjects.Add("Forms.Checkbox.1")
End Sub
Private Sub m_Checkbox_Click()
''Do stuff
End Sub
Obviously, you'll only be able to handle a set number of checkboxes this way - I would recommend creating a class to hold each checkbox.