Exceptions in Visual Studio 2010 AddIn - vb.net

I have a WinForms application raising exceptions from code in an EventHandler (E.G. a Button click subscriber);
I have a try..catch block around the invocation of the ShowDialog of the Form;
The exception does not propagate to the try..catch block, but it just stops at the handler and the Form gets closed.
1) How can I propagate the exception to the block?
2) What is the best practice in these cases?
This is a new VS 2010 AddIn probject with a simple Tools menu entry added by the wizard:
Public Sub Exec(ByVal commandName As String, ByVal executeOption As vsCommandExecOption, ByRef varIn As Object, ByRef varOut As Object, ByRef handled As Boolean) Implements IDTCommandTarget.Exec
handled = False
If executeOption = vsCommandExecOption.vsCommandExecOptionDoDefault Then
If commandName = "MyAddin1.Connect.MyAddin1" Then
Dim form1 = New Form1
Try
form1.ShowDialog()
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
handled = True
Exit Sub
End If
End If
End Sub
The only content of the Form is a button with a static handler raising a "New Exception()".
When I click the button, the exception is not caught.

In Windows Forms each event handler needs its own try/catch block, or you can try to use Application.ThreadException although maybe it is not possible in an add-in. See Top-Level Exception Handling In Windows Forms Applications

Related

Events raised from a ShowDialog Form aren't raised all the way to a calling vb6 app via com interop?

We have a legacy VB6 app where we have been calling .net assemblies for a long time, including displaying WinForms from the .net assembly. But now I also need to raise events from the .net assembly, from the WinForm, back up to the VB6 app.
This works (the VB6 event fires) when the form is displayed with .Show. But when the form is displayed with .ShowDialog, the event doesn't fire in the VB6 app. And, of course, I need to show the form(s) modally, so that's why .ShowDialog is being used.
Code:
Create a .net class library, enabling com interop. This is named ClassLibrary2 when I created it on my machine.
Option Strict On
Option Explicit On
<ComClass(Class1.ClassId, Class1.InterfaceId, Class1.EventsId)>
Public Class Class1
#Region "COM GUIDs"
' These GUIDs provide the COM identity for this class
' and its COM interfaces. If you change them, existing
' clients will no longer be able to access the class.
Public Const ClassId As String = "3E245773-5A31-4B09-A26B-19D2E593395E"
Public Const InterfaceId As String = "5A184A72-AE12-4564-83FB-15EEAC8C9A13"
Public Const EventsId As String = "75C80E42-6B66-4B43-A1FA-BD62C95D117E"
#End Region
Public Sub New()
MyBase.New
End Sub
Public Event MyEvent(sParm As String)
Private WithEvents ofrm As Form1
Public Sub MySub()
RaiseEvent MyEvent("MySub Entry")
ofrm = New Form1
'ofrm.Show() ' With .Show, all events are raised to calling app
ofrm.ShowDialog() ' With .ShowDialog, events from the form are raised to this class, but then subsequently aren't raised to the calling app
RaiseEvent MyEvent("MySub Exit")
End Sub
Private Sub ofrm_MyFormEvent(sParm As String) Handles ofrm.MyFormEvent
RaiseEvent MyEvent(sParm)
End Sub
End Class
Form added to the assembly, then a button added to the form.
Option Strict On
Option Explicit On
Public Class Form1
Public Event MyFormEvent(sParm As String)
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
RaiseEvent MyFormEvent("Form Closing")
Me.Close()
End Sub
Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown
RaiseEvent MyFormEvent("Form Shown")
End Sub
End Class
VB6 executable application, with a button on the Form, also a reference added to the ClassLibrary2 assembly.
Private WithEvents ox As ClassLibrary2.Class1
Private Sub Command1_Click()
Set ox = New ClassLibrary2.Class1
Call ox.MySub
End Sub
Private Sub ox_MyEvent(ByVal sParm As String)
Debug.Print sParm
End Sub
When running all this, the events raised from MySub fire the VB6 ox_MyEvent event handler. And when Form1 is displayed with .Show, the events raised from the form fire the ofrm_MyFormEvent handler, which raises the event further, and the VB6 ox_MyEvent event handler fires:
MySub Entry
MySub Exit
Form Shown
Form Closing
But when Form1 is displayed with .ShowDialog, the events raised from the form fires the ofrm_MyFormEvent handler, but the event raised from there never fires the VB6 ox_MyEvent event handler:
MySub Entry
MySub Exit
What's happening here? Some kind of VB6 UI thread blocking? But why would the ShowDialog be doing that when the events raised from MySub can get through?
Using VS2015, Framework 4.5.2
Update
I've run another test, using a .net Winform app (exe) as the calling application, instead of the VB6 code above. Tried calling the ClassLibrary2 assembly as a .net assembly, and also as a COM object. In both cases, the events fired up to the main app as expected:
MySub Entry
Form Shown
Form Closing
MySub Exit
So it's not a COM issue (or at least not just COM), definitely involving something with VB6?
Modal dialogs have their own inner message pumps. Perhaps because that inner message pump is .NET controlled instead of VB6 controlled, there is an incompatibility? As in, the VB6 message loop is satisfying something VB6 needs that the .NET message pump is not doing?
In any case, personally I moved away from events in COM interop scenarios in favor of using an explicit callback interface. The advantage of this is there is no message pump involved at all, you bypass any issues related to that.
So instead of Public Event MyEvent(sParm As String) create an Interface call IClass1EventListener or IClass1CallbackHandler and give it a method Sub OnMyFormEvent(sParm As String)
Then in your VB6 Form use Implements IClass1EventListener.
Your code then looks more like this:
Private ox As ClassLibrary2.Class1
Private Sub Command1_Click()
Set ox = New ClassLibrary2.Class1
Call ox.MySub(Me)
End Sub
Private Sub IClass1EventListener_OnMyEvent(ByVal sParm As String)
Debug.Print sParm
End Sub
You should take care that to either have the .NET side explicitly clear the callback reference after it completes the ShowDialog or change the method signature to one that your VB6 code can manage it, like this:
Private Sub Command1_Click()
Set ox = New ClassLibrary2.Class1
Set ox.Listener = Me
Call ox.MySub()
Set ox.Listener = Nothing
End Sub

Events and exceptions in 3-layer architecture

I have another question considering events and exceptions
A small introduction to my project: I'm working in a 3 layer architecture, so I have a GUI(form), BLL(Business logic layer) and a DAL(data acces layer).
So my GUI is linked to a BLL which is connected to several DAL classes. For example my BLL has a class clsCSV (which reads and writes CSV files)
I now want to raise events and/or catch exceptions from this class to inform the user and log exceptions. There are 3 events: "ErrorLoad", "ErrorWrite", "Ready"
Public Class clsCSV
Public Sub New()
End Sub
Public Function Load(sFilePath As String)
Dim oFileHelper As New DelimitedFileEngine(Of clsItem)
Dim oList As New List(Of clsItem)
' Load a CSV file
Try
oList = oFileHelper.ReadFileAsList(sFilePath)
Catch ex As Exception
'RaiseEvent ErrorLoad("")
clsErrorLog.Log(ex)
Finally
End Try
' If the list is empty return nothing
If Not IsNothing(oList) Then
Return oList
Else
Return Nothing
End If
End Function
Public Sub Write(sFilePath As String, oList As List(Of clsTranslatedItem))
Dim oFileHelper As New DelimitedFileEngine(Of clsTranslatedItem)
oFileHelper.WriteFile(sFilePath, oList)
RaiseEvent Ready()
End Sub
Public Event ErrorLoad(ByVal sender As Object, ByVal e As EventArgs)
Public Event ErrorWrite(sMessage As String)
Public Event Ready()
End Class
There is a try-catch in my public sub load which catches any exception and logs it to a text file by calling clsErrorLog.log(ex) which then calls this class and logs the error:
Public Shared Sub Log(ex As Exception)
Try
Dim oBestand As New System.IO.StreamWriter(My.Computer.FileSystem.SpecialDirectories.Desktop & "\ErrorLog.txt", True)
If Not IsNothing(oBestand) Then
oBestand.WriteLine("")
oBestand.WriteLine(My.Computer.Clock.LocalTime.ToString)
oBestand.WriteLine(ex.Message)
oBestand.WriteLine(ex.StackTrace)
oBestand.WriteLine("_________________________________________")
oBestand.Close()
End If
Catch exc As Exception
End Try
End Sub
This works fine, but now I want to inform the user that an error occured, so I was thinking to raise an event called "ErrorLoad" (in clsCSV) and catch this in my BLL. But what can I do then? Add a sub that handles this event, which then raises an event in the BLL, caught by the GUI? or is this a long way to this?
I also want to do this for the other events offcourse. But the whole event system in the layered architecture is a bit confusing.
Anyone who can enlighten me?
Thanks in advance!
Without knowing the implementation details of your UI, I can only give you some general advice.
You need some kind of two directional communication between your GUI and your BLL. For example in WPF this is usually done using INotifyPropertyChanged interface and the event it defines.
So the BLL raises a event and the GUI is listening. The basically only has the task of informing the GUI that it has to check a specific property or function or anything like this from the BLL. The rest is up to the GUI to do so and inform the user.
I wouldn't do that using exceptions. These are there to handle the error path of specific functions. This path is done once your BLL noticed the error and prepared it for the UI handling.

MultiThreaded solution to avoid DialogBox pausing execution

I am currently automating a series of calls to a library in VB.NET consoleApplication. The functioncalls usually require a series of user selected inputs. My problem with this is that a set of these functions create a programmatically inaccessible DialogBox instance and pauses the execution of the program until they have been interacted with.
Right now I have tried to solve this problem by using multiple threads according to the code below.
Public Sub StartFormFunction(ByVal inputValue As String)
frameWork.showHiddenDialogBox(inputValue)
End Sub
Public Sub threadFunction(ByValue inputValue As String)
Dim nrOfOpenForms As Integer = Application.OpenForms.Count()
Try
Dim t As New Thread(New ParameterizedThreadStart(AddressOf StartFormFunction))
t.Priority = Threading.ThreadPriority.Highest
t.Start(inputValue)
'Wait until the prompt has been created.
While (Application.OpenForms.Count() = nrOfOpenForms) And (t.IsAlive)
End While
if Not t.IsAlive Then
log.Error("Thread did not open dialogBox")
Return
End If
'Select preffered button on dialogBox
Dim isFinished As Boolean = False
For Each curForm As Form In Application.OpenForms
For Each btn As Button In curForm.Controls.OfType(Of Button)
If btn.Name = "Button3" Then
btn.PerformClick()
isFinished = True
Exit For
End If
Next
if isFinished Then
Exit For
End If
Next
'Wait until thread completed Function
While t.IsAlive
End While
Catch ex As Exception
log.Error("Thread Error")
End Try
End Sub
I have not found a way to use Control.Invoke() in a console application yet and is because of this the reason it is not used.
The way I can get my code to be able to execute completely is to disable CheckForIllegalCrossThreadCalls which I am trying to avoid.
Is it possible to solve the problem of accessing a DialogBox without using multiple threads? If not, is the problem solvable by invoking the subcall?
EDIT
Some of my description might have been lacking in detailed information.
My problem is that my application run a method showHiddenDialogBox(), that run a set of instructions in a class that is kept out of scope from my code. This inaccessible class displays a form when all functionality have been executed. When this form is shown the application pause all execution of code until a user is promoting a input.
This makes it necessary to use multiple threads to get around. However this new thread will own this form while it is displayed an all of the content. This included in the buttons that I would need the other thread to access.
Dont use "CheckForIllegalCrossThreadCalls", just invoke a control with this code:
'Insert this in a module
<Runtime.CompilerServices.Extension()>
Public Sub InvokeCustom(ByVal Control As Control, ByVal Action As Action)
If Control.InvokeRequired Then Control.Invoke(New MethodInvoker(Sub() Action()), Nothing) Else Action.Invoke()
End Sub
The call this sub for every control in a thread
textbox1.InvokeCustom(sub() textbox1.text = "abc")

mybase.load stops loading when calling a function vb.net

I step through this code and find out that the function is not only never called but the rest of the myBase.Load never completes what is going on here.
All outside references are displayed now. Program never hits the lines surrounded in ** and does run frmMain_Load as first item. the stepthrough icon does land ON the line that starts with reader= but never calls runAsIsQuery (breakpoints don't catch and stepthrough just evaporates). then it shows me frmMain without proccessing any other code from frmMain_Load nor from runAsISQuery
Private Sub frmMain_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
sqlstring = "SELECT Nickname FROM tblBikeInfo"
reader = sql.runAsIsQuery(cnn, sqlstring) 'never fires
**If 1 = 1 Then**
'ummmm never comes back here either
End If
Extra details asked for about the other references these are in frmMain as global vars
Dim reader As OleDbDataReader
Dim sql As OLEDB_Handling 'custom class
Public cnn = MotorcyleDB.GetConnection
Function from Custom Class (OLEDB_Handling)
**Public Function runAsIsQuery(connection As OleDbConnection, SQL As String) As OleDbDataReader**
Dim reader As OleDbDataReader
Dim command As New OleDbCommand(SQL)
command.Connection = connection
Try
connection.Open()
reader = command.ExecuteReader()
Return reader
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Function
Connection string class called (MotorcyleDB)
Public Shared Function GetConnection() As OleDbConnection
Return New OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\************\Documents\Visual Studio 2010\Projects\MotorcycleMinder\MotorcycleMinder\MotorcycleServiceLog11.accdb")
End Function
I found this:
http://blog.adamjcooper.com/2011/05/why-is-my-exception-being-swallowed-in.html
This is a snippet from the site.
If these conditions are met:
You are running on a 64-bit version of Windows (whether your application is built for 32-bit or 64-bit doesn’t matter; only the bit
depth of the OS)
You are building a WinForms app
You are debugging the application with Visual Studio (using default
options for Exception catching)
Your main form has a Load event handler
During the execution of your Load handler, an exception occurs
Then:
The exception will be silently swallowed by the system and, while your
handler will not continue execution, your application will continue
running.If you wrap your handler code in a try/catch block, you can
still explicitly catch any thrown exceptions. But if you don’t, you’ll
never know anything went wrong.
Note that all of the conditions must be met. If, for instance, you run
the application without debugging, then an unhandled exception still
be correctly thrown.
There is also a workaround on the site. But I would put the code in a try catch block, or put the entire thing in the Initializer/Constructor.

VB.Net Sending errors through mail when error is detected

I'm developing an application with an error log when something goes bad. It must send an e-mail with the error details so I can remotely fix and upload a new update with the fix.
I'm using Try Catch Exception but I have a lot of methods to include this option in.
Is there another way to do it without doing so much code?
Since exceptions bubble up to the application instance try using the Application.SetUnhandledExceptionMode Method.
From above MSDN Link:
It is often not feasible to catch all of the exceptions thrown by
Windows Forms. Using this method, you can instruct your application
whether it should catch all unhandled exceptions thrown by Windows
Forms components and continue operating, or whether it should expose
them to the user and halt execution.
Public Shared Sub Main()
' Add the event handler for handling UI thread exceptions to the event.
AddHandler Application.ThreadException, AddressOf Form1_UIThreadException
' Set the unhandled exception mode to force all Windows Forms errors to go through
' our handler.
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException)
' Add the event handler for handling non-UI thread exceptions to the event.
AddHandler AppDomain.CurrentDomain.UnhandledException, AddressOf CurrentDomain_UnhandledException
' Runs the application.
Application.Run(New Form1()) '' This is your applications Main Form
End Sub
Private Shared Sub Form1_UIThreadException(ByVal sender As Object, ByVal t As ThreadExceptionEventArgs)
'Put Error Handling Code here see the MSDN article for an example implementation
End Sub
Private Shared Sub CurrentDomain_UnhandledException(ByVal sender As Object, _
ByVal e As UnhandledExceptionEventArgs)
''Put Error Handling Code here see the MSDN article for an example implementation
End Sub
Sorry, misunderstood your question. Try putting your logic in a method and just try to call that method in every try catch statement you have.
Example:
Public Shared Sub Method1()
Try
'Method logic here
Catch ex As Exception
EmailError(ex)
End Try
End Sub
Public Shared Sub EmailError(ex As Exception)
'your remote error email logic here
End Sub