VBA Excel Sub continues running after Form was unloaded - vba

I don't know what happened but after I implemented a new code the 'Sub' continues running after I press my 'Cancel' button.
My 'Cancel' Button:
Private Sub CancelButton_Click()
If Cancel Then Exit Sub
Cancel = True
'Save Settings
....
Unload Me
End Sub
My Sub and new function:
Sub Example()
Dim myArr() as string
...
some loops....
If Cancel Then
Exit Sub
End If
myArr = NewFunction(a1,a2)
myVar = myArr(1)
...
End Sub
Function NewFunction(a1,a2) As String ()
Dim tmpArr() as string
...
ReDim tmpArr(1)
...
NewFunction = tmpArr()
End Function
Issues:
If I avoid execution of line myVar = myArr(1) the code continues running after canceling, so I need to stop execution manually. Why?
If line myVar = myArr(1) is executed, the form will be unloaded and error appears:
runtime error 9
Subscript out of range
Don't get it what is wrong. The code runs ok if I'm not canceling it. The problem only is with 'Cancel' button which was working perfect before...
Cheers, Andy
Form buttons:

This is correct code which works for me:
Dim Cancel As Boolean
Private Sub UserForm_Initialize()
Cancel = False
End Sub
Private Sub CancelButton_Click()
SetStatus ("Cancelling")
If Cancel = False Then
Cancel = True
Exit Sub
End If
'Save Settings
'... some code
If Cancel Then
Unload Me
End If
End Sub
Sub Example()
'...
'some loops....
If Cancel Then
'close open files...
'... some code
SetStatus ("Cancelled")
Exit Sub
End If
'...
End Sub
When 'Cancel' button is pressed, the code will stop in safe manner. Then the second click on 'Cancel' button the form will be unloaded.

Related

VBA raising events from modeless Userfrom

I'm trying to raise events from a modeless userform. My starting point is this excellent example. When I show the form modeless, the code raising the event executes, but the event handler never runs (I don't get the expected MsgBox when the Cancel button is clicked.) When I show the form modal, the events are handled as desired, but the form is no longer modeless as desired.
The userform named FormWithEvents has an OKButton and a CancelButton; here's the code behind:
Option Explicit
Public Event FormConfirmed()
Public Event FormCancelled(ByRef Cancel As Boolean)
Private Function OnCancel() As Boolean
Dim cancelCancellation As Boolean
RaiseEvent FormCancelled(cancelCancellation)
If Not cancelCancellation Then Me.Hide
OnCancel = cancelCancellation
End Function
Private Sub CancelButton_Click()
OnCancel
End Sub
Private Sub OKButton_Click()
Me.Hide
RaiseEvent FormConfirmed
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = VbQueryClose.vbFormControlMenu Then
Cancel = Not OnCancel
End If
End Sub
Here's the code for the Presenter class that shows the form:
Option Explicit
Private WithEvents myModelessForm As FormWithEvents
Public Sub Show()
Set myModelessForm = New FormWithEvents
' COMMENT OUT ONE OF THE FOLLOWING TWO LINES TO TOGGLE MODELESS OR MODAL
myModelessForm.Show vbModeless ' Modeless, but events don't get handled (no msgbox on clicking cancel button)
' myModelessForm.Show vbModal ' Events get handled, but no longer modal
End Sub
Private Sub myModelessForm_FormCancelled(Cancel As Boolean)
' Setting cancel to True will leave the form open
Cancel = MsgBox("Cancel this operation?", vbYesNo + vbExclamation) = vbNo
If Not Cancel Then
' Modeless form was cancelled and is now hidden
' ...
Set myModelessForm = Nothing
End If
End Sub
Private Sub myModelessForm_FormConfirmed()
' Form was okayed and is now hidden
Set myModelessForm = Nothing
End Sub
And here's the code in the main module:
Option Explicit
Public Sub RunForm()
With New Presenter
.Show
End With
End Sub
Any ideas on where I've gone wrong?
Maybe this instead so you can keep your Presenter instance in scope:
Dim pres as Presenter
Public Sub RunForm()
Set pres = New Presenter
pres.Show
End Sub
Avoid using RaiseEvent. Declare a public instance of the Presenter class in the standard module.
Option Explicit
Public Preter As New Presenter
Public Sub RunForm()
With New Presenter
.Show
End With
End Sub
Convert the event procedure to a sub with public scope.
Sub FormCancelled(Cancel As Boolean)
' Setting cancel to True will leave the form open
Cancel = MsgBox("Cancel this operation?", vbYesNo + vbExclamation) = vbNo
If Not Cancel Then
' Modeless form was cancelled and is now hidden
' ...
Set myModelessForm = Nothing
End If
End Sub
The call the sub within the class.
Private Function OnCancel() As Boolean
Dim cancelCancellation As Boolean
Preter.FormCancelled cancelCancellation
If Not cancelCancellation Then Me.Hide
OnCancel = cancelCancellation
End Function
I'm not sure but your code line Set myModelessForm = Nothing in the class module bother me. I wonder whether the form should be hidden instead so that the code can continue to the Show procedure where the form can then be put to rest.

How to check if a userform is closed with "X" Windows button?

There is a sub, it creates a CourtForm userform and then takes a data from it. The problem appears when said form is closed prematurely, by pressing "X" window button and I get a runtime error somewhere later. For reference, this is what I'm talking about:
In my code I tried to make a check to exit sub:
Private Sub test()
'Create an exemplar of a form
Dim CourtForm As New FormSelectCourt
CourtForm.Show
'The form is terminated at this point
'Checking if the form is terminated. The check always fails. Form exists but without any data.
If CourtForm Is Nothing Then
Exit Sub
End If
'This code executes when the form proceeds as usual, recieves
'.CourtName and .CourtType variable data and then .hide itself.
CourtName = CourtForm.CourtName
CourtType = CourtForm.CourtType
Unload CourtForm
'Rest of the code, with the form closed a runtime error occurs here
End Sub
Apparently the exemplar of the form exists, but without any data. Here's a screenshot of the watch:
How do I make a proper check for the form if it's closed prematurely?
Add the following code to your userform
Private m_Cancelled As Boolean
' Returns the cancelled value to the calling procedure
Public Property Get Cancelled() As Boolean
Cancelled = m_Cancelled
End Property
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
Code taken from here. I would really recommend to have a read there as you will find a pretty good basic explanation how to use a userform.
One of the possible solutions is to pass a dictionary to the user form, and store all entered data into it. Here is the example:
User form module code:
' Add reference to Microsoft Scripting Runtime
' Assumed the userform with 2 listbox and button
Option Explicit
Public data As New Dictionary
Private Sub UserForm_Initialize()
Me.ListBox1.List = Array("item1", "item2", "item3")
Me.ListBox2.List = Array("item1", "item2", "item3")
End Sub
Private Sub CommandButton1_Click()
data("quit") = False
data("courtName") = Me.ListBox1.Value
data("courtType") = Me.ListBox2.Value
Unload Me
End Sub
Standard module code:
Option Explicit
Sub test()
Dim data As New Dictionary
data("quit") = True
Load UserForm1
Set UserForm1.data = data
UserForm1.Show
If data("quit") Then
MsgBox "Ввод данных отменен пользователем"
Exit Sub
End If
MsgBox data("courtName")
MsgBox data("courtType")
End Sub
Note the user form in that case can be closed (i. e. unloaded) right after all data is filled in and action button is clicked by user.
Another way is to check if the user form actually loaded:
Sub test()
UserForm1.Show
If Not isUserFormLoaded("UserForm1") Then
MsgBox "Ввод данных отменен пользователем"
Exit Sub
End If
End Sub
Function isUserFormLoaded(userFormName As String) As Boolean
Dim uf As Object
For Each uf In UserForms
If LCase(uf.Name) = LCase(userFormName) Then
isUserFormLoaded = True
Exit Function
End If
Next
End Function

How to prevent sharepoint file checkin from triggering workbook before close?

My sub Workbook_BeforeClose runs twice, because in my Sub CloseWBFromSharePointFolder, I either check in my file, discard it or cancel and do nothing (see code below). Both the check in and the discarding of the file trigger Workbook_BeforeClose to run again.
Private Sub Workbook_BeforeClose(Cancel As Boolean)
CloseWBFromSharePointFolder
End Sub
Both snippets from CloseWBFromSharePointFolder which trigger Workbook_BeforeClose
Check in
ActiveWorkbook.CheckIn SaveChanges:=True, Comments:="Checked-In by " & Application.Username
Discard
Application.ActiveWorkbook.CheckIn False
Any help would be appreciated.
P.s. I also tried to use a public variable to track if it runs again. This does not work because the public variable got reset. The explanation I found is because Workbook_BeforeClose calls CloseWBFromSharePointFolder, which then triggers Workbook_BeforeClose. This resets everything and the public variable becomes empty
P.s.2 for more details.
CloseWBFromSharePointFolder Code
Sub CloseWBFromSharePointFolder()
Dim myForm1 As UserForm1
Set myForm1 = UserForm1
myForm1.Caption = "Choose before closing:"
myForm1.Show
End Sub
UserForm1 Code
Dim Buttons() As New BtnClass
Private Sub UserForm_Initialize()
Dim ButtonCount As Integer
Dim ctl As Control
' Create the Button objects
ButtonCount = 0
For Each ctl In UserForm1.Controls
If TypeName(ctl) = "CommandButton" Then
'Skip the OKButton
If ctl.Name <> "OKButton" Then
ButtonCount = ButtonCount + 1
ReDim Preserve Buttons(1 To ButtonCount)
Set Buttons(ButtonCount).ButtonGroup = ctl
End If
End If
Next ctl
Me.CommandButton1.Caption = "Check in"
Me.CommandButton2.Caption = "Discard check-out"
Me.CommandButton3.Caption = "Keep checked-out"
Me.CommandButton4.Caption = "Cancel"
End Sub
BtnClass Code
Public WithEvents ButtonGroup As MsForms.CommandButton
Private Sub ButtonGroup_Click()
If UserForm1.Visible = True Then
Select Case ButtonGroup.Name
Case "CommandButton1" 'check in
CheckIn
Case "CommandButton2" 'Discard check-out
Discard
Case "CommandButton3" 'Keep checked-out
KeepCheckedOut
Case Else ' Cancel
'Do Nothing
End Select
Unload UserForm1
ElseIf UserForm2.Visible = True Then
Select Case ButtonGroup.Name
Case "CommandButton1" 'check out
CheckOut
Case "CommandButton2" 'Read only
'Do Nothing
Case Else ' Cancel
'Do Nothing
End Select
Unload UserForm2
End If
End Sub
Sub CheckIn()
If ActiveWorkbook.CanCheckIn = True Then
'Check In, Save and Close
ActiveWorkbook.CheckIn SaveChanges:=True, Comments:="Checked-In by " & Application.Username
MsgBox ("File sucessfully checked in")
Else
MsgBox ("File could not be checked in!")
End If
End Sub

how to check if workbook is closing in deactivate event

SOLVED
Is there any way to check if workbook is closing when the code is in workbook_deactivate procedure? so i can inform a different message to users depending on whether they are just leaving to another workbook or they are closing the file. like following
Private Sub Workbook_Deactivate()
if thisworkbook.closing then
msgbox "message1"
else
msgbox "message2"
end if
End Sub
i've searched on the net but no solution at all.
so any help would be appreciated
SOLUTION
i've thought of a trick. i'm putting the value 1 in Z1000(if it is available) in before_close event and in deactivate, i'm checking if Z1000's value. that's it.
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Range("Z1000").Value = 1 'wherever is avaliable
Me.Saved = True
End Sub
Private Sub Workbook_Deactivate()
If Range("Z1000").Value = 1 Then
MsgBox "quitting"
Else
MsgBox "deactivating"
End If
End Sub
You can detect that using the BeforeClose Event
Private Sub Workbook_BeforeClose(Cancel As Boolean)
' set Cancel to true to prevent it from closing
End Sub

Determine which command button was clicked to open userform

I have two command buttons (cmd1 and cmd2) on userform1, when clicked each show the same userform (userform2). Within the initialize or load sub is it possible to determine which command button was clicked on userform1 and therefore show the form differently? I imagine the code in either the initialize or load sub on userform2 to have the following skeleton:
if (cmd1 was clicked)
' do stuff relating to 1
elseif (cmd2 was clicked)
' do stuff relating to 2
else
' error handling
End if
The respective "stuff" could be moved into the event handler for cmd1 and cmd2 however, if the method described above can be used it will be a lot simpler and cleaner.
Use a Public Variable in UserForm1 and then test it in UserForm2_Initialize Event.
Something like this in UserForm1:
Public whatsclicked As String
Private Sub CommandButton1_Click()
whatsclicked = "CommandButton1"
UserForm2.Show
End Sub
Private Sub CommandButton2_Click()
whatsclicked = "CommandButton2"
UserForm2.Show
End Sub
And then in UserForm2:
Private Sub UserForm_Initialize()
Select Case UserForm1.whatsclicked
Case "CommandButton1": MsgBox "CommandButton1 loaded form."
Case "CommandButton2": MsgBox "CommandButton2 loaded form."
Case Else 'Do something else
End Select
End Sub
you can do it also without public variable.
i won't show an example where you can simply write something in a cell or hidden sheet, instead i just pass the wanted info directly .
this time whatsclicked is the name of a label in userform2,
in userform1, before calling userform2:
Private Sub CommandButton1_Click()
load UserForm2
with UserForm2
.whatsclicked.caption= "CommandButton1"
.Show
end with
End Sub
Private Sub CommandButton2_Click()
load UserForm2
with UserForm2
.whatsclicked.caption= "CommandButton2"
.Show
end with
End Sub
Of course, you will have to hide the text of whatsclicked to the user (same color as font or background...)
You can use ActiveControl for this as when you click a control it becomes active/focussed:
Private Sub CommandButton1_Click()
Debug.Print Me.ActiveControl.Name
End Sub
Private Sub CommandButton2_Click()
Debug.Print Me.ActiveControl.Name
End Sub
So more in line with your example:
Private Sub CommandButton1_Click()
Call DoStuff
End Sub
Private Sub CommandButton2_Click()
Call DoStuff
End Sub
Private Sub DoStuff()
select case Me.ActiveControl.Name
case "CommandButton1"
' do stuff relating to 1
case "CommandButton2"
' do stuff relating to 2
case else
'error etc.
end select
End Sub