why is this worker/ thread not stopping? - vb.net

I have a panel which has a several comboboxes in it and a button to close the panel. I set up the comboboxes to get populated with another thread because it has many entries. So the background worker starts and everything goes as expected until you close the panel. If I close the panel before the background worker is finished, I get an error. Let me explain:
I start the process of by starting the background worker:
get_info.RunWorkerAsync()
The close button sub looks like this:
Private Sub close_everything()
' dispose of the panel that holds the comboboxes
info_panel.dispose()
' tell the background worker to stop working
get_info.CancelAsync()
End Sub
I set up the background worker like this:
Private Sub getInfo_doWork()
'populate the combo boxes
populate_last_names()
End Sub
Then the subroutine to populate the last name looks like this:
Private Sub populate_last_names()
' get the names
get_names()
' since the combobox control is in another thread, I need to use the Invoke method to access it
' put them in the combobox
for a as integer = 0 to last_names.count - 1
last_name_box.Invoke(Sub() last_name_box.Items.Add(last_names(a)))
next
End Sub
When the close button is pressed, I get an error stating that the combobox has been disposed. So I added a check for the background worker first. So my code changed to:
' put them in the combobox
for a as integer = 0 to last_names.count - 1
if not(get_info.CancellationPending) then
last_name_box.Invoke(Sub() last_name_box.Items.Add(last_names(a)))
end if
next
but I still got the same error, so I added another check and changed the code to this:
' put them in the combobox
for a as integer = 0 to last_names.count - 1
if not(get_info.CancellationPending) then
if not(last_name_box.IsDisposed) then
last_name_box.Invoke(Sub() last_name_box.Items.Add(last_names(a)))
end if
end if
next
and I STILL get an error stating that the last_name box has been disposed, if I close the panel before the loop is finished.
The error even states that the value of last_name_box.IsDisposed is TRUE and the value of CancellationPending is TRUE. So why is it executing the next line if it is TRUE?
How can I stop this from happening?

Your code is badly constructed. What you should be doing is getting the data in the DoWork event handler and then passing it to the RunWorkerCompleted event handler via the e.Result property. You then populate the controls with the data in the RunWorkerCompleted event handler, which is executed on the UI thread.
You should also not be hiding the Panel anywhere but in the RunWorkerCompleted event handler. You request cancellation from the UI thread and then you actually perform the cancellation in the DoWork event handler. In the RunWorkerCompleted event handler, you check whether the operation was cancelled or not. If it was then you simply hide the Panel, otherwise you populated the list(s).

Try stopping the background worker first:
Private Sub close_everything()
' tell the background worker to stop working
get_info.CancelAsync()
' dispose of the panel that holds the comboboxes
info_panel.dispose()
End Sub

Since a viable answer to my problem did not seem possible, I ended up switching to a textbox with autofill instead of a combobox with autofill. I was just using the combo box because of the autofill capability and didn't realize the textbox could do the same thing. The data is gathered in the background worker and the source for the autofill is assigned after the data is collected in the main UI. I couldn't accomplish exactly what I wanted so I settled for this. There is still some delay but it is manageable.

Related

Listbox not displaying in UserForm

I'm trying to display a listbox in a UserForm from a separate sheet called "Fields". The problem is, the list will not display. It shows as a drop down arrow next to the cell, but not in the userform like I'm wanting.
Private Sub UserForm_Activate2()
On Error Resume Next
Me.ListBox2.Clear
For Each element In gFieldsListArr
Me.ListBox2.AddItem element
Next element
UserForm_initialize2
End Sub
Private Sub UserForm_initialize2()
For Each element In Split(gCellCurrVal2, ",")
For ii = 0 To ListBox2.ListCount - 1
If element = Me.ListBox2.List(ii) Then
Me.ListBox2.Selected(ii) = True
End If
Next ii
Next element
End Sub
TL;DR: You can't rename event handlers or change their member signature in any way*, because the correct member definition is defined by the event, not its handlers.
Watch the dropdowns at the top of the editor as you navigate between procedure scopes:
Whenever the left-side dropdown says (General), you're not inside an event handler.
Contrast with:
The left-side dropdown is listing all available interfaces and event sources; to handle the events of a form, you must pick UserForm from that dropdown, and then pick a member from the right-side dropdown.
When you do this, the VBE creates the procedure stubs for you, with the correct name and signature every time.
Whenever you navigate to what's intended to be an event handler and the left-side dropdown says (General), you're looking at dead code that isn't responding to any events.
* You may change the accessibility from Private to Public, but invoking an event handler directly is a design smell so there shouldn't be a need to do that. You may change the parameter names, but not their type; renaming handler parameters is a rather surprising thing to do though, and best avoided too. So yeah, best not change these member signatures in any way.

Excel VBA - Calling TextBox Exit Event on Userform manually?

I'm building a userform where it has two text boxes to enter dates. Once the date is entered, I'm validating them when the Exit event fires. If the validation turns up that the user data isn't what is needed, the user is notified, the text box is cleared, and the focus is returned back to the textbox.
The issue comes if the user uses the mouse to select outside of the box, rather than Tab. If Tab is used, it fires perfectly and as expected, and the field is cleared and the focus is returned. If the mouse is used, it doesn't fire. According to this article, this is expected behavior (It's for Access, but I didn't see the similar relevant MSDN article for Excel.)
So instead I tried the AfterUpdate event. However, SetFocus doesn't work within an AfterUpdate event, I'm assuming because of the chain of events as outlined in the response to this question. Thus, I don't have a way to return the focus back to the textbox after it has fired. That thread had a suggestion as an alternate answer to SetFocus to another control and come back as a workaround, but that doesn't work for me, so I assume that may be an Access-specific workaround.
My last option I've considered is having the AfterUpdate event just call the Exit event, however the Exit event has a required argument (ByVal Cancel As MSForms.ReturnBoolean), which is how you cancel out of the exit and return the user to the textbox. As such, there isn't a value that you can pass to it that doesn't throw an error that I can find (the closest I found was passing Nothing but it failed out when trying to set it to True later to cancel the exit.)
Is there a way to achieve what I'm looking for here, or should I just stick to the AfterUpdate and ignore the SetFocus I'm trying to achieve?
I know that this was answered a few months back but giving an alternative solution. For any one who finds this question.
For validation of Excel Textbox data use the BeforeUpdate Event, it fires before the AfterUpdate Event and has the ability to prevent losing Focus on the control.
Rework the sample code to your requirements
Remember Cancel = True stops the control update to the control and it remains in focus.
Private Sub TextBox1_BeforeUpdate(ByVal Cancel As MSForms.ReturnBoolean)
Cancel = doValidation(Textbox1.Text) 'Validation Route
End Sub
Private Function doValidation(strText) as Boolean
'Do Validation
if Valid then
doValidation = False
Else
msgBox "Not Valid"
doValidation = True
End if
End Sub
In my opinion this is the easiest way to validate an input on an Excel Userform Textbox
I can't right now find the correct MSDN article at this time, all Google wants to return is Access Results.
Exit event works on all the mouse clicks which fire up Enter for another Control on the Form. But When you click, directly on the form instead of any other control, nothing happens.
Here, use the ActiveControl property to determine about the last control you were in, before exiting and moving to user form.
Sample Code, rework it according to your requirement.
Private Sub TextBox1_Exit(ByVal Cancel As MSForms.ReturnBoolean)
Call doValidation(Me.TextBox1.Text) '/ Validation Routine when user leaves TextBox
End Sub
Private Sub UserForm_Click()
'/ If user clicked on the user form instead of ay other control
If Me.ActiveControl.Name = Me.TextBox1.Name Then
Call doValidation(Me.TextBox1.Text) '/ Validation Routine when user leaves TextBox
End If
End Sub
Private Sub doValidation(strText)
MsgBox strText
End Sub
Advise: For Date inputs, use DateTimePicker instead of TextBox, will save you from alot of trouble in future.
In VBA you can call any defined sub or function with the word Call Subname:
e.g. Call Textbox1_exit(params)
However from the somewhat confusing description I believe your problem is that you limit yourself to just a few event functions. I would suggest exploring all event functions and see which one is a good fit for your event fire.
Here is a list of events and their sequences in Access VBA:
https://msdn.microsoft.com/en-us/library/office/jj249049.aspx
and the order of form events:
https://support.office.com/en-us/article/Order-of-events-for-database-objects-e76fbbfe-6180-4a52-8787-ce86553682f9
I think for your application from the description you gave the lost_focus or got_focus for certain components might be useful.
Furthermore, you can manually set the focus to almost any component it is a built in method: compName.SetFocus()

Wait for 1 second before starting code again - VB.NET

I desperately need help with a game I am making. For a bit of context, i am making a memory game and i have the following piece of code that is being troublesome. I have a bunch of labels on the form, 16 to be exact, with 1 randomly generated symbol placed in each. Each symbol appears in the labels twice.
------------------------------Continued----------------------------------------
'MsgBox("hello") 'used to check if the second inccorect press shows up - it does show but instantly changes colour
'''''''''''''''''NEED SOME CODE THAT PAUSES IT HERE'''''''''''''''
labels(0).ForeColor = Color.DarkRed
sender.ForeColor = Color.DarkRed
End If
flips = 1
End If
End If
tmrmemory.Enabled = True ' starts the timer after the user clicks the first label
End Sub
What's supposed to happen is that when the labels clicked don't match, it should show both the clicked labels for a short period before changing them both back to "DarkRed" which is the colour of the form's background.
I have tried using a timer but then i can't use sender.forecolor=color.darkred because it is not declared globally.
I have also tried using the command Threading.Thread.Sleep(500) but it still doesn't show the second incorrect click. I know that the code i have used works because when i use the message box, i can see both symbols and when the two clicks are correct, it stays.
Threading.Thread.Sleep(500) will actually pause your code for half a second. However during this time it won't do anything, not even refresh your controls. To get the effect you want, you need to call the YourControl.Refresh method before calling Threading.Thread.Sleep to force the control to redraw immediately.
On a side note, I would advise you not to call Threading.Thread.Sleep on UI thread. It will give a feeling of program hang. Instead do your work on a separate thread. You can either do all the work yourself right from creating a separate thread to destroying it, or use the BackgroundWorker control which has all the functionality built in.
Here is the link to an article I wrote a long time ago regarding BackgroundWorker that might be useful for you:
http://www.vbforums.com/showthread.php?680130-Correct-way-to-use-the-BackgroundWorker
Declare a variable outside the sub that stores what label should be flipped when the timer ends.
Label click sets
storedLabel = sender
Timer tick sets storedLabel.ForeColor = Color.DarkRed

Button Click vs ShortCut Key (ALT) Event firing order

I have a Save button on a form with a shortcut key set (ALT+S).
In my datagrid in the cellvalidated event I make a determination if I can enable or disable this button for the users.
Similar to the old VB6 problem of Event firing order....
If the button was currently enabled, but user puts invalid data in the datagrid cell. If they Click on the button, the cell validated event fires, disables the button - no issue. When, instead, they input the bad data, and do ALT+S, the cell validated event fires, but in this case the Button_Click event still fires.
Any way to prevent this? Basically if the use the ALT+, I don't want the Click event to fire.
If everything else fails - there is this method (in pseudo-code)
Class Form
private _validated as Boolean
sub Cell_validate
' validate here and set
_validated = true/false
end sub
sub Button_Click
If not _validated then Return
' have your save logic here
end sub
End class
This way you will protect your code from executing actual logic of Button_Click when button disabled.

Automatically execute some code after the UserForm is drawn

I have created a UserForm where the user is required to fill-in three fields. The macro attempts to auto-detect these fields' values in the UserForm_Initialize() event, then displays the found values in the three fields, but the user can change them. The auto-detection takes a few seconds, though, and delays the appearance of the UserForm. I'd like the UserForm to appear with its fields blank before the auto-detection procedure, then have the auto-detection procedure fill the fields automatically. What would be the best way to do this? Making the UserForm non-modal makes the macro run without waiting for the user's input, which is problematic. I don't want to have an "auto-detect" button: this has to be done automatically.
Use the Activate() event instead of Initialize() :)
Private Sub UserForm_Activate()
End Sub
FOLLOWUP
Thanks! It works, but there seems to be a bug: the dialog is drawn all white until the macro completes. screenshot (the dialog should be gray)
No. It is not a bug :) Try This. Add Doevents as shown below.
Private Sub UserForm_Activate()
UserForm1.ProgressBar1.Value = 0
starttime = Timer
While Timer - starttime < 1
UserForm1.ProgressBar1.Value = (Timer - starttime) * 100
DoEvents
Wend
End Sub
HTH
Sid
There is a simpler way to do this...
1) On your userform create a new 'CommandButton' that will execute the macro you wish to trigger.
2) Set the height and the width of the button to 0
3) Make sure the 'TabIndex' parameter for the button is 0... This will create an 'invisible' CommandButton that will receive the focus as soon as the form opens.
4) In the calling routine, immediately before the command that 'shows' the userform enter the line - 'Application.SendKeys "~"'
How it works...
The commandbutton created in (1) is a valid control just like any other wxcept that you can't see it or click it with the mousebutton. The 'SendKeys' command replicates a left mouse key click which is stored in the keyboard buffer until the form displays when it will be read. This has exactly the same effect as a mouse click and will run the required macro.
Incidentally, if you are calling the macro from more than one location and wish to have different actions dependant on the source of the call, you can add more than one 'invisible' button and add "{Tab}" before the "~" character to tab the focus through the available controls. e.g. 'Application.SendKeys "{Tab}~"' will activate the button with the 'TabIndex' parameter set as 1. 'Application.SendKeys "{Tab}{Tab}{Tab}{Tab}~" ' will activate the button with the 'TabIndex' parameter set as 4 etc.
RF
I'd suggest using a timer. Open the form with the input fields disabled and empty, and set the timer to fire within a couple of hundred milliseconds. This should allow the form to be displayed immediately. Do your auto-detection within the timer's tick event (disable the timer first), then enable the fields and fill in the detected values.