Button Click vs ShortCut Key (ALT) Event firing order - vb.net

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.

Related

Check If User Pressed Enter to leave DataGridViewCell

I am working in VB.Net programming user functionality into a DataGridView. Currently, I am trying to allow users to update the data that is being shown in the DataGridView by editting what is in the Cells.
Right now I have a method to detect when the user begins the edit the cell:
Private Sub dataTable_CellBeginEdit(ByVal sender As Object, ByVal e As DataGridViewCellCancelEventArgs) Handles dataTable.CellBeginEdit
oldCellVal = dataTable.Rows(e.RowIndex).Cells(e.ColumnIndex).Value
End Sub
The point of the method is to store the old data in the cell in memory so that it can be restored later if the user decides not to edit the cell.
Now I am not sure how to set up the method for when the user completes the edit. I know there is a CellEndEdit event I can make a Handler for, but in that method how would I detect how they left the Cell?
I'd like to set it up so that if my User presses the Enter key, only then is their edit submitted. If they use the Arrow Keys, or the ESC key to exit the Cell, they instead get a "Would you like to stop editting?" prompt. This makes it sound like I need a keypress event, but if I do that kind of event, how do I properly detect the Cell that was modified? I need the know the row and column index of the updated Cell in order to properly submit the changes.
So how should I go about doing this? KeyPress or CellEndEdit? Or is there something else I haven't considered?

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()

Selected Index and Cell Value in Detail SubForm

I'm trying to implement a very simple subform document grid on a form:
I have everything except getting the file path when the user clicks the grid. How can I get the filepath value from the user row click event ?
Sorry if my terminology is off I rarely write vba
You can create a public subroutine that references your FilePath field and then reference this subroutine in the on-click event of each of the fields on your subform.
So if your subform looks like this:
Go in to Design View of the form, and select one of your fields:
With a field selected, go to the Property Sheet on the right-hand side and go to the Event tab and click the [...] button on the On Click event:
You'll be taken to the form's VBA script module and it'll create the initial VBA for the on-click event of the field you selected:
Ignore that field's on-click event VBA for now, and instead move your cursor to the top and make some room for a public subroutine above the field's on-click event.
Write something like this at the top:
Public Sub GetFilePath()
Debug.Print Me.FilePath
End Sub
So your code in Access should now look something like this:
That public sub I've called GetFilePath can now be referenced in the on-click event of every field in your subform. Let's finish the on-click event of the ID field that we just started...
...and also reference the same public sub in every other field's on-click event (again, by selecting the field in Design View and then clicking [...] button in Property Sheet's on-click event):
In the VBA editor, make sure you have the Immediate Window open; it should be in the area below your VBA code. If it's not there press Ctrl + G or go to View > Immediate Window:
With the VBA editor and Immediate Window still open, go back to your form and put it into Form View.
Click on any row and you should see the FilePath data for the row you've clicked on print out in the Immediate Window (this is what Debug.Print does):
You probably don't want the FilePath to go the Immediate Window, but as you haven't specified where you want it to go I figured this would at least illustrate how you can get at that data by clicking on a record in your subform.
All you need to do is replace the Debug.Print Me.FilePath line to whatever is useful to you.
Hope this was enough to get you started though :)
MS Access does not provide Row click event. You have to either perform, [Form onClick Event] or ideally make the filePath as HyperLink and onClicking FilePath retrieve its value.
When you go for [Form onClick event] you will get the FilePath from selected row. But the click event is only fired if you are clicking the form and none of the fields.
As above mentioned, make the FilePAth field as hyperlink, add onClick event to it and retrieve the value.
sorry for upwarming but the accepted answer is not the best way to go.
Instead of creating an OnClick-event for every column in that subform (for each control),
you should rather use the OnCurrent event of the Subform itself, which is triggered whenever you change the current record. This basically happens whenever you click on another row in the subform.
Doing so will save you work whenever you extend the subform with a new column.
in that OnCurrent Event of the Subform you will then have the same code.
Basically it should look like this:
Public Sub Form_Current()
Debug.Print Me.FilePath
End Sub

why is this worker/ thread not stopping?

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.

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.