I have Created a excel user form with three Combo box and list box with 3 columns.
When user selects a value from drop down list and click Add button it will be displayed in the list box - Done
When user selects a item in the list box it will again displays in drop down list - done
But when user selects the item in the list box and click update button its calling the ListBox
_click () function, I dont want it to call the listbox_click function
PLease help. updating the code
Code for adding value from drop down list to list box
Private Sub cmdAdd_Click()
Call lstValues.AddItem(AddPpayTierOption(cboPpayTier.Value))
lstValues.List(UBound(lstValues.List), COL_BRAND) = cboBrandTier.Value
lstValues.List(UBound(lstValues.List), COL_GEN) = cboGenTier.Value
End Sub
when clicks the item in the list box it will display the value in drop down list
Private Sub lstValues_Click()
Dim I As Long
cmdEdit.Enabled = True
cmdRemove.Enabled = True
If lstValues.ListIndex <> -1 Then
For I = 0 To lstValues.ColumnCount - 1
If I = 0 Then
cboPpayTier.Value = lstValues.Column(I)
Else
If I = 1 Then
cboBrandTier.Value = lstValues.Column(I)
Else
If I = 2 Then
cboGenTier.Value = lstValues.Column(I)
End If
End If
End If
Next I
End If
End Sub
when user click the update button code.
when it goes to lstValues.Column(j) = cboPpayTier.Value line its calling the lstValues_Click() function I don't want code to call that function. Please Help
Private Sub cmdEdit_Click()
Dim j As Long
Dim var As Variant
If lstValues.ListIndex <> -1 Then
For j = 0 To lstValues.ColumnCount - 1
If j = 0 Then
lstValues.Column(j) = cboPpayTier.Value
Else
If j = 1 Then
lstValues.Column(j) = cboBrandTier.Value
Else
If j = 2 Then
lstValues.Column(j) = cboGenTier.Value
End If
End If
End If
Next j
End If
End Sub
please let me for more clarification.
Two options:
Use Application.EnableEvents = False to disable event processing. And Application.EnableEvents = True to re-enable. Note, this disables all event handling, so place it either side of code that would otherwise trigger an event you don't want.
Declare a global flag. Set it just before code that will trigger an event you don't want. In the Event Sub itself, examine the flag, and exit if it's set. Reset the flag either on the other side of the event triggering code, or in the event Sub itself. Doesn't stop the event firng, but stops it executing code you don't want.
As requested, example of option 2
In a standard module, at the top (just after Option Explicit), add this code
Global InhibitEvent As Boolean
In your code
' Somewhere in your code,
' when you are about to execute some logic that will trigger an event
InhibitEvent = True
'... event triggering logic here
InhibitEvent = False
In the Event Sub that you don't want to execute (eg a listBox Change event)
Private Sub ListBox1_Change()
' Look for Event Inhibit
If InhibitEvent Then Exit Sub
' rest of event code here
End Sub
Try using a List as the data source of list box. When updating, first put datasource of listbox null then equal to List
Related
I have add a ListBox from Active X Controls in my Excel File and made it a multi select box with checkboxes.
I have also added a selection change event in the VB script against this list box.
Sub lstMultiSelectBox_Change()
If blnCheck = False Then
CheckAll
End If
End Sub
Now what I am struggling to find is that which item was last checked. With this information I want to implement Select All and Un Select All feature in this list box.
In order to make ListBox1_Change event returning the last selected list box value, you can use the solution. It can detect the selected value, independent of its position in the list:
Create a Private variable on top of the sheet module where the list box exists (in the declarations area):
Private colS As New Collection
Then copy the next adapted event code:
Private Sub ListBox1_Change()
Dim i As Long
For i = 0 To ListBox1.ListCount - 1
If ListBox1.Selected(i) Then
If colS.Count = 0 Then
colS.Add ListBox1.List(i), ListBox1.List(i)
Else
If Not itExists(colS, ListBox1.List(i)) Then
colS.Add ListBox1.List(i), ListBox1.List(i)
End If
End If
Else
If itExists(colS, ListBox1.List(i)) Then
colS.Remove ListBox1.List(i): Exit Sub
End If
End If
Next i
If colS.Count > 0 Then MsgBox colS(colS.Count)
End Sub
If you want it triggering only if the selected value is "Select All", then replace the last event code line with something like:
If colS.Count > 0 Then
If colS(colS.Count) = "Select All" then
'do whatever you need in such a case
'but, if you try selecting all of lines, in order to avoid the event
'being triggered again, you should use 'Application.EnableEvents = False`, before selecting and 'Application.EnableEvents = True` after
End If
End If
The simplest solution should be the one suggested in the first comment:
If Listbox1.Selected(1) = True Then
'do whatever you need
End If
But, in order to make it working as it should, the line "Select All" should be the second of the list...
I have a User Form in Excel in which questions are indexed in a Listbox control. Clicking on an item in the Listbox calls a Change event which populates other controls' values according to which item has been selected.
The user may change values within the text boxes. Upon changing them, a "Saved" flag gets set to False for that question. The user may then save the question into memory; or navigate away from the question.
If the user navigates away without saving (by means of clicking a different item in the Listbox), I want to present them with a warning - giving the option to either abandon their unsaved changes; or to remain with the current selection, and revert the Listbox selection which they just clicked.
If "Abandon changes" is selected, it works fine. However it runs into trouble when I try to revert the Listbox selection. I use an "EventsOn" Boolean to handle when the Change procedure should proceed, to avoid it calling itself. This seems to work, at the correct point in the code. However after EventsOn is reinstated, and after Exit Sub, it seems that the Change event is called again.
I do not know why the event is firing again. This results in the user being presented with the option a second time.
A lot of the following code has been stripped out because it relates to details of other form controls; loading/saving data from a database; and handling classes and dictionaries. However I have retained the relevant logic of the form controls:
Option Explicit
Dim NumberOfQuestions As Long
Dim EventsOn As Boolean
Dim SelectedListIndex As Long, CurrentQuestion As Long, QuestionSaved As Variant
Private Sub UserForm_Initialize()
' Stripped out lots of code here. Basically opens a recordset and loads values
ReDim QuestionSaved(1 To NumberOfQuestions) As Boolean
'
For X = 1 To NumberOfQuestions
lbox_QuestionList.AddItem "Question " & X ' Populate the listbox with items
QuestionSaved(X) = True ' Flag the initial state as saved, for each question
If Not X = rst.RecordCount Then rst.MoveNext
Next X
'
' Select the first question by default. Note that the Listbox ListIndex starts at 0, whereas the questions start at 1
SelectedListIndex = 0
CurrentQuestion = 1
EventsOn = True
lbox_QuestionList.ListIndex = SelectedListIndex
End Sub
Private Sub lbox_QuestionList_Change()
' Ensure this event does NOT keep firing in a loop, when changed programmatically
If Not EventsOn Then Exit Sub
'
If Not QuestionSaved(CurrentQuestion) Then
If MsgBox(Prompt:="Abandon changes to current question?", Title:="Current question not saved", Buttons:=vbYesNo + vbDefaultButton2) = vbYes Then
' Abandon changes = Yes
' Mark as saved
QuestionSaved(CurrentQuestion) = True
' Then proceed to change as normal
' (If the user comes back to this question, it will be re-loaded from memory in its original form)
' This works okay
Else
' Abandon changes = No
EventsOn = False ' So this sub is not called again
' REVERT the ListBox selection. Do this by recalling the current question number, and applying it to the ListIndex
SelectedListIndex = CurrentQuestion - 1 ' Remember that the index will be minus 1, due to indexing starting at 0
lbox_QuestionList.ListIndex = SelectedListIndex
EventsOn = True
Exit Sub ' This should be the end of it. But somehow, it's not...
End If
End If
' Proceed with loading a new question according to the new selected ListIndex
SelectedListIndex = lbox_QuestionList.ListIndex ' Recognise the current selection
' ListIndex starts at zero, so we need to add 1
CurrentQuestion = SelectedListIndex + 1
ShowQuestion CurrentQuestion
End Sub
Private Sub ShowQuestion(QuestionNumber As Long)
' Stripped out code for brevity. Basically loads details from a dictionary of classes, and populates into textboxes
End Sub
Private Sub cb_Save_Click()
' Stipped out code. Takes values of current text boxes and saves them into a class in a dictionary
' Mark the current question as saved:
QuestionSaved(CurrentQuestion) = True
End Sub
''''''''''' Event handlers ''''''''''''''
Private Sub tb_Question_Change()
DoChange
End Sub
' Several other form controls have similar events: all calling "DoChange" as below
Private Sub DoChange()
If Not EventsOn Then Exit Sub
QuestionSaved(CurrentQuestion) = False ' Flag the current question as NOT saved, if any changes are made to form values
End Sub
Naturally, I have searched for this problem - but there are no answers so far which have assisted me:
Listbox events firing strangely - relates to C# and not VBA
listbox selected item changed event fired two times - relates to C# and not VBA
vba listbox event fires twice - suggests that a SetFocus method of the Listbox could solve the issue. However I have tried this, and the problem remains
The logic of my code seems sound. The mystery is why the Change event is being called a second time, even after Exit Sub.
(curses to OP for getting this problem in my brain!)
In my testing, I used the following UserForm:
The code below uses the ListBox1_AfterUpdate event, and I believe it may work for you.
Option Explicit
Private Const TOTAL_QUESTIONS As Long = 3
Private qSaved As Variant
Private selectedDuringTextboxChange As Long
Private eventsInProgress As Long
Private Sub ListBox1_AfterUpdate()
Debug.Print "listbox clicked, item " & (ListItemSelected() + 1) & " selected"
If eventsInProgress > 0 Then
Debug.Print " ... event in progress, exiting"
eventsInProgress = eventsInProgress - 1
Exit Sub
End If
If Not qSaved(selectedDuringTextboxChange) Then
Dim answer As VbMsgBoxResult
answer = MsgBox("Abandon changes?", vbYesNo + vbDefaultButton2)
If answer = vbYes Then
Debug.Print "yes, abandon the changes"
qSaved(selectedDuringTextboxChange) = True
Else
Debug.Print "nope, keep the changes"
'--- return to the previously selected list item
eventsInProgress = eventsInProgress + 1
UnselectAll
ListBox1.Selected(selectedDuringTextboxChange - 1) = True
ListBox1.ListIndex = selectedDuringTextboxChange - 1
End If
End If
End Sub
Private Sub QuitButton_Click()
Me.Hide
End Sub
Private Sub SaveButton_Click()
qSaved(ListBox1.ListIndex + 1) = True
End Sub
Private Sub TextBox1_Change()
selectedDuringTextboxChange = ListBox1.ListIndex + 1
qSaved(selectedDuringTextboxChange) = False
Debug.Print "changed text for question " & selectedDuringTextboxChange
End Sub
Private Sub UserForm_Initialize()
ReDim qSaved(1 To TOTAL_QUESTIONS)
selectedDuringTextboxChange = 1
With ListBox1
Dim i As Long
For i = 1 To TOTAL_QUESTIONS
.AddItem "Question " & i
qSaved(i) = True
Next i
.Selected(0) = True
End With
eventsInProgress = False
End Sub
Private Sub UnselectAll()
eventsInProgress = eventsInProgress + 1
With ListBox1
Dim i As Long
For i = 0 To .ListCount - 1
.Selected(i) = False
Next i
End With
eventsInProgress = eventsInProgress - 1
End Sub
Private Function ListItemSelected() As Long
ListItemSelected = -1
With ListBox1
Dim i As Long
For i = 0 To .ListCount - 1
If .Selected(i) Then
ListItemSelected = i
End If
Next i
End With
End Function
Private Sub WhichListItem_Click()
With ListBox1
Dim i As Long
For i = 0 To .ListCount - 1
Debug.Print "listbox item(" & i & ") = " & .Selected(i)
Next i
End With
Debug.Print "eventsInProgress = " & eventsInProgress
End Sub
After looking into it for awhile, it appears that having the listbox set its own listindex from within its own change event (effectively recursively calling it) causes some weird backend issues. Fortunately, it's easy enough to deal with by migrating that bit out to its own function. After some experimenting, the best way to do it would be to create a function that clears and repopulates the listbox, so create this function in your UserForm code:
Private Function PopulateListbox(Optional ByVal arg_lSelected As Long = -1)
Me.lbox_QuestionList.Clear
Dim X As Long '
For X = 1 To NumberofQuestions
lbox_QuestionList.AddItem "Question " & X ' Populate the listbox with items
QuestionSaved(X) = True ' Flag the initial state as saved, for each question
'If Not X = rst.RecordCount Then rst.MoveNext
Next X
Me.lbox_QuestionList.ListIndex = arg_lSelected
End Function
Now adjust your Initialize event to look like this (note that you need to define NumberofQuestions here, and then call the new function at the end to populate the listbox and select the first entry):
Private Sub UserForm_Initialize()
' Stripped out lots of code here. Basically opens a recordset and loads values
NumberofQuestions = 3 'This is where NumberofQuestions gets defined
ReDim QuestionSaved(1 To NumberofQuestions)
ReDim aAnswers(1 To NumberofQuestions)
'
' Select the first question by default. Note that the Listbox ListIndex starts at 0, whereas the questions start at 1
SelectedListIndex = 0
CurrentQuestion = 1
EventsOn = True
PopulateListbox SelectedListIndex 'Call the new function and set the 1st selection
End Sub
Lastly, update your listbox_change event to look like this (basically just outsourcing the setting of the listbox entry to the new function):
Private Sub lbox_QuestionList_Change()
' Ensure this event does NOT keep firing in a loop, when changed programmatically
If Not EventsOn Then Exit Sub
'
If Not QuestionSaved(CurrentQuestion) Or aAnswers(CurrentQuestion) <> Me.tb_Question.Text Then 'I added the second condition for testing purposes, may not be necessary in your full code
If MsgBox(Prompt:="Abandon changes to current question?", Title:="Current question not saved", Buttons:=vbYesNo + vbDefaultButton2) = vbYes Then
' Abandon changes = Yes
' Mark as saved
QuestionSaved(CurrentQuestion) = True
' Then proceed to change as normal
' (If the user comes back to this question, it will be re-loaded from memory in its original form)
' This works okay
Else
' Abandon changes = No
EventsOn = False ' So this sub is not called again
' REVERT the ListBox selection. Do this by recalling the current question number, and applying it to the ListIndex
SelectedListIndex = CurrentQuestion - 1 ' Remember that the index will be minus 1, due to indexing starting at 0
PopulateListbox SelectedListIndex 'Call your new function here
EventsOn = True
Exit Sub ' This should be the end of it. But somehow, it's not...
End If
End If
' Proceed with loading a new question according to the new selected ListIndex
SelectedListIndex = lbox_QuestionList.ListIndex ' Recognise the current selection
' ListIndex starts at zero, so we need to add 1
CurrentQuestion = SelectedListIndex + 1
ShowQuestion CurrentQuestion
End Sub
I had a problem with a Private Sub ListBox_Click() running twice.
When I cleared the ControlSource in the list box properties it fixed the problem. I had to add a line of code to specifically write the value from the listbox to the cell in the worksheet. At first the cell would not display the data so I set the range name to another cell and that was OK. So, I then dragged and dropped the new cell into the original location.
I don't understand where the problem originated, but the fix worked.
I had a similar unexpected issue, so maybe someone will find this result helpful.
Within a multi-selection-enabled Listbox_Change event, I checked the value of the currently-selected item to see whether it had been checked or unchecked.
Private Sub lstBox_Change()
With lstBox
If .Selected(.ListIndex) Then
' Call Method A.
Else
' Call Method B.
End If
End With
End Sub
When the list was checked, it would properly detect the selection and call A--but then, when stepping through the code and reaching the Change event's End Sub, the checkbox would automatically become unselected, and the Change event would fire again. Note that I wasn't setting any value in the ListBox itself; I was only checking to see whether the current item was checked or unchecked. But, somehow, that triggered it to unselect itself. (Also, this only seemed to happen on the first call to the Change event. Thereafter it behaved normally.)
I tried some of the other fixes, but BeforeUpdate and AfterUpdate never seemed to fire at all. The problem went away when I moved the selection test outside of the If statement and put the result into a Boolean instead:
Private Sub lstBox_Change()
With lstBox
BooleanResult = (.Selected(.ListIndex) = True)
If BooleanResult Then
' Call Method A.
Else
' Call Method B.
End If
End With
End Sub
After that, the ListBox consistently behaved as expected.
On the listbox, an user has an option to add dates. I am trying to also give the user the option to remove dates by clicking on a button. My code looks like this:
Private Sub RemoveDate_Click()
Dim objSpecifyDates As Object
Dim i As Integer
Set objSpecifyDates = ActiveSheet.OLEObjects("SpecifyDatesListBox").Object
If objSpecifyDates.listCount = 0 Then
Exit Sub
Else
For i = 0 To objSpecifyDates.listCount - 1
If Not objSpecifyDates.Selected(i) Then
objSpecifyDates.RemoveItem (i)
End If
Next
End If
End Sub
The problem with this code is once an item is removed, it returns error at the last loop since that index became nonexistent. Is there a way to restart the loop once it removes an item?
When removing things like this you need to move backwards through the collection.
For i = objSpecifyDates.listCount - 1 To 0 Step -1
...
Next i
I have a command button that processes selected items in a ListBox (ListBox4). While i CAN deselect all the items in that command's Click() procedure, I wanted to, if the user clicked in the ListBox, to THEN deselect everything in the ListBox, prior to their selecting again.
I have code like the following, but it never seems to get called:
Private Sub ListBox4_Click()
If Apply_Format_Occurred Then
For i = 0 To ListBox4.ListCount - 1
ListBox4.Selected(i) = False
Next i
End Sub
Do i need an outside command, etc to do this? I was hoping to be able to do it like how i described.
Any help is greatly appreciated!
thanks,
Russ
You can use the GotFocus event of the ListBox so the code runs when the ListBox receives focus from the user.
Here is an example showing coding for the button and the ListBox:
Dim Apply_Format_Occurred As Boolean
Private Sub CommandButton1_Click()
'<other processes>
Apply_Format_Occurred = True
End Sub
Private Sub ListBox4_Change()
If Apply_Format_Occurred Then
For i = 0 To ListBox4.ListCount - 1
ListBox4.Selected(i) = False
Next i
Apply_Format_Occurred = False
End If
End Sub
I see this thread is old and maybe there is an easy or more elegant way to un-select a list box item. But, I figured out a way for my needs. In my case I wanted the list box to un-select only if the same item was clicked. If a new item was clicked, it would be selected as normal. I had to use two static variable (a boolean and "old selection" placeholder).
There's probably a much easier way. But, I'm new to it and and couldn't find it. Hope this helps. Setting selectedindex to -1 unselects everything. Altenatively and as effective is listboxName.ClearSelected()
Private Sub lstPendingQuotes_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lstPendingQuotes.Click
Static blnSelectable As Boolean = True 'use static boolean to retain value between calls
Static objOldSelected As Object = lstPendingQuotes.SelectedIndex 'use old selected in case a different index is selected
'if nothing is already selected (as in first form open) allow the selection but change boolean to false and set the OldSelected variable to the current selection
If blnSelectable Then
blnSelectable = False
objOldSelected = lstPendingQuotes.SelectedIndex
ElseIf lstPendingQuotes.SelectedIndex = objOldSelected Then 'if an item is selected and the same item is clicked un-select all and reset boolean to true for a new possible selection
lstPendingQuotes.SelectedIndex = -1 'can substitute lstPendingQuotes.ClearSelected()
blnSelectable = True
Else 'If a different index is chosen allow the new selection and change boolean to false so if the same item is clicked it will un-select
objOldSelected = lstPendingQuotes.SelectedIndex
blnSelectable = False
End If
End Sub
I had an onclick event for my listbox. I tried listbox.listindex=-1 at the end of the code in that event. It wouldn't work.
I created a separate AfterUpdate event with the single line:
listbox.ListIndex = -1
works like a charm
I am getting an XML that contains data to be bound to combobox. While binding this data each time when an item is added to the combobox, its change event is fired. I want to fire change event only after data is bound and user selects any item.
Any help, code that can solve this problem?
Use a flag to indicate whether or not you want to handle the event;
private mblIsUpdating as boolean
...
sub addDataFromXml
mblIsUpdating = true
combo.additem ...
mblIsUpdating = false
end sub
sub combo_change
if (mblIsUpdating) then exit function
//handle change
end sub
In my experience, the combobox change event only fires when changing the combobox list of items if the combobox's value is not null. If this problem is happening when you first initialize the combobox, don't assign a default value until after you fill the combobox.
If you need to change the combobox list at other times, like Alex K says, create a boolean flag to indicate if you want to ignore the change event.
I'm not clear from your question if the change event is firing once as you populate the combobox or once for each .AddItem. If it is the latter issue, then you can cut down on the number of change events by creating an array of values for your combobox and assigning it to the combobox's .List.
Here is an example with a 2-d array that is populating the combobox with the names and paths of all the open workbooks. (A 1-d array also works.)
Private Sub InitializeComboBox()
Dim aList() As String
Dim i As Integer, iMax As Integer
' build combobox list with zero-based array
iMax = Application.Workbooks.Count - 1
ReDim aList(iMax, 2)
For i = 0 To iMax
With Application.Workbooks(i + 1)
aList(i, 0) = i
aList(i, 1) = .Name
aList(i, 2) = .Path
End With
Next i
With Me.ComboBox1
.ColumnCount = 3
.ColumnWidths = "0 pt;80 pt;220 pt"
.ListWidth = "300 pt"
.List = aList
End With
End Sub