I had a Run-time error
'-2147418105 (800100007)': Automation error The object invoked has disconnected from its clients.
which is raised once in a while. I can't relate it to a specific context for this error. The only clue I have is that before using ADO code I never had that error. The implemented pattern was used many times.
I use Excel 2016 32 bits on windows 7 with a vba code.
Private mForm As frmCfgPrjctTm
Public Sub U_CfgPrjctTm_OnOpen()
If (mForm Is Nothing) Then
Call U_UnlockTeam
Set mForm = New frmCfgPrjctTm
End If
'>>>>>> the error occurs after this comment
mForm.Show vbModeless
End Sub
The code to "close" the form is as following
Public Sub U_CfgPrjctTm_OnClose()
If (Not mForm Is Nothing) Then
mForm.Hide
Dim tmp As frmCfgPrjctTm
Set tmp = mForm
Set mForm = Nothing
Unload tmp
End If
End Sub
and in the form code (childCfgPrjctTmSettings and childCfgPrjctTmSettings are defined in an enum to falg a user action before closing the form)
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
Call U_UnlockTeam
Select Case CloseMode
Case vbAppWindows, vbAppTaskManager
Call U_CfgPrjctTm_OnClose
Case vbFormControlMenu, vbFormCode
Call Save
Select Case mbOpenForm
Case childCfgPrjctTmSettings
' this opens another form
Call U_Sttngs_OnOpen(delUsr)
Case childCfgPrjctTmUsrId
' this opens another form
Call U_UsrLggd_OnOpen(dpyUsrLggdCfgPrjctTeam)
End Select
End Select
Cancel = False
End Sub
and in the form code
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
Call U_UnlockTeam
Select Case CloseMode
Case vbAppWindows, vbAppTaskManager
Call U_CfgPrjctTm_OnClose
Case vbFormControlMenu, vbFormCode
Call Save
Select Case mbOpenForm
Case childCfgPrjctTmSettings
Call U_Sttngs_OnOpen(delUsr)
Case childCfgPrjctTmUsrId
Call U_UsrLggd_OnOpen(dpyUsrLggdCfgPrjctTeam)
End Select
End Select
Cancel = False
End Sub
This error is raised after creating a form and at the very moment to show it up.
The call U_UnlockTeam involves some ADO code called inside to retreive data from a data base. The form has no Activate event handler.
Did some one have the same issue and how did you cope with ?
I am able to reproduce the error. The issue is that you unload the form inside the form. Take just an empty userform and the following code in a module. Run the code and close the form by clicking on X. There should be no code behind the form! If you run the code a second time you will get the error mentioned.
Option Explicit
Private mForm As UserForm1
Public Sub U_CfgPrjctTm_OnOpen()
If mForm Is Nothing Then
'Call U_UnlockTeam
Set mForm = New UserForm1
End If
'>>>>>> the error occurs after this comment
mForm.Show vbModeless
End Sub
Reason of the behavior is that the class destroyed itself and mForm is a module wide variable which does not know it was destroyed when calling the code the second time.
Solution would be to avoid a self-destroing class/userform or as a workaround make mForm a local variable.
Here is a better explanation
https://excelmacromastery.com/vba-user-forms-1/#Cancelling_the_UserForm
Related
I have a form which have referenced to previous form variable in the following code. I have change the scope to Public for the variable WorkflowName, however, error still happened.
Form 1:
Option Compare Database
Option Explicit
Public WorkflowName As String
Private Sub btn_CreditControlEmail_Click()
WorkflowName = "Email to Credit Control"
DoCmd.OpenForm "WorkFlow Email Preview"
End Sub
Form 2:
Option Compare Database
Dim frmPrevious As Form
Dim WorkflowName As String
Private Sub btn_ToOutlook_Click()
'update workflow status to Renewal Table
MsgBox frmPrevious![WorkflowName]
End Sub
Private Sub Form_Load()
Set frmPrevious = Screen.ActiveForm
End Sub
However, when run to second form
MsgBox frmPrevious![WorkflowName]
The following error happened.
In watching the value of frmPrevious in second form, I can see the "WorkflowName" value of frmPrevious.
If you want to refere to a variable in another form you can't use frmPrevious![WorkflowName], because that refers to a field in the first form that does not exist.
But use this: a dot and without brackets:
MsgBox frmPrevious.WorkflowName
Edit:
Another way to do this, and get the Intelisense to work, is by using the Form_ syntax.
Lets say Form 1 is named "WorkflowStart"
MsgBox Form_WorkflowStart.WorkflowName
I've seen a few other posts about this but I seem to be confused since I've seen it done several ways and haven't gotten it correct any of the ways, so I thought I would ask specifically for my case so I can learn what I am doing wrong. After learning and switching to C# for most of my programming, VB.net seems so clunky in its syntax with a lot of things, especially lambda and "on-the-fly" functions.
I have a long running task for a football game I am working on where it generates players. Its an async method utilizing the Task.Factory.StartNew method.
Here is the pertinent code:
Private Sub CreateDraft_Click(sender As Object, e As RoutedEventArgs) Handles CreateDraft.Click
TimeIt(Sub() ReallyGenNewPlayers())
End Sub
'Uses a stopwatch to time how long it takes and sends to console.
Private Sub TimeIt(MyAction As Action)
Dim SW As New Stopwatch
SW.Start()
MyAction()
Console.WriteLine($"Total Time Generating Players: {SW.Elapsed} seconds")
SW.Stop()
End Sub
Private Async Sub GenNewPlayersASync()
Dim myvalue = 0
'Generate the Players on an Async Thread
Dim x As Integer
For i As Integer = 1 To NumPlayers
x = i 'avoids issue with using the iteration variable inside the delegate
CollegePlayers.GenDraftPlayers(i, MyDraft, DraftDT, DraftClass, PosCount)
'Prog_ValueChanged(DBNull.Value, New RoutedPropertyChangedEventArgs(Of Double))
'Calls a delegate to update the progress bar to avoid having the variable locked by the background thread
Dispatcher.Invoke(Sub()
worker.ReportProgress((x / NumPlayers) * 100)
End Sub)
Next i
End Sub
'Creates a task to run the player generation code and wait til its finished
Private Sub ReallyGenNewPlayers()
Dim mytask = Task.Factory.StartNew(Sub() GenNewPlayersASync())
Task.WaitAll(mytask)
End Sub
So here is what I would like to do:
I have a progressbar I created in XAML that has a Progress_Changed Event. This is what I have for it so far based on another post, but the issue is when I have to call the function inside GenNewPlayersAsync() where it wants a RoutedPropertyChangeEventArgs as a double which I'm not exactly sure what to do...I tried creating one using .New(old value, new value) but that didn't work either and threw an error.
Public Async Sub Prog_ValueChanged(sender As Object, e As RoutedPropertyChangedEventArgs(Of Double))
Dim progress = New Progress(Of Integer)(Function(percent)
Prog.Value = e.NewValue
Return Prog.Value
End Function)
Await Task.Run(Sub() DoProcessing(progress))
End Sub
Public Sub DoProcessing(progress As IProgress(Of Integer))
Dim i As Integer = 0
While i <> 100
Thread.Sleep(100)
' CPU-bound work
If progress IsNot Nothing Then
progress.Report(i)
End If
End While
End Sub
I would like for the progressbar to be bound to an INotifyChanged Property and update itself automatically when the value gets changed. None of it seems to be working. The UI is still unresponsive and when I set different parts to ASync, I start getting Exceptions where it appears certain parts of the generation algorithm aren't working as they are returning null...very confused with all of this, and I am sure the answer is probably pretty simple...
If you guys could give several examples of different ways to get this to work so I can learn different methods and maybe state the pros and cons of the method I would greatly appreciate it...
Branching off a previous question... Retrieving only PRINT command from SQL Server procedure in VB.NET
I was able to dump all the text from SQL Server's messages and use what I need but for some reason I am unable to update my textbox with the events.
Private Shared Sub OnInfoMessage(ByVal sender As Object, ByVal e As System.Data.SqlClient.SqlInfoMessageEventArgs)
Dim Counter As Integer
Counter = 1
For Each line In e.Message.Split(vbNewLine)
If (line.Contains("====")) Then
RestoreTool.txtTRNStatus.Text = "TRN #" & Counter & "Restored"
Using LogFile As IO.StreamWriter = New IO.StreamWriter("C:\SDBT\worklog.ft", True)
LogFile.WriteLine(line)
End Using
Counter += 1
End If
Next
Everything runs smoothly but the textbox (txtTRNStatus) does not update with anything and remains blank (Where it should be showing "TRN #1 Restored")
I am utilizing a Background Worker as well to call the procedure that actually performs the restoration of a database. That procedure is what contains the event handler for the SQLInfoMessages.
Dim SQL As SqlCommand
Dim DBConn As New SqlConnection(ConnectionString)
SQL = New SqlCommand(DBScript, DBConn)
AddHandler DBConn.InfoMessage, New SqlInfoMessageEventHandler(AddressOf OnInfoMessage)
Dim SQLResult As IAsyncResult = SQL.BeginExecuteNonQuery()
Try
Catch e As Exception
MessageBox.Show(e.Message)
End Try
SQL.EndExecuteNonQuery(SQLResult)
CompletedTask = True
DBConn.Close()
I have a feeling the only way I can get the text to update is through the addition of something in the DoWork() portion of the Backgroundworker but cannot figure out what exactly. Can the OnInfoMessages be called at will when an update is needed or is it only specific to where the Event Handler is?
As you can see in the image, It's during the actual "restoration" that the tool would normally report which transaction log has been restored based off of the SQL Message received but it remains blank...
Thank you once again!
EDIT: My progress update for my background worker is set up correctly, I just am having trouble calling events from the OnInfoMessage Sub as a progress update which is where gets the next line from SQL.
Make sure you use the backgroundworker properly. In the "doWork" there must not have any line of code that affect the UI(UserInterface) as this thread isn't the main thread of the application. If it does, it will either crash or do nothing.
So if you need to change the UI from the background worker you will need to tell the main thread to do so from the "DoWork" method. This can be achieved by hooking a callback to event "ProgressChanged" of your backgroundWorker object.
' This event handler updates the UI. '
Private Sub backgroundWorker1_ProgressChanged( _
ByVal sender As Object, ByVal e As ProgressChangedEventArgs) _
Handles backgroundWorker1.ProgressChanged
Me.txtTRNStatus.Text = e.UserState.ToString()//Update UI
End Sub
From the "DoWork" method, raise this event like shown below to tell main thread to update UI. Use the second parameter to pass whatever is needed by main thread to do so.
Private Sub backgroundWorker1_DoWork(sender As Object, e As DoWorkEventArgs)
' This method will run on a thread other than the UI thread. '
' Be sure not to manipulate any Windows Forms controls created'
' on the UI thread from this method.'
Dim SomeObject As [Object]
//some stuff
backgroundWorker.ReportProgress(1, SomeObject)
//some stuff
backgroundWorker.ReportProgress(2, SomeObject)
End Sub
See MSDN for more detail on backgroundworker usage.
Or you could just use "invoke" like #Mark suggested in the comment. This tell main thread execute a sub. As long as the UI update is done by the main thread you will be fine.
I have several commandbuttons in different sheet. Now I want the user to be able to run all at once if wanted. Therefore I want to use a new CommandButton that runs all CommandButtons in all Worksheets. However I allways get a error message..I have to approaches and have not worked out for me:
running the subs:
Private Sub CommandButtonX_Click()
run CommandButtonY_Click
run CommandButtonZ_Click
etc.
end sub
..Nothing happens
My other Approach is to select all worksheets and "activate the commmand button", which is always called CommandButton1_Click however I still donĀ“t know how to do it
This was my approach:
Sheets(Array("AA", "BB", "CC",etc.).Select
Sheets("AA").ActivateCall
Call CommandButton1_Click
End Sub
I am new at this, so I would really apreciate your Help!
Thanks!
Jens
This is purely a design opinion, but I would try and tackle this problem a different way.
My preference would be to have my subroutines in a separate module and simply call them from a button. That is, the _Click event simply looks something like this:
Private Sub Command1_Click()
Call DoSomeStuff
End Sub
While in a separate module all of my subs are grouped accordingly:
Sub DoSomeStuff()
Sheet1.Range("A1").Value = "Hooray for VBA"
End Sub
Using this method you avoid having to work with VBA's sometimes wonky (In my opinion) event handler and you can re-use subs with the appropriate arguments if they're called by several different buttons.
Then your "Master Button" just calls all the subs:
Private Sub Command1_Click()
Call DoSomeStuff
Call DoSomeMoreStuff
Call DoTheFinalStuff
End Sub
What error message do you get?
What version of Excel are you running?
This code runs fine in Excel 2007 on Windows 7:
Sub Button1_Click()
MsgBox ("Hey there, I'm button 1")
End Sub
Sub Button2_Click()
MsgBox ("Hey there, I'm button 2")
End Sub
Sub Button3_Click()
MsgBox ("Hey there, I'm button 3")
End Sub
Sub Button4_Click()
Button1_Click
Button2_Click
Button3_Click
End Sub
I have 3 buttons, each on a different worksheet, and they all display a message that identifies them. The 4th button is also on a separate worksheet and it displays the other 3 messages one after the other as would be expected.
Can you attach the error message to your post?
I've got the following situation,
Private Sub MyButton_Click(sender as Object, args as EventArgs) Handles MyButton.Click
Me.pleaseWaitFrm = New PleaseWaitForm()
' Fire up new thread to do some work in (AddressOf DoMyWork)
Me.pleaseWaitFrm.ShowDialog()
End Sub
Private Sub DoMyWork()
Dim log = Me.DoTheActualWork()
Me.pleaseWaitFrm.Close()
Using logFrm as New LogViewer(log)
logFrm.ShowDialog()
End Using
End Sub
If the DoTheActualWork() call exits fast enough, the Me.pleaseWaitFrm.Close() call is happening during the Me.pleaseWaitFrm.ShowDialog() call. The result, no surprise, is this exception:
An unhandled exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll
Additional information: Value Close() cannot be called while doing CreateHandle().
Obviously this is the old "you cannot .Close() a WinForm while it's in the process of loading" problem. But what isn't obvious to me is how best to prevent that from happening in this case? How does one safely and reliably delay the .Close() until it is safe to do so in this situation?
Me.pleaseWaitFrm.Close()
That's an illegal call, you are not allowed to close a form from another thread. Not sure how you got away with it, that should raise an IllegalOperationException when you run with a debugger. Review your code and delete any assignment to the Control.CheckForIllegalCrossThreadCalls property.
This exception you're getting is a clear side-effect of this. You must use Control.Invoke() to get the dialog closed. This automatically solves your problem, the delegate target cannot execute until the dialog is loaded.
Leverage the BackgroundWorker class, it makes this easy. You can close the dialog in a RunWorkerCompleted event handler.
Why not set a "ShouldClose" flag which can be checked when it's safe to close the form - and close it if required?
With regards to a code example, I'd implement it slightly differently but let me know if this breaks any other requirements and we can modify it...
''in PleaseWaitForm:
Public Property ShouldClose as boolean = false
Private Sub frmSplash_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Me.Close()
End Sub
''Your posted code
Private Sub MyButton_Click(sender as Object, args as EventArgs) Handles MyButton.Click
Me.pleaseWaitFrm = New PleaseWaitForm()
'' Fire up new thread to do some work in (AddressOf DoMyWork)
Me.pleaseWaitFrm.ShowDialog()
End Sub
Private Sub DoMyWork()
Dim log = Me.DoTheActualWork()
Me.pleaseWaitFrm.ShouldClose = True
If Me.pleaseWaitFrm.Created Then
Me.pleaseWaitFrm.Created.Close
End If
Using logFrm as New LogViewer(log)
logFrm.ShowDialog()
End Using
End Sub
In short, if we Can close the form, we do - otherwise, set a flag and the form will do it when it finishes loading.
I tested and didn't get any issues calling Me.Close inside the frmSplash.Load() but if you do encounter any problems, you can make it cast-iron by having a worked on frmSplash which checks the value rather than use the Load event
Edit: Spotted and fixed a bug