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

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.

Related

Set a sub-form hidden field to visible, based on a check box status

C, Thank you for your input and encouragement! I have changed my form and script slightly, I am afraid I kept the if then statement as I am comfortable with the formatting. The script now works when the 'On Open'event runs.
Private Sub Form_Open(Cancel As Integer)
Me.ChkAlbumNotes.SetFocus
If Me.ChkAlbumNotes.Value = False Then
Me.lblAlbumNotes.Visible = False
Me.txtAlbumNotes.Visible = False
Me.btnAlbumNotes.Visible = True
Else
Me.lblAlbumNotes.Visible = True
Me.txtAlbumNotes.Visible = True
Me.btnAlbumNotes.Visible = False
End If
Me.TrackName.SetFocus
If Me.TrackName = " " Then
Me.btnAddRecord.SetFocus
Else
Me.btnNextRecord.SetFocus
End If
End Sub
This is fine when the form opens for the first time but I have a set of navigation buttons that are installed by the application as Macros. I cannot add my script to the On_Click event when the button is clicked, as On_Click is linked to the Macro. Is there a way to incorporate the script from the On_Load process to the pre-defined macro? Or can you suggest a neater way to achieve my requirements which are;
When the form opens,a check is made for the existence of a false value in the checkbox
if the check box is set to false, then the Notes Text Box and label are hidden and the notes button is visible.
If the check box has a true value, then the Notes text box and label are made visible and the button is hidden.
On completion of the test I check the field Track Name
if this is empty, I assume I am at the last record and give the Add New Record button the focus
If Track Name is not empty, then focus is set to Next Record button
when this button is clicked, the next record page opens and the process starts again.
Many Thanks
Mike
You should use the Form_Current event instead of Form_Open . This fires on starting the form (2 times) and everytime you move to another record.
Private Sub Form_Current()
Me.lblAlbumNotes.Visible = Me.ChkAlbumNotes.Value
Me.txtAlbumNotes.Visible = Me.ChkAlbumNotes.Value
Me.btnAlbumNotes.Visible = Not Me.ChkAlbumNotes.Value
If Me.TrackName = "" Then ' I suggest If Me.TrackName = " " being a typo and you want to check if empty ( that's why you should use vbNullString instead of "")
Me.btnAddRecord.SetFocus
Else
Me.btnNextRecord.SetFocus
End If
End Sub

Call a sub after this one has finished

I have a userform looping through a range with 2 settings; manual and automatic.
When I have an option button on my form set to manual, and click a next command button, I check the next cell in the range, change the contents of the form accordingly, then wait for the next button press.
However if I find the option button is set to automatic then instead of finishing up my code and waiting for the next button press, I have to call the next button press programmatically. That means that the previous subs each calling the next code slowly build up in the call stack, and I worry this will have some memory implications if I'm looping over a large range.
In code:
Private Sub nextItem()
Dim willShow As Boolean
returnResults 'return details from the form to the sheet
clearMemory 'clear out previous items on form
itemNo = itemNo + 1 'iterate over the range
SetupParts 'place new items on form
'do what next
Select Case displaySetting 'this variable holds the result from my option button in a custom enum "dispMode"
Case dispMode.Manual 'always show form
willShow = True
Case dispMode.SemiAutomatic 'show form based on condition
willShow = (Data.Count = 0) 'if SetupParts returns no data, display form, otherwise keep hidden
Case dispMode.Automatic 'don't show form
willShow = False
End Select 'there are actually a few more options here, but I've simplified
If willShow = False Then
If Me.Visible = True Then 'if needs to hide, and currently visible, then hide the form
Me.Hide
nextItem 'this is the problem, I call this code again, so the call stack grows
Else
'form is already hidden, do nothing
End If
ElseIf Me.Visible = False Then 'willShow must be True
Me.Show 'then wait for button click
End If
End Sub
Private Sub commandBtnNext_Click()
nextItem
End Sub
To avoid this problem, is there any way of getting nextItem to run immediately after the previous call of nextItem has run; ie. to tell a sub to run immediately after this one has finished (without introducing time delays). Or maybe this isn't an issue; if so, please explain why.
UPDATE
There is also a SemiAutomatic check to see which mode to use based on the contents of the userform. This is fine when calling recursively, but I can't see how to incorporate it into a looping approach.

Stop dropdown selection from autofilling combobox

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.

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

Pause VBA macro, allow user to make a selection, and restart where it left off

I want to allow the user to make a selection, run some code, pause for another selection, run more code?
I work with documents with large number of tables that eventually convert to HTML. Sometimes the formatting on two like tables doesn't convert the same. Knowing how the converter works, I'd like to copy all of the formatting data from one table and "paste" it onto another one.
I've the idea of a userform to have the user select something, hit a copy button, select something else and hit a paste button.
The timer function allows you to do this. It may not be the best way to code, but it is the answer to your problem:
'1st code here
Start = Timer
Do While Timer < Start + 10
DoEvents
Loop
'do 2nd code here
DoEvents allows the user to select text, etc. After 10 seconds, the code resumes at "2nd code."
You can use global a global variable:
Public myVar as Variant
Sub fillmyVar()
myVar = Selection
End Sub
Sub doSth()
' use myVar and the new selected text
End Sub
Using the answer from Aaron and incorporating it with a ToggleButton in the Userform you can successfully pause the code. With this you can then work in an additional selection to change the operation.
I originally did not use Global or Public Variables but soon learnt that its easier for passing data between Subs and Userforms
Userform:
Public Sub ToggleButton1_AfterUpdate()
'Program is Paused / Selected to Pause
If ProgBar.ToggleButton1.Value = True Then
'Changing the Text of the Toggle button once the program is selected to Pause
'If program paused then button will display continue
ProgBar.ToggleButton1.Caption = "Continue"
'For Sending to Loop Code
ProgramStatus = "0"
Call LoopCode.PrgStat(ProgramStatus)
End If
'Program is running / Selected to run
If ProgBar.ToggleButton1.Value = False Then
'Changing the Text of the Toggle button once the program is selected to continue
'If program running then button will display pause
ProgBar.ToggleButton1.Caption = "Pause"
'For Sending to Loop Code
ProgramStatus = "1"
Call LoopCode.PrgStat(ProgramStatus)
End If
End Sub
In your Module
Public Status As String
Public Sub PrgStat(ByVal ProgStatus As String)
Status = ProgStatus
End Sub
Sub SomeSub()
Do While
' Some Loop Code Running
If Status = "1" Then
'Toggle Not Pressed
End If
If Status = "0" Then
'Toggle Button Pressed
Do While Status = "0"
'Program will stop here until the togglebutton on the
'userform is pressed again which changes Status = 1
'This is where you can make another selection on the
'userform
DoEvents
Loop
End If
Loop
End Sub