How do objects get disconnected to its clients? - excel

I made the code below from Excel 2016 VBA.
Private Sub useridno_AfterUpdate()
Dim strMsg As String
Dim ret_type As Integer
Dim strTitle As String
strTitle = "Wrong User ID Number!"
strMsg = " Would you like to try again?"
If Me.useridno.Value <> 1 Then
ret_type = MsgBox(strMsg, vbYesNo + vbCritical, strTitle)
Select Case ret_type
Case 6
Me.useridno.SetFocus
Me.useridno.Text = ""
Case 7
Unload Me
End Select
End If
End Sub
When I run it, it returns the following error:
This happens when I choose the "No" button in the message box. Why is this so?

Somewhere in your project you have a Show command which displays the userform. From that moment forward the userform is in control. The code following the Show command will resume when the userform is closed. The command to do so is Me.Hide as #Storax has pointed out.
Your code has Unload Me instead. The error occurs when the other procedure tries to refer to the userform after the Show command. Your code might try to read some of the data from the form or there might be an innocuous Set UserForm = Nothing. It is already unloaded and can't, therefore, be referred to anymore.
The correct way would be to unload the form in the calling procedure, following the Show command and after you have retrieved all data from the form that you might want to use. Then, if you wish to explicitly release the form's object variable from memory you can do so.

Related

"Run-time error "-2147417848 (80010108) Automation error: The object invoked has disconnected from its clients"

I have a userform in which I check if the name entered is in the database.
I distinguish between adding contacts and editing contacts in different forms, so if in the 'adding contacts' form a name is entered that is in the database, a vbYesNo MsgBox tells the user and gives the opportunity to either change the entered name (vbNo) or to edit the existing name (vbYes).
Private Sub cmbLastName_AfterUpdate()
Dim FullName As String
FullName = cmbFirstName & " " & cmbLastName
Dim answer As Integer
If Application.WorksheetFunction.CountIf(Range("List_Full_Name"), FullName) Then
answer = MsgBox(FullName & " already included in contact list. Edit contact?", vbYesNo, "Error").
If answer = vbYes Then
Unload AddContact
EditContact.Show
Sheets("Engine").Range("C4").Value = "EDIT"
Else
End If
Else
End If
End Sub
The EditContact.Show appears to work, yet when I close the EditContact userform, the program crashes and shows
Run-time error "-2147417848 (80010108) Automation error: The object invoked has disconnected from its clients
When I press debug AddContact.Show is highlighted:
Sub ShowAddContact()
Sheets("Engine").Range("C4").Value = "NEW"
AddContact.Show
End Sub
This piece of code opens the AddContact userform and is invoked when the button 'add contact' is clicked in the worksheet. The Engine worksheet refers to a cell that is changed, dependent on whether an entry should be added to the database or an existing entry should be changed.
To check what is the real cause of your problem you should go into VBA editor -> Tools -> Options -> General and then change error traping option to "Break in Class Module". Call the error again and this time Debug option show you exact line that cause the error.

How I make excel show a await message while execute a macro?

I have a macro that consulte to Database (in this case is a excel workbook), but when the macro consulte to database, it take 30 sec, and the user thinks that the program is broke. I tried with "Application.StatusBar = "Refresh File. "", but the user can´t see the sentence, the best way is a msgbox with the message "Await a moment", and, when the macro is finished, this message is closed. Do you help me with the code? I think the solution is this way:
Sub Button1_Click()
'Call msgbox with message "Await a moment"
Dim x As Long
For x = 0 To 2000000000 Step 1
Next
'The msgbox is closed
End Sub
I think the solution is a msgbox but it is a another option, Go Ahead!
A Msgbox wont work, because it would stop execution of everything until the user clicks OK.
The statusbar would work, if you put a DoEvents right after you set it so the system has a moment to update the screen.
Your only other option would be to create a special form that you show while it's processing, but you would also need a DoEvents after you show it as well.
Sub Test()
' create a userform and name it frmWait
' put whatever you want on that form
' that is what will be displayed while the user waits
frmWait.Show vbModeless
DoEvents
' do your processing here
Unload frmWait
End Sub
Or:
Sub Test()
Application.StatusBar = "Refreshing File. "
DoEvents
' do your processing here
' reset the status bar to normal
Application.StatusBar = ""
End Sub

Excel Userform Run-Time Error when trying to launch a 2nd time after initial 'Cancel' or close on red 'X'

Problem: I am building a Userform that has a 'Submit' and 'Cancel' button. I want the entire form to clear any entered data and close the form if the user hits the 'Cancel' button but I also am trying to build in the same functionality if the user hits the red 'X' in the top right corner. I'm unclear where I need to unload the form. I currently have it placed within the btnCancel_Click() method and I'm able to launch the form, enter some data and hit Cancel and it will close the form down.
But when I try to re-launch the form a 2nd time I get an error (I attached a picture of that message) that says
"Run-Time error '-2177418105 (80010007): Automation Error - The Callee (server [not server application]) is not available and disappeared; all connections are invalid. The call may have executed.
If I remove Unload Me from btnCancel_Click() then the form can close and re-open just fine, but any data I entered the first time will still be on the form and isn't cleared properly. I'm wondering if this is an Unload Me error or do I need to reset all form controls when I initialize the form?
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
' how was the form closed?
' vbFormControlMenu = X in corner of title bar
If CloseMode = vbFormControlMenu Then
' cancel normal X button behavior
Cancel = True
' run code for click of Cancel button
btnCancel_Click
End If
End Sub
'******************************************************************
Private Sub btnCancel_Click()
mbCancel = True
Me.Hide
Unload Me
End Sub
'*********************************************************************
Private Sub UserForm_Initialize()
'Populate values for 2 combo boxes
lastEmp = Sheets("Form_Ref").Cells(Rows.Count, 1).End(xlUp).Row
Me.cmbBoxEmpName.List = Sheets("Form_Ref").Range("A2:A" & lastEmp).Value
lastBld = Sheets("Form_Ref").Cells(Rows.Count, 2).End(xlUp).Row
Me.cmbBoxBuildingName.List = Sheets("Form_Ref").Range("B2:B" & lastBld).Value
End Sub
'******************************************************************
Public form As New CheckOutForm
Sub testFormOptions()
'Button pressed within Excel will start program and show the userform
form.Show
End Sub
This is the easiest quick and dirty solution:
Delete Public form As New CheckOutForm from the code. Then add it in the testFormOptions():
Sub testFormOptions()
Dim form As New CheckOutForm
form.Show
End Sub
Some not-that-good VBA books/tutorials would even go a bit like this, but this is brutal:
Sub testFormOptions()
CheckOutForm.Show
End Sub
Anyway, now the problem with the predefined values in the form is solved.
For the clean and not-so-easy solution, consider writing a MVC framework around the form:
https://codereview.stackexchange.com/questions/154401/handling-dialog-closure-in-a-vba-user-form
this blogpost (disclaimer - mine!), which pretty much says what the above link proposes, but it does not have the errors from the question.
the old StackOverflow tutorial for UserForms
If you execute Unload, you destroy the form object. With other words, your (global) variable form gets invalid and if you issue a new form.show, you get the runtime error.
When, on the other hand, you just unhide the form, the form-object stays valid (it's just currently not visible) and all controls keep their value.
Either you do some housekeeping by resetting all controls when a form is displayed (use the UserForm_Activate-event), or you have to create a new form-object every time you want to display it (I would strongly advice not to use the name form as variable name to avoid confusion).
Sub testFormOptions()
dim myForm as CheckOutForm
myForm.Show
End Sub

Obtaining a single variable from a userform in Visual Basic

I have little VB knowledge but was hoping to figure this out without any help, but alas!
I have a spread sheet that shows the layout of HP blades inside a chassis. Currently I have set-up a macro (I right-clicked view code on the sheet) whereby you click on a specific 'blade' (really a few merged cells) and an inputbox appears, asking the user which IP they'd like - this then looks for exact matches (e.g. O - OAM, I - iLO) and depending on the option selected - assigns a value in a case statement. For example: O - OAM IP would assign the number 2 - which performs a VLOOKUP and looks in column 2 to return an IP address to the users clipboard.
I'm trying to change the inputbox to a userform. I've created one and have a simple dropdown with the available options.
I'm struggling to export the users selection into a variable to be used later on.
All of my efforts so far result in a variable being assigned following completion of the 'userform' and pressing the 'OK' button. But that variable is then not assigned in the sheet's code area. I assume it's something to do with scope and assignment.
Code for the worksheet
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Not Intersect(Target, Range("D57:H80")) Is Nothing Then
Dim HostName As String
Dim ColIndex As Integer
Dim NodeIP As String
HostName = ActiveCell.Value
ufrmIPSelection.Show
'uOption = InputBox("Option Required" & vbCrLf & "o = OAM IP" & vbCrLf & "i = iLO IP", "User selection required")
'uOption
MsgBox "Option captured is " & uOption
Select Case uOption
Case "OAM IP"
ColIndex = 2
Case "iLO IP"
ColIndex = 3
End Select
MsgBox "Column referenced is " & ColIndex
NodeIP = Application.VLookup(HostName, Sheet7.Range("A1:C503"), ColIndex, False)
MsgBox "Hostname is " & HostName
MsgBox "Node IP is " & NodeIP
End If
End Sub
and code for the userform OK button
Public Sub cmdOK_Click()
uOption = cboIP.Value
MsgBox "Combo Box Value " & uOption
'Above line used to see if variable has been assigned
Unload Me
End Sub
Thanks,
Unload Me essentially says "destroy this object". But a form's default instance isn't just any other object - it's a global instance that's automatically re-created by VBA whenever it's referenced again, if it's ever destroyed. But the re-created instance can't magically remember what the previous state was: the re-created instance therefore has whatever the inital / design-time state was.
Another thing that destroys a form and its state, is the little [X] button in the top-right corner.
A modal form is essentially a dialog - and a dialog can either be accepted, or cancelled. When the user dismisses your dialog by clicking that little [X] button, they're telling your program that they wish to cancel whatever that dialog was intended to be doing.
For this reason, it's important that a dialog does nothing other than collect data from the user. The basic code-behind boilerplate for a UserForm with an Ok and a Cancel button, might look like this:
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
Handling the QueryClose event allows us to cancel the destruction of the form and the resetting of its state when the user clicks that [X] button.
Now the calling code can do this:
ufrmIPSelection.Show
If ufrmIPSelection.IsCancelled Then Exit Sub
But since that's storing the state in the form's global/default instance, it's a much better idea to do this:
With New ufrmIPSelection
.Show
If .IsCancelled Then Exit Sub
End With
That way every usage of the form is nicely insulated, and the form instance and its state only live as long as it needs to (i.e. it's gone at End With).
You could go ahead and query the individual controls' state from the calling code, but a much cleaner approach would be to encapsulate that state, using properties (a bit like suggested in 3.1415's answer); these properties only need to be read from the calling code, so only a Propery Get member is needed:
Public Property Get SelectedIPType() As String
SelectedIPType = cboIP.Value
End Property
And now the calling code can do this:
With New ufrmIPSelection
.Show
If .IsCancelled Then Exit Sub
MsgBox .SelectedIPType
End With
The next step would be to extract an actual stateful class to encapsulate the model, so as to completely decouple the form from the data.
Read more about forms' default instance in UserForm1.Show, an article I wrote a few months ago (the basic code-behind boilerplate snippet is taken from there).
You need to add a public property to your form ...
Private m_OptionChoice as String
Property Let OptionChoice(value As String)
m_OptionChoice = value
End Property
Property Get OptionChoice() As String
OptionChoice = m_OptionChoice
End Property
And then change your OK button code to ...
Public Sub cmdOK_Click()
OptionChoice = cboIP.Value
MsgBox "Combo Box Value " & uOption
'Above line used to see if variable has been assigned
Unload Me
End Sub
Now when your form closes, you can access the OptionChoice via ufrmIPSelection.OptionChoice. Eg change ...
MsgBox "Option captured is " & uOption
to ...
MsgBox "Option captured is " & ufrmIPSelection.OptionChoice
Finally, as suggested in Mathieu Guindon's comment below, you need to replace Unload Me with Me.Hide. Thanks Mathieu!

how to load the combobox and then how to use the selected value from it in vba?

a question is : how to load a or give value to a combo box list and then call it in my worksheet and get the selected value from it ?i have a module that i want to call my userform1 which include the combobox in it . but when i debug the program it is just a show of the combo box . i think it doesn't do anything ... thanks for your time ..this is the code for user form:
Private Sub UserForm_Initialize()
With ComboBox1
.AddItem "weibull"
.AddItem "log-normal"
.AddItem "gambel"
.Style = fmStyleDropDownList
End With
End Sub
and this is how i ask in my sub to show the combobox:
UserForm1.Show
If ComboBox1.ListIndex = -1 Then
MsgBox "There is no item currently selected.", _
vbInformation, _
"Combo Box Demo"
Exit Sub
End If
MsgBox "You have selected " & ComboBox1.List(ComboBox1.ListIndex) & "." & vbNewLine _
& "It has " & ComboBox1.ItemData(ComboBox1.ListIndex) ", _
vbInformation, _
"Combo Box Demo"
the second part is what i found in net , but it made the program at least to show the combo box !
I thought Siddharth's answer was pretty clear particularly as it was posted from a mobile! However, I had an email from the OP saying he did not understand the answer. I provided the following background which was apparently sufficient to allow him to understand Siddharth's answer and solve his problem. I post it here for the benefit of any other visitor who needs more background on forms than Siddharth provides.
If you select VBA Help and type “userform show” you will get a description of the Show command.
If your user form is named “UserForm1”, you can have statements:
UserForm1.Show
UserForm1.Show vbModal
UserForm1.Show vbModeless
Statements 1 and 2 are equivalent.
The choice of VbModal or vbModeless completely changes the way the user form is controlled.
If a form is shown modeless, the user can see it but cannot access it. If I have a macro that takes a long time, I will use a modeless form to show progress. If I am working down the rows of a large worksheet I might have a form containing:
I am working on row nnnn of mmmm
Each of the boxes is a label. I set the value of the label containing “mmmm” to the number of rows when I start the macro. I set the value of the label containing “nnnn” to the row number at the start of each repeat of my loop. The user sees:
I am working on row 1 of 5123
then I am working on row 2 of 5123
then I am working on row 3 of 5123
and so on.
If it takes the macro five minutes to process every row, this tells the user that something is happening. Without the form, the user might think the macro had failed. With the form, the user knows the macro is busy and they have time to get a fresh cup of coffee.
On the other hand, if the form is shown modal, the macro stops until the user does something that closes or unloads the form with a statement such as:
Unload Me
The positioning of this statement depends on your form. I normally have a Submit button that saves information before ending with this statement.
Once the Unload Me statement is executed, the macro restarts at the statement after the Show statement. When the macro restarts, the form has gone. This is why the form must save anything the macro needs in global variables.
You are trying to access a control when the userform is already closed. And I say closed becuase you are not using vbmodeless to show the form. So the only way the next line after that can run is when the form is closed. Here is what I recommend.
Declare public variables in a module which will hold the relevant values when the useform closes and then use that later. For example
Paste this code in the userform
Option Explicit
Private Sub UserForm_Initialize()
With ComboBox1
.AddItem "weibull"
.AddItem "log-normal"
.AddItem "gambel"
.Style = fmStyleDropDownList
End With
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If ComboBox1.ListIndex <> -1 Then
SelectItem = ComboBox1.List(ComboBox1.ListIndex)
pos = ComboBox1.ListIndex + 1
End If
End Sub
And paste this in a module
Option Explicit
Public SelectItem As String, pos As Long
Sub Sample()
'
'~~> Rest of your code
'
SelectItem = "": pos = 0
UserForm1.Show
If pos = 0 Then
MsgBox "There is no item currently selected.", _
vbInformation, "Combo Box Demo"
Exit Sub
End If
MsgBox "You have selected " & SelectItem & "." & vbNewLine & _
"It is at position " & pos, vbInformation, "Combo Box Demo"
'
'~~> Rest of your code
'
End Sub
Also
There is no .Itemdata property of the Combobox. It is available in VB6 but not in VBA. With .Itemdata property of the Combobox, I guess you were trying to get the position?

Resources