VBobCat
Well-known member
Dear friends, I need your help with solving this puzzle.
I wanted to wrap Word and Outlook interop code in classes that work regardless of Office Version on target user machines.
For that reason, I chose late binding instead of adding reference to a specific PIA.
Much with help of examples found on web, I wrote two classes, AutoWord and AutoOutlook, and their code follows below. Both execute well, that is, both run their commands against both Word and Outlook, so I can open documents and send e-mails, ok.
But I also wanted to listen to some events raised by these external applications. So I also included implementation for Event Interfaces of both applications, from their GUID, and their events DispId. Once again, I relied a lot on samples found in the web.
What puzzles me, however, is that only AutoWord worked fine, and I can listen to MS-Word events. AutoOutlook, in the other hand, fails on executing the IConnectionPoint.Advise command, which would link it to the Event Sink.
If I comment and neutralize that single line, the class starts to work, but event-deaf.
Can someone point what is going wrong here?
Thank you very much!
Class AutoWord
Class AutoOutlook
I wanted to wrap Word and Outlook interop code in classes that work regardless of Office Version on target user machines.
For that reason, I chose late binding instead of adding reference to a specific PIA.
Much with help of examples found on web, I wrote two classes, AutoWord and AutoOutlook, and their code follows below. Both execute well, that is, both run their commands against both Word and Outlook, so I can open documents and send e-mails, ok.
But I also wanted to listen to some events raised by these external applications. So I also included implementation for Event Interfaces of both applications, from their GUID, and their events DispId. Once again, I relied a lot on samples found in the web.
What puzzles me, however, is that only AutoWord worked fine, and I can listen to MS-Word events. AutoOutlook, in the other hand, fails on executing the IConnectionPoint.Advise command, which would link it to the Event Sink.
If I comment and neutralize that single line, the class starts to work, but event-deaf.
Can someone point what is going wrong here?
Thank you very much!
Class AutoWord
Imports System.Reflection Imports System.Runtime.CompilerServices Imports System.Runtime.InteropServices <ClassInterface(ClassInterfaceType.None)> _ Public Class AutoWord Implements IApplicationEvents2 Implements IDisposable Private wordApplication As Object Private mConnectionPoint As ComTypes.IConnectionPoint Private mSinkCookie As Integer Public Sub New() Renew() End Sub Private Sub Renew() wordApplication = CreateObject("Word.Application") DirectCast(wordApplication, ComTypes.IConnectionPointContainer).FindConnectionPoint( _ GetType(IApplicationEvents2).GUID, mConnectionPoint) mConnectionPoint.Advise(Me, mSinkCookie) wordApplication.Visible = True End Sub Public Sub Close() overrideDocumentBeforeClose = True wordApplication.Documents.Close(0) wordApplication.Quit(0) wordApplication = Nothing End Sub Public ReadOnly Property Active As Boolean Get Return wordApplication IsNot Nothing End Get End Property Public ReadOnly Property Documents() As String() Get Dim DD As New List(Of String) Dim DC As Integer = wordApplication.Documents.Count() Dim DI As Object If DC > 0 Then For i = 1 To DC DI = wordApplication.Documents.Item(i) DD.Add(DI.Name) Next End If Return DD.ToArray End Get End Property Public Function AddDocument(ByVal full_name As String) As String Dim DI As Object = Nothing Try DI = wordApplication.Documents.Add(full_name) Catch ex As Exception End Try If DI IsNot Nothing Then Return DI.Name Return Nothing End Function Public Function OpenDocument(ByVal full_name As String) As String Dim _objDocument As Object = Nothing Dim _fileName As Object = full_name Dim _confirmConversions As Object = False Dim _readOnly As Object = False Dim _addToRecentFiles As Object = False Dim _revert As Object = False Dim _format As Object = 0 Dim _encoding As Object = 50001 Dim _visible As Object = True Dim _openAndRepair As Object = True Dim _documentDirection As Object = 0 Dim _nNoEncodingDialog As Object = True Try _objDocument = wordApplication.Documents.Open(_fileName, _confirmConversions, _readOnly, _addToRecentFiles, , , _revert, , , _format, _encoding, _visible, _openAndRepair, _documentDirection, _nNoEncodingDialog) Catch ex As Exception Stop End Try If _objDocument IsNot Nothing Then Return _objDocument.Name Return Nothing End Function Public Sub SaveDocument(ByVal item_name As String) Dim DI As Object = Nothing Try DI = wordApplication.Documents.Item(item_name) Catch ex As Exception End Try If DI IsNot Nothing Then DI.Save() End Sub Public Function SaveDocumentAs(ByVal item_name As String, ByVal new_full_name As String) As String Dim DI As Object = Nothing Try DI = wordApplication.Documents.Item(item_name) Catch ex As Exception End Try If DI IsNot Nothing Then DI.SaveAs(new_full_name) Return DI.Name End If Return Nothing End Function Public Sub DocumentFindReplace(ByVal item_name As String, ByVal dfr As Dictionary(Of String, String)) Dim SL = New List(Of Object), FI As Object Dim DI As Object = Nothing, SC As Integer Try DI = wordApplication.Documents.Item(item_name) Catch ex As Exception End Try If DI IsNot Nothing Then SC = DI.StoryRanges.Count If SC > 0 Then For i = 1 To SC SL.Add(DI.StoryRanges.Item(i)) Next End If End If Dim _findText As Object Dim _matchCase As Object = False Dim _matchWholeWord As Object = False Dim _matchWildcards As Object = False Dim _matchSoundsLike As Object = False Dim _matchAllWordForms As Object = False Dim _forward As Object = True Dim _wrap As Object = 1 Dim _format As Object = False Dim _replaceWith As Object Dim _replace As Object = 2 Dim _matchKashida As Object = False Dim _matchDiacritics As Object = False Dim _matchAlefHamza As Object = False Dim _matchControl As Object = False For Each SR In SL FI = SR.Find For Each KVP In dfr _findText = KVP.Key _replaceWith = KVP.Value FI.Execute( _findText, _matchCase, _matchWholeWord, _matchWildcards, _matchSoundsLike, _matchAllWordForms, _forward, _wrap, _format, _replaceWith, _replace, _matchKashida, _matchDiacritics, _matchAlefHamza, _matchControl) Next Next End Sub Public Event Quit(ByVal sender As Object, ByVal e As EventArgs) Public Sub OnQuit() Implements IApplicationEvents2.Quit wordApplication = Nothing RaiseEvent Quit(Me, New EventArgs) End Sub Public Event DocumentChange(ByVal sender As Object, ByVal e As EventArgs) Public Sub OnDocumentChange() Implements IApplicationEvents2.DocumentChange RaiseEvent DocumentChange(Me, New EventArgs) End Sub Public Event DocumentOpen(ByVal sender As Object, ByVal e As DocumentOpenEventArgs) Public Sub OnDocumentOpen(ByVal doc As Object) Implements IApplicationEvents2.DocumentOpen RaiseEvent DocumentOpen(Me, New DocumentOpenEventArgs(doc)) End Sub Public Class DocumentOpenEventArgs Inherits EventArgs Public Sub New(ByVal document As Object) _doc = document End Sub Private _doc As Object Public ReadOnly Property Document As Object Get Return _doc End Get End Property End Class Private overrideDocumentBeforeClose As Boolean Public Event DocumentBeforeClose(ByVal sender As Object, ByVal e As DocumentBeforeCloseEventArgs) Public Sub OnDocumentBeforeClose(ByVal doc As Object, ByRef cancel As Boolean) Implements IApplicationEvents2.DocumentBeforeClose Dim DBCEA = New DocumentBeforeCloseEventArgs(doc) RaiseEvent DocumentBeforeClose(Me, DBCEA) cancel = DBCEA.Cancel And Not overrideDocumentBeforeClose End Sub Public Class DocumentBeforeCloseEventArgs Inherits EventArgs Public Sub New(ByVal document As Object) _doc = document End Sub Private _doc As Object Public ReadOnly Property Document As Object Get Return _doc End Get End Property Public Property Cancel As Boolean End Class #Region "IDisposable Support" Private disposedValue As Boolean Protected Overridable Sub Dispose(ByVal disposing As Boolean) If Not Me.disposedValue Then If disposing Then RemoveConnection() End If If Me.Active Then Me.Close() End If Me.disposedValue = True End Sub Public Sub Dispose() Implements IDisposable.Dispose Dispose(True) GC.SuppressFinalize(Me) End Sub Public Sub RemoveConnection() If mConnectionPoint IsNot Nothing AndAlso mSinkCookie <> 0 Then mConnectionPoint.Unadvise(mSinkCookie) End If mConnectionPoint = Nothing mSinkCookie = 0 End Sub #End Region <ComImport(), Guid("000209FE-0000-0000-C000-000000000046"), TypeLibType(CShort(4304))> _ Private Interface IApplicationEvents2 <MethodImpl(MethodImplOptions.InternalCall), DispId(2)> _ Sub Quit() <MethodImpl(MethodImplOptions.InternalCall), DispId(3)> _ Sub DocumentChange() <MethodImpl(MethodImplOptions.InternalCall), DispId(4)> _ Sub DocumentOpen(<InAttribute(), MarshalAs(UnmanagedType.Interface)> ByVal doc As Object) <MethodImpl(MethodImplOptions.InternalCall), DispId(6)> _ Sub DocumentBeforeClose(<InAttribute(), MarshalAs(UnmanagedType.Interface)> ByVal doc As Object, <InAttribute(), Out(), MarshalAs(UnmanagedType.Interface)> ByRef cancel As Boolean) End Interface End Class
Class AutoOutlook
Imports System.Reflection Imports System.Runtime.CompilerServices Imports System.Runtime.InteropServices <ClassInterface(ClassInterfaceType.None)> _ Public Class AutoOutlook Implements IApplicationEvents Implements IDisposable Private outlookApplication Private outlookNamespace As Object Private mConnectionPoint As ComTypes.IConnectionPoint Private mSinkCookie As Integer Public Sub New() Renew() End Sub Private Sub Renew() Try outlookApplication = GetObject(, "Outlook.Application") Catch ex As Exception outlookApplication = CreateObject("Outlook.Application") End Try TryCast(outlookApplication, ComTypes.IConnectionPointContainer).FindConnectionPoint( _ GetType(IApplicationEvents).GUID, mConnectionPoint) ' mConnectionPoint.Advise(Me, mSinkCookie) ' the line above doesn't execute in this class and I don't know why outlookNamespace = outlookApplication.GetNamespace("MAPI") outlookNamespace.Logon() End Sub Public ReadOnly Property Active As Boolean Get Return outlookApplication IsNot Nothing End Get End Property Public Sub Test() Dim PF = outlookNamespace.PickFolder Stop End Sub Public Function SendMessageHTML(ByVal _to As String, ByVal _subject As String, ByVal _htmlBody As String, ByVal _attachments As String()) As Boolean Try Dim olMail As Object olMail = outlookApplication.CreateItem(0) olMail.To = _to olMail.Subject = _subject olMail.BodyFormat = 2 olMail.HTMLBody = _htmlBody For Each _file In _attachments If IO.File.Exists(_file) Then olMail.Attachments.Add(_file, 1) End If Next olMail.Send() olMail = Nothing Return True Catch ex As Exception Return False End Try End Function <ComImport(), Guid("0006304E-0000-0000-C000-000000000046"), TypeLibType(CShort(4304))> _ Private Interface IApplicationEvents <MethodImpl(MethodImplOptions.InternalCall), DispId(61442)> _ Sub ItemSend(<InAttribute(), MarshalAs(UnmanagedType.Interface)> ByVal item As Object, <InAttribute(), Out(), MarshalAs(UnmanagedType.Interface)> ByRef cancel As Boolean) <MethodImpl(MethodImplOptions.InternalCall), DispId(&HF003)> _ Sub NewMail() <MethodImpl(MethodImplOptions.InternalCall), DispId(&HF006)> _ Sub Startup() <MethodImpl(MethodImplOptions.InternalCall), DispId(&HF007)> _ Sub Quit() End Interface Private Sub OnStartup() Implements IApplicationEvents.Startup Stop End Sub Private Sub OnQuit() Implements IApplicationEvents.Quit Stop End Sub Private Sub OnItemSend(ByVal item As Object, ByRef cancel As Boolean) Implements IApplicationEvents.ItemSend Stop End Sub Private Sub OnNewMail() Implements IApplicationEvents.NewMail Stop End Sub #Region "IDisposable Support" Private disposedValue As Boolean Protected Overridable Sub Dispose(ByVal disposing As Boolean) If Not Me.disposedValue Then If disposing Then RemoveConnection() End If End If Me.disposedValue = True End Sub Public Sub Dispose() Implements IDisposable.Dispose Dispose(True) GC.SuppressFinalize(Me) End Sub Public Sub RemoveConnection() If mConnectionPoint IsNot Nothing AndAlso mSinkCookie <> 0 Then mConnectionPoint.Unadvise(mSinkCookie) End If mConnectionPoint = Nothing mSinkCookie = 0 End Sub #End Region End Class