VSTO Outlook Visual Basic - Handle Mail Item Closure - outlook

I'm creating an Outlook Add-In, and am struggling to detect and handle the event when a mail item is closed. The inspectors_NewInspector function is triggered sucessfully (as I can obtain the subject and sender of the email), but I cannot get the mailItem_close() function to be called at all.
Public Class ThisAddIn
Public WithEvents inspectors As Outlook.Inspectors
Public WithEvents mailItem As Outlook.MailItem
Private Sub ThisAddIn_Startup() Handles Me.Startup
inspectors = Me.Application.Inspectors
End Sub
Private Sub ThisAddIn_Shutdown() Handles Me.Shutdown
End Sub
Private Sub inspectors_NewInspector(ByVal Inspector As Microsoft.Office.Interop.Outlook.Inspector) Handles inspectors.NewInspector
If Inspector.CurrentItem.size > 0 And Inspector.CurrentItem.class = 43 Then
mailItem = Inspector.CurrentItem
Dim mSubject As String = mailItem.Subject
Dim mFrom As String = mailItem.SenderEmailAddress
Dim mTime As String = mailItem.ReceivedTime
'MsgBox(mSubject)
End If
End Sub
Private Sub mailItem_Close()
MsgBox("closing")
End Sub

It seems you have copied the code from VBA to a VB.NET add-in. In add-ins you need to use the AddHandler method to attach an event handler programmatically, see How do I create an event handler for a programmatically created object in VB.NET? for more information.
Note, each time the NewInspector event is fired the mailItem is overwritten, so the previous item instance is lost and can't fire events:
Private Sub inspectors_NewInspector(ByVal Inspector As Microsoft.Office.Interop.Outlook.Inspector) Handles inspectors.NewInspector
If Inspector.CurrentItem.size > 0 And Inspector.CurrentItem.class = 43 Then
mailItem = Inspector.CurrentItem
Dim mSubject As String = mailItem.Subject
Dim mFrom As String = mailItem.SenderEmailAddress
Dim mTime As String = mailItem.ReceivedTime
'MsgBox(mSubject)
End If
End Sub
Private Sub mailItem_Close()
MsgBox("closing")
End Sub
Instead, you need to maintain a list or array of items, so each time the NewInspector event is fired you will add an item to the list to keep the reference alive, so the events will be fired.
You may find the Implement a wrapper for inspectors and track item-level events in each inspector article helpful.

With huge thanks to Eugene, the solution was indeed to use AddHandler.
Private Sub inspectors_NewInspector(ByVal Inspector As Microsoft.Office.Interop.Outlook.Inspector) Handles inspectors.NewInspector
If Inspector.CurrentItem.size > 0 And Inspector.CurrentItem.class = 43 Then
inspectorEvents = TryCast(Inspector, Outlook.InspectorEvents_Event)
AddHandler inspectorEvents.Close, AddressOf inspectorEvents_Close
End If
End Sub
Public Sub inspectorEvents_Close()
Dim currentInspector As Outlook.Inspector
currentInspector = Application.ActiveInspector
mailItem = currentInspector.CurrentItem
Dim mSubject As String = mailItem.Subject
Dim mFrom As String = mailItem.SenderEmailAddress
Dim mTime As String = mailItem.ReceivedTime
MsgBox("closing" & mSubject)
End Sub
I'm occasionally getting multiple close handlers firing when a mail message is closed, but I should be able to get around that by maintaining an array / list of inspector ids and ensuring only one is created per mail item.

Related

ThisOutlookSession Public WithEvents variables stopped being public

I use event handlers to automatically sort email in Outlook 2016. Those event handlers have been defined in ThisOutlookSession as Public WithEvents variables, they are set using the Application_Startup event, and they call different procedures located in a different code module which directly reference these event handler objects during the procedures.
This morning I get "object is required" error whenever these procedures are run. I turned on Option Explicit and it revealed that when I try to run the procedure manually I get a compile error indicating these variables (the public variable event handlers in ThisOutlookSession) are not defined. The error(s) stop when I either move the procedure to ThisOutlookSession or reference the event handler as "ThisOutlookSession.EventHandler1" instead of just "EventHandler1".
I could use these workarounds, but it would be a lot of work. Any idea what happened or how this could be fixed (without resorting to workarounds)?
Example code (in ThisOutlookSession):
Public WithEvents InboxItems As Outlook.Items
Public WithEvents InboxFolder As Outlook.Folder
Private Sub Application_Startup()
Initialize_handler
End Sub
Public Sub Initialize_handler()
Set InboxItems = Application.GetNamespace("MAPI").GetDefaultFolder(olFolderInbox).Items
Set InboxFolder = Application.GetNamespace("MAPI").GetDefaultFolder(olFolderInbox)
End Sub
Private Sub InboxItems_ItemAdd(ByVal oMail As Object)
Call Inbox_Sort
End Sub
Example code (in separate code module "Custom")
Sub Inbox_Sort()
Dim ML As Outlook.mailItem
Dim oObj As Object
For Each oObj In InboxItems
If TypeOf oObj Is mailItem Then
Call Sort(oObj)
Else
oObj.Move Application.GetNamespace("MAPI").GetDefaultFolder(olFolderInbox)
End If
Next
End Sub

VSTO Word Application events for new Word documents, windows or instances

In a VSTO addin for Word 16 (32) on a Windows 10, I keep track of each document for Ribbon purposes.
I am using the VSTO Designer's Application object. In the designer it looks like this;
Friend WithEvents Application As Microsoft.Office.Interop.Word.Application
Then in ThisAddinn I have the standard code for a WithEvents object.
Private Sub Application_NewDocument(Doc As Microsoft.Office.Interop.Word.Document) Handles Application.NewDocument
End Sub
In ThisAddin I also handle Application.DocumentOpen
After the first instance of Word is open if a user right clicks on the Word icon in the task bar and chooses Word then a new Word document is created, however the above 2 events do not fire.
I also wanted to point out that opening a document from the Word interface or clicking on a word.docx file and opening it all work. It is only when you open a new instance of work by right clicking on the word icon in the task bar.
What event do I need?
I do see that Application.WindowActivate fires. Do I need to use this?
The Application.NewDocument event is fired when a new document is created. If you are working with a document embedded within another document, this event will not occur. You may try to use the following VBA macro to make sure events are fired:
Public WithEvents appWord as Word.Application
Private Sub appWord_NewDocument(ByVal Doc As Document)
Dim intResponse As Integer
Dim strName As String
Dim docLoop As Document
intResponse = MsgBox("Save all other documents?", vbYesNo)
If intResponse = vbYes Then
strName = ActiveDocument.Name
For Each docLoop In Documents
With docLoop
If .Name <> strName Then
.Save
End If
End With
Next docLoop
End If
End Sub
The Application.DocumentOpen event is fired when a document is opened.
Public WithEvents appWord as Word.Application
Private Sub appWord_DocumentOpen(ByVal Doc As Document)
Dim intResponse As Integer
Dim strName As String
Dim docLoop As Document
intResponse = MsgBox("Save all other documents?", vbYesNo)
If intResponse = vbYes Then
strName = ActiveDocument.Name
For Each docLoop In Documents
With docLoop
If .Name <> strName Then
.Save
End If
End With
Next docLoop
End If
End Sub
At startup, you can check whether any document is opened and simulate the DocumentOpen event fired. It seems that events can be fired before you subscribe to them.

How do I listen to "ItemAdd" for all my "Sent items" folders?

My Outlook consists of 3 user mailboxes (cached) and 10 shared mailboxes (online).
I need to catch when a mail in either of these mailboxes is sent, so I googled that I should listen to "ItemAdd" event.
Problem is, that ItemAdd event is not fired.
Here's my test code:
Imports System.Runtime.InteropServices
Public Class ThisAddIn
Private sentFolders As New List(Of Outlook.Folder)
Private Sub ThisAddIn_Startup() Handles Me.Startup
Call InitSentFolders()
End Sub
Private Sub ThisAddIn_Shutdown() Handles Me.Shutdown
End Sub
Private Sub InitSentFolders()
Dim ns As Outlook.NameSpace = Application.GetNamespace("MAPI")
Dim stores As Outlook.Stores = ns.Stores
For i As Integer = 1 To stores.Count
Try
Dim store As Outlook.Store = stores(i)
Try
Dim sentFolder As Outlook.Folder = TryCast(store.GetDefaultFolder(Microsoft.Office.Interop.Outlook.OlDefaultFolders.olFolderSentMail), Outlook.Folder)
AddHandler DirectCast(sentFolder.Items, Outlook.Items).ItemAdd, AddressOf ItemAdd
sentFolders.Add(sentFolder)
Catch ex As Exception
End Try
Marshal.ReleaseComObject(store)
Catch ex As Exception
End Try
Next
Marshal.ReleaseComObject(stores)
Marshal.ReleaseComObject(ns)
End Sub
Private Sub ItemAdd(ByVal ItemObject As Object)
If TypeOf (ItemObject) Is Outlook.MailItem Then
Dim item As Outlook.MailItem = CType(ItemObject, Outlook.MailItem)
MsgBox(item.Sender.ToString)
Marshal.ReleaseComObject(item)
End If
End Sub
End Class
Any idea why it is not fired?
Thanks
That is a widely spread mistake for beginners...
You need declare the source object at the global scope (for example, at the add-in class) and keep it alive to get the event. Or the garbage collector swipes the heap and source object will be destroyed.
In your case define a list of Outlook folders where you can keep all references.

VBA Custom Event Not Found when Raised in UserForm

I was following this MSDN guide on creating custom events. I feel like I understand the process now, but I cannot figure out why I am getting a Compile Error: Event Not Found for RaiseEvent ItemAdded. The weird thing is, the ItemAdded event is recognized by the IDE (I can type it in all lowercase and it is then automatically formatted properly), so I know that it is recognized by VB.
DataComboBox Class Module Code:
Public Event ItemAdded(sItem As String, fCancel As Boolean)
Private pComboBox As Control
Public Property Set oComboBox(cControl As Control)
Set pComboBox = cControl
End Property
Public Property Get oComboBox() As Control
oComboBox = pComboBox
End Property
Private Sub Class_Initialize()
End Sub
Private Sub Class_Terminate()
End Sub
The UserForm contains two controls - a CommandButton named btnAdd and a ComboBox named cboData.
UserForm Code:
Private WithEvents mdcbCombo As DataComboBox
Private Sub UserForm_Initialize()
Set mdcbCombo = New DataComboBox
Set mdcbCombo.oComboBox = Me.cboData
End Sub
Private Sub mdcbCombo_ItemAdded(sItem As String, fCancel As Boolean)
Dim iItem As Long
If LenB(sItem) = 0 Then
fCancel = True
Exit Sub
End If
For iItem = 1 To Me.cboData.ListCount
If Me.cboData.List(iItem) = sItem Then
fCancel = True
Exit Sub
End If
Next iItem
End Sub
Private Sub btnAdd_Click()
Dim sItem As String
sItem = Me.cboData.Text
AddDataItem sItem
End Sub
Private Sub AddDataItem(sItem As String)
Dim fCancel As Boolean
fCancel = False
RaiseEvent ItemAdded(sItem, fCancel)
If Not fCancel Then Me.cboData.AddItem (sItem)
End Sub
You cannot raise an event outside the classes file level.
Add a routine like this inside "DataComboBox1" to allow you to raise the event externally.
Public Sub OnItemAdded(sItem As String, fCancel As Boolean)
RaiseEvent ItemAdded(sItem, fCancel)
End Sub
Then call the OnItemAdded with the current object.
Example...
Private WithEvents mdcbCombo As DataComboBox
...
mdcbCombo.OnItemAdded(sItem, fCancel)

Save Windows Form Size

I'm developing a piece in VB.NET. Inside my primary form, I'm creating a new form to use as a dialog. I was wondering if there was a way to, upon the close of the new dialog, save it's size settings for each user (probably in a file on their machine, through XML or something?)
you can save it to the settings file, and update it on the 'onclosing' event.
to make a setting goto Project Properties ->settings -> then make a setting like 'dialogsize' of type system.drawing.size.
then do this in your dialog form:
Public Sub New()
InitializeComponent()
End Sub
Public Sub New(ByVal userSize As Size)
InitializeComponent()
Me.Size = userSize
End Sub
Protected Overrides Sub OnClosing(ByVal e As System.ComponentModel.CancelEventArgs)
MyBase.OnClosing(e)
My.Settings.DialogSize = Me.Size
My.Settings.Save()
End Sub
do something like this to check and use the setting:
Dim dlg As MyDialogWindow
If My.Settings.DialogSize.IsEmpty Then
dlg = New MyDialogWindow()
Else
dlg = New MyDialogWindow(My.Settings.DialogSize)
End If
dlg.ShowDialog()
Although this is for C#, it will help with VB.Net as well.
You can also add a new setting to your application (size) and set it to system.drawing.size
Then, you make sure you save the current size to settings on close.
Private Sub myForm_FormClosing(ByVal sender As System.Object,
ByVal e As System.Windows.Forms.FormClosingEventArgs) _
Handles MyBase.FormClosing
My.Settings.size = Me.Size
My.Settings.Save()
End Sub
and on load you apply the size you have saved in settings
Private Sub myForm_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles MyBase.Load
' if this is the first time to load the form
' dont set the size ( the form will load with the size in the designe)
If Not My.Settings.size.IsEmpty Then
Me.Size = My.Settings.size
End If
End Sub
Here's a solution that I found online that seems to work rather well for me.
Some of the previously mentioned solutions weren't working for me as expected. Depending on where my form was positioned at the time of closing the form wouldn't get repositioned back to that exact location when I would load it again.
This solution seems to do the trick by taking into account some other factors as well:
You need to set up these two setting under Project Properties -> settings: WindowLocation and WindowSize like so:
Then create the following function:
Private Sub LoadWindowPosition()
'Get window location/position from settings
Dim ptLocation As System.Drawing.Point = My.Settings.WindowLocation
'Exit if it has not been set (X = Y = -1)
If (ptLocation.X = -1) And (ptLocation.Y = -1) Then
Return
End If
'Verify the window position is visible on at least one of our screens
Dim bLocationVisible As Boolean = False
For Each S As Screen In Screen.AllScreens
If S.Bounds.Contains(ptLocation) Then
bLocationVisible = True
Exit For
End If
Next
'Exit if window location is not visible on any screen
If Not bLocationVisible Then
Return
End If
'Set Window Size, Location
Me.StartPosition = FormStartPosition.Manual
Me.Location = ptLocation
Me.Size = My.Settings.WindowSize
End Sub
Next, you'll need to add code to your form's load and closing events like so:
Private Sub frmMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
LoadWindowPosition()
End Sub
Private Sub frmMain_Closing(sender As Object, e As CancelEventArgs) Handles Me.Closing
My.Settings.WindowLocation = Me.Location
My.Settings.WindowSize = Me.Size
End Sub
I hope that helps.
You can also do this using the UI provided by the VB.NET IDE itself. In the properties pane for a form, look under the item called "(Application Settings)" and then under "Property Binding." You can bind just about every property of the form (including size and location) to a settings value for that application.
As it turns out, I found a way to do this using the System.IO.IsolatedStorage

Resources