The attached VBA procedures are for a progress bar userform. Everything works as expected, except that the cancel button is intermittently unresponsive.
I say intermittently because, 95% of the time I have to click the cancel button numerous times before the procedure stops. I can see the button click event being animated, but the procedure isn't being interrupted. It looks as though something is stealing focus from the cancel button before the button down event can occur.
The escape and window close buttons respond as expected with one click.
What do I need to do to make the cancel button respond correctly? Thanks!
Update: I noticed that when I click and hold down on the cancel button, instead of the button staying "down" it gets kicked back up. So apparently something is resetting the button state to up, fast enough that the procedure is not catching the down state to fire the click event.
Here is the code in the userform module (named UserForm1):
Private mbooUserCancel As Boolean
Public Property Get UserCancel() As Boolean
UserCancel = mbooUserCancel
End Property
Private Property Let UserCancel(ByVal booUserCancel As Boolean)
mbooUserCancel = booUserCancel
End Property
Public Sub UpdateProgress(CountTotal As Long, CountProgress As Long)
On Error GoTo Error_Handler
ProgressBar1.Value = CountProgress / CountTotal * 100
DoEvents
Error_Handler:
If Err.Number = 18 Then CommandButton1_Click
End Sub
Private Sub CommandButton1_Click()
Hide
UserCancel = True
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
Cancel = True
CommandButton1_Click
End Sub
Private Sub UserForm_Activate()
With Application
.Interactive = False
.EnableCancelKey = xlErrorHandler
End With
End Sub
Private Sub UserForm_Terminate()
Application.Interactive = True
End Sub
Here is the code for the module (named Module1) that calls the UserForm1:
Sub TestProgress()
On Error GoTo Error_Handler
Dim objUserForm As New UserForm1
Dim lngCounter As Long
Dim lngSubCounter As Long
With objUserForm
.Show vbModeless
DoEvents
For lngCounter = 1 To 5
If .UserCancel Then GoTo Exit_Sub
For lngSubCounter = 1 To 100000000
Next lngSubCounter
.UpdateProgress 5, lngCounter
Next lngCounter
Application.Wait Now + TimeValue("0:00:02")
.Hide
End With
Exit_Sub:
If objUserForm.UserCancel Then
MsgBox "User Cancelled from UserForm1"
End If
Exit Sub
Error_Handler:
If Err.Number = 18 Then
Unload objUserForm
MsgBox "User Cancelled from Module1"
End If
End Sub
Works for me on the first click every time. Try unloading any add-ins or any other code that could be running inside the container app and see if that helps.
If people are still in need of the answer, well I researched & cracked it!
For the click on cancel button, code should be;
Private Sub CommandButton1_Click()
Unload Me
'this bit ends all macros
End
End Sub
The answer is to use modal userforms for progress bars so that the button click event can fire without getting obscured by processing in the calling procedure.
Related
I want to keep a variable from Form to Workbook Events, it works from Form to Module, but doesn't work from Form to Workbook Event, the Variable "Salvar" stay as "Empty" rather than "True"...
'WorkBook Event
Public Salvar As Boolean
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
UserForm_Salvar.Show 'Calls "Private Sub Botao_Salvar_Click()"
If SaveAsUI = True Then
If Salvar = True Then
Cancel = False
Salvar = False
Else
Cancel = True
End If
End If
End Sub
'UserForm
Private Sub Botao_Salvar_Click()
Dim PassWord As Variant
PassWord = Senha_TextBox
If PassWord = 123 Then
Unload UserForm_Salvar
Salvar = True
End If
End Sub 'Go back to "Workbook_BeforeSave"
You don't really need a global variable if you declare the form as an object, which is the better practice anyway, and then handle it correctly. Use any of the form's properties or the form's controls' properties to convey the message. I recommend to use the Tag property. Here is the code in the worksheet's module. Observe that I named the form FrmSalvar.
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
' 091
Dim Form As FrmSalvar
If SaveAsUI = True Then
Set Form = New FrmSalvar ' create a new instance of the form
With Form
.Show ' the Form takes control
' code resumes here with Form hidden but still in memory
Cancel = (Val(.Tag) <> True) ' True = -1
End With
Unload Form ' only now the form is destroyed
Set Form = Nothing
End If
End Sub
and the code in the Form's code sheet.
Private Sub Botao_Salver_Click()
' 091
Dim PassWord As String ' Textboxes handle text = strings
PassWord = Senha_TextBox.Value ' this is a string, even if numeric
If PassWord = "123" Then
Me.Tag = -1 ' write something to the Tag property
Else
MsgBox "The password is incorrect", vbInformation, "Access denied"
End If
Me.Hide ' hide the form
End Sub
The line Cancel = (Val(.Tag) <> True) in the BeforeSave procedure will crash if the user closed the form using the X button. This is because that button effectively unloads the form before your code can do it. In your system, that action should be paramount to "no password". Therefore this code will remedy the situation.
On Error Resume Next
Cancel = (Val(.Tag) <> True)
If Err.Number Then Cancel = True
That's a more dignified exit than gathering your skirts and fleeing the scene in panic. However, there is also a method by which you can simply not allow the form to be closed that way. I use the two event procedures below in most of my forms.
Private Sub CmdCancel_Click()
' 091
Me.Hide
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
' 091
Cancel = (CloseMode <> vbFormCode)
If Cancel Then CmdCancel_Click
End Sub
Read up about the QueryClose event here. In its above incarnation it just cancels what the user pressed and suggests alternative code which could be simply Me.Hide. I have Me.Hide in the procedure that responds to the Cancel button on my form and I rather invoke that procedure than allowing a second cancel exit from the form in my code. In this way there are just two exits from my form, with OK or with Cancel. Pressing the X is handled in the same way as when Cancel was pressed. Either way, the Me.Tag is preserved until the bitter end.
Change the value of Salvar before Unloading the UserForm.
If PassWord = 123 Then
Salvar = True
Unload UserForm_Salvar
End If
I have a button the executes a user form to display with a required password before executing the buttons command, however when the user form is displaying it freezes the rest of the sheet. On the user Form a I created a cancel button so that the user can exit out of the UserForm should he not know the password and use the other buttons I have on the sheet. When the user clicks the cancel button, it still executes the command even though he/she did not put a password in. The user Form works properly when you enter the correct password/incorrect password, its only when you click cancel that it does not work. Could any on please offer any help? see my code below for the button and the code for my cancel button
Sub Feeder_Schedule()
UserForm1.Show
If Sheets("Bulk_Data").Visible = xlVeryHidden Then
Sheets("Bulk_Data").Visible = True
End If
Sheets("Bulk_Data").Select
Sheets("Home").Visible = xlVeryHidden
End Sub
code for the Cancel button
Private Sub CommandButton1_Click()
Unload Me
End Sub
If you want to make it right change/add to your code of the userform
Option Explicit
' USERFORM CODE
Private m_Cancelled As Boolean
' Returns the cancelled value to the calling procedure
Public Property Get Cancelled() As Variant
Cancelled = m_Cancelled
End Property
Private Sub buttonCancel_Click()
' Hide the Userform and set cancelled to true
Hide
m_Cancelled = True
End Sub
' Handle user clicking on the X button
Private Sub UserForm_QueryClose(Cancel As Integer _
, CloseMode As Integer)
' Prevent the form being unloaded
If CloseMode = vbFormControlMenu Then Cancel = True
' Hide the Userform and set cancelled to true
Hide
m_Cancelled = True
End Sub
Your code could then look like that
Sub Feeder_Schedule()
Dim frm As UserForm1
Set frm = New UserForm1
frm.Show
If Not frm.Cancelled Then
If Sheets("Bulk_Data").Visible = xlVeryHidden Then
Sheets("Bulk_Data").Visible = True
End If
Sheets("Bulk_Data").Select
Sheets("Home").Visible = xlVeryHidden
End If
End Sub
Where should I put Load and Unload frm1 (Userform name is frm1) and where should I put Me.Show and Me.Hide?
The (x) button within the UserForm doesn't work.
My Load and Unload is within the Active-X command button code on Sheet1:
Private Sub cmdb1_Click()
Load frm1
Unload frm1
End Sub
This way my UserForm is initialized and I can run the code
Private Sub Userform_Initialize()
'Some other code that Works...
frm1.Show
End Sub
that shows my Userform. Now I have a command button in my Userform that has the code
Private Sub cmdbClose_Click()
Me.Hide
End Sub
which I use to hide the sub, upon which the last line within cmdb1_Click() is executed and UserForm is unloaded. This Works.
However when I press the (x) button in my UserForm, the following error appears
Debugger says error lies within cmdb1_Click(). I've tried adding a sub called UserForm_QueryClose(), but the error persists. If I'd have to guess, I'd say the error is caused by the way I handle Load and Unload, thus by cmdb1_Click().
EDIT:
My problem is solved. ShowUserform and cmdbClose_Click contain the code CallumDA suggests. My command button now has:
Private Sub cmdb1_Click()
Load frm1
Call ShowUserform
End Sub
I recommend that you create an instance of your userform:
Dim MyDialog As frm1
Set MyDialog = New frm1 'This fires Userform_Initialize
You can then easily check whether the form is loaded before attempting to unload it:
If Not MyDialog Is Nothing Then
Unload MyDialog
End If
I also recommend that you don't call the Show method from the form's Initialize event. Think of your userform as just another object in Excel and manage it from your main code body.
Also, I wouldn't unload it in the cmdbClose_Click event as suggested by CallumDA (though that works fine to solve the current issue). Often you will need to be able to refer to values on your form from your main code body, and if you unload it they won't be available. Instead, keep it like you had it in the first place:
Private Sub cmdbClose_Click()
Me.Hide
End Sub
So your main code body (in your activeX button) will look something like this:
Dim MyDialog as frm1
Set MyDialog = New frm1 'This fires Userform_Initialize
'Place any code you want to execute between the Initialize and Activate events of the form here
MyDialog.Show 'This fires Userform_Activate
'When the close button is clicked, execution will resume on the next line:
SomeVariable = MyDialog.SomeControl.Value
'etc.
If Not MyDialog Is Nothing Then
Unload MyDialog
End If
You can also catch the event that fires when a user clicks the "X" on the form, and prevent the form from being unloaded:
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = VbQueryClose.vbFormControlMenu Then
Cancel = True
Me.Hide
End If
End Sub
Lastly, often you need a Cancel button on the form. The way I handle this is to create a "Cancelled" property in the form's code-behind:
Public Cancelled as Boolean
'(Note You can create additional properties to store other values from the form.)
In the Cancel button's click event:
Private Sub cmdbCancel_Click()
Me.Cancelled = True
Me.Hide
End Sub
And in the main code body:
Dim MyDialog as frm1
Set MyDialog = New frm1
MyDialog.Show
If Not MyDialog.Cancelled Then
SomeVariable = MyDialog.SomeControl.Value
SomeOtherVariable = MyDialog.SomeOtherProperty
'etc.
End If
If Not MyDialog Is Nothing Then
Unload MyDialog
End If
(I know the above is not strictly the correct way to declare a property, but this will work fine. You can make it read-only in the usual way if you wish.)
Put this in a standard module and link it up to the button you use to show the userform
Sub ShowUserform
frm1.Show
End Sub
And then in the userform
Private Sub cmdbClose_Click()
Unload Me
End Sub
I struggled for years with forms in Excel. I could never understand how an object (a form) could be destroyed but still have it's code running.
I now use this code on every form that has [OK] and [Cancel] buttons, as it allows you to control the [OK], [Cancel] and [X] being clicked by the user, in the main code:
Option Explicit
Private cancelled As Boolean
Public Property Get IsCancelled() As Boolean
IsCancelled = cancelled
End Property
Private Sub OkButton_Click()
Hide
End Sub
Private Sub CancelButton_Click()
OnCancel
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = VbQueryClose.vbFormControlMenu Then
Cancel = True
OnCancel
End If
End Sub
Private Sub OnCancel()
cancelled = True
Hide
End Sub
You can then use the form as an instance as follows:
With New frm1
.Show
If Not .IsCancelled Then
' do your stuff ...
End If
End With
or you can use it as an object (variable?) as noted above such as:
Dim MyDialog As frm1
Set MyDialog = New frm1 'This fires Userform_Initialize
Then similar instance noted above.
This is all based on an excellent article by RubberDuck which goes into more detail and explains the code given above.
I'm having an issue with the way a listbox behaves on an Excel form. Steps to reproduce the issue:
Create a user form with one listbox control
Use following code with this user form:
Private Sub ListBox1_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
Me.ListBox1.Locked = True
Me.ListBox1.Locked = False
End Sub
Private Sub UserForm_Initialize()
Dim i As Integer
For i = 1 To 10
Me.ListBox1.AddItem i
Next i
End Sub
When the form is first shown, I am able to navigate the list box normally, using arrow keys and page keys. However, after the double-click event is triggered, all keyboard navigation has no effect, including tabbing to other controls (if they're on the form). Clicking on the listbox does seem to work, and the focus outline is shown correctly, but there's something wrong with the way the focus is handled after the listbox is locked and then unlocked. What is going on?
Using Office 2013 32-bit edition.
I managed to replicate this issue, and setting the focus somewhere else before locking and unlocking the listbox worked for me:
Private Sub ListBox1_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
Me.TextBox1.SetFocus 'or some other control.
Me.ListBox1.Locked = True
Me.ListBox1.Locked = False
Me.ListBox1.SetFocus
End Sub
2 solutions I tested that allows you to use Arrow Keys to navigate.
Given that there isn't a single click event handler, try call the Single Click Event after the DblClick (works all the time):
Private Sub ListBox1_Click()
Debug.Print "ListBox1_Click()"
End Sub
Private Sub ListBox1_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
Debug.Print "ListBox1_DblClick()"
With Me.ListBox1
.Locked = True
.Locked = False
End With
ListBox1_Click
End Sub
Private Sub UserForm_Initialize()
Dim i As Integer
For i = 1 To 10
Me.ListBox1.AddItem i
Next i
End Sub
Setting the Cancel = False at the end of DblClick. (Sometimes does not work!)
Private Sub ListBox1_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
Debug.Print "ListBox1_DblClick()"
With Me.ListBox1
.Locked = True
.Locked = False
End With
Cancel = False
End Sub
Private Sub UserForm_Initialize()
Dim i As Integer
For i = 1 To 10
Me.ListBox1.AddItem i
Next i
End Sub
Changing the zoom in the sheet that has the listbox worked for me.
Private Sub Worksheet_Activate()
Dim temp As Double
Application.ScreenUpdating = False
'Change worksheet zoom setting for the active window
temp = ActiveWindow.Zoom
ActiveWindow.Zoom = temp + 10
ActiveWindow.Zoom = temp
Application.ScreenUpdating = True
End Sub
Found some tips here and in google, but can't implement properly.
Say I have a loop that runs, and I need to show a box with a button "Cancel".
The code must run till I press the button.
In the following example I used For Loop and show iteration number in the Label1.Caption
' action of UserForm1 on Button Click
Private Sub CommandButton1_Click()
cancel = True
End Sub
Public cancel as boolean
Sub example ()
cancel = False
Dim i As Integer
For i = 1 To 1000
Application.Wait (Now + #12:00:01 AM#)
UserForm1.Label1.Caption = CStr(i)
UserForm1.Show vbModeless
If cancel = True Then
Exit For
End If
Next i
End Sub
This code runs, but it doesn't react on Button click.
If I do UserForm1.Show vbModal, then the code stops and waits till I click the button.
What am I doin wrong?
This is my code and it works perfectly when the userform is opened with frmTest.Show false
Dim cancelbool As Boolean
Function loopfunction()
Dim i As Integer
i = 0
Do Until cancelbool
DoEvents
Me.lblIteration.Caption = i
i = i + 1
Loop
End Function
Private Sub cmdCancel_Click()
cancelbool = True
End Sub
Private Sub UserForm_Activate()
loopfunction
End Sub
And by the way, the Application.Wait makes your app unresponsive