I'm trying to write an Outlook Add-in that deletes an email after the email has been read completely.
The problem is that the read-flag turns true the second we click on it and it doesn't give us much time to read the email.
I tried to delete the email after closing it:
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
this.Application.ItemSend += new Outlook.ApplicationEvents_11_ItemSendEventHandler(Application_ItemSend);
Outlook.MAPIFolder inbox = this.Application.ActiveExplorer().Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox);
foreach (Outlook.MailItem mail in inbox.Items)
{
((Outlook.ItemEvents_10_Event)mail).Close += new Outlook.ItemEvents_10_CloseEventHandler(MailItem_Close);
}
}
void MailItem_Close(ref bool Cancel)
{
Outlook.MAPIFolder inbox = this.Application.ActiveExplorer().Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox);
Outlook.Items inboxReadItems = inbox.Items.Restrict("[Unread]=false");
foreach (Outlook.MailItem mail in inboxReadItems)
{
mail.Delete();
}
}
This was the idea i came up with, sometimes it works but most of the times it ends with an error in the mail.Delete():
The Error: System.Runtime.InteropServices.COMException: 'The item’s properties and methods cannot be used inside this event procedure.'
First of all, your event handler may not be fired because the source object is declared in the methods and the scope of that object is limited by the method/function. After execution of the method/function ends, the garbage collector may swipe the heap (unpredictable) with your source object, so you may never get the event fired.
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
this.Application.ItemSend += new Outlook.ApplicationEvents_11_ItemSendEventHandler(Application_ItemSend);
Outlook.MAPIFolder inbox = this.Application.ActiveExplorer().Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox);
foreach (Outlook.MailItem mail in inbox.Items)
{
((Outlook.ItemEvents_10_Event)mail).Close += new Outlook.ItemEvents_10_CloseEventHandler(MailItem_Close);
}
}
Instead, you need to declare the sourceobject at the class level if you want to keep getting events fired, in your case maintain a list of objects (which is also inefficient when dealing with COM objects):
List<Outlook.MailItem> items = new List<Outlook.MailItem>();
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
this.Application.ItemSend += new Outlook.ApplicationEvents_11_ItemSendEventHandler(Application_ItemSend);
Outlook.MAPIFolder inbox = this.Application.ActiveExplorer().Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox);
foreach (Outlook.MailItem mail in inbox.Items)
{
((Outlook.ItemEvents_10_Event)mail).Close += new Outlook.ItemEvents_10_CloseEventHandler(MailItem_Close);
list.Add(mail);
}
}
But in that case you may reach the limit of currently held and opened objects in Outlook with Exchange. So, the better solution is to handle the SelectionChange event of the Explorer class and deal with the currently selected items only. Every time the selection is changed, your source objects are renowned (old are released and new ones are subscribed).
But a better yet solution is to implement an item wrapper for Outlook items, so you could subscribe to the Close event or any other events in a convenient fashion. See Implement a wrapper for inspectors and track item-level events in each inspector for more information.
This is as inefficient as it gets - you set up event handlers on (potentially) thousands of emails in the Inbox folder. Do not do that.
Track the Explorer.SelectionChange event and set up event handlers only on those emails that are selected (and remove event handlers from the previous selections). If the item is unread, trap its read state change, and put its entry id in a list. When selection changes, delete the previously unread email(s) from that list.
Related
I'm writing Outlook VSTO add-in and I want to do size check before attachment add and if the file is too big I want to upload it to the cloud, so the code looks like:
public partial class ThisAddIn
{
private void ThisAddIn_Startup(object sender, EventArgs e)
{
Application.Inspectors.NewInspector += InspectorsOnNewInspector;
}
private void InspectorsOnNewInspector(Inspector inspector)
{
if (inspector.CurrentItem is MailItem mailItem)
{
mailItem.BeforeAttachmentAdd += MailItemOnBeforeAttachmentAdd;
}
}
private void MailItemOnBeforeAttachmentAdd(Attachment attachment, ref bool cancel)
{
// check and upload
cancel = true;
}
private void ThisAddIn_Shutdown(object sender, EventArgs e)
{
// Note: Outlook no longer raises this event. If you have code that
// must run when Outlook shuts down, see https://go.microsoft.com/fwlink/?LinkId=506785
}
#region VSTO generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
[SuppressMessage("ReSharper", "ArrangeThisQualifier")]
[SuppressMessage("ReSharper", "RedundantDelegateCreation")]
[SuppressMessage("ReSharper", "RedundantNameQualifier")]
private void InternalStartup()
{
this.Startup += new System.EventHandler(ThisAddIn_Startup);
this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
}
#endregion
}
The problem is that all works fine until the file exceeds the size limit configured in MS Exchange. When it happens I get a notification message and after clicking "OK" mailItem.BeforeAttachmentAdd event doesn't fire. How can I deal with it?
None of your event handlers will work for more than a few seconds - you are setting the event handler either on a temporary variable (created by the compiler) in case of Application.Inspectors.NewInspector or on a local variable (when you set mailItem.BeforeAttachmentAdd event handler).
The object raising the events must be alive - store these objects on the global (class) level to make sure they are not collected by the Garbage Collector.
Also, there is no particular / guaranteed order of events, but I would imagine Outlook would always get the first pick. Worst case, you can patch IDropTarget implementation of the Outlook window and provide your own implementation. Not much you can do if an attachment is being inserted from the Ribbon...
For the case you have more accounts/stores in your Outlook and want that ItemAdd event fires e.g. for all sent items folder.
This is what I have so far but the event is not firing for all sent items folder:
foreach (Outlook.Store store in _outlookNameSpace.Stores)
{
// _SentItems = null;
// _items = null;
try
{
_SentItems = store.GetDefaultFolder(OlDefaultFolders.olFolderSentMail);
_items = _SentItems.Items;
_items.ItemAdd += new Outlook.ItemsEvents_ItemAddEventHandler(items_ItemAdd); // BUGBUG: The problem is probably here, as the object needs to be alive which is firing the event?
}
catch
{
AppUtils.DoLog("Skipping this store.");
}
}
These guys are defined as global class variables:
Outlook.NameSpace _outlookNameSpace;
Outlook.MAPIFolder _SentItems;
Outlook.Items _items;
Create a wrapper class that takes Items object as a parameter in its constructor, saves it in a field, and sets up an ItemAdd event handler. You can then initialize a wrapper for each store and store then in a list to ensure the wrappers (and their Items objects) stay alive and can raise events.
I'm currently migrating a VSTO add in written in VB to C# for outlook.
The general idea, is to log every single email information into a database of my own.
I've searched thoroughly and it seems that NewMail / NewMailEX events(from the application object) are the best options to handle it.
However, both events won't trigger for emails received when outlook client is down.
I'm having problems while trying to process all mails that are downloaded on startup from the exchange server, so i thought that "Item-add" event from items collection, might address this issue.
I know for a fact that this can be addressed within "item-add" event because we are actually handling this issue in the VB code.
However, when a try suscribing to the "item-add" event for each Inbox folder in Outlook, nothing happens!
There is no error nor exception thrown whatsoever.
in our VB code,
we could managed to suscribe to the mentioned event with this code:
outlookNameSpace = Me.Application.GetNamespace("MAPI")
inbox = outlookNameSpace.Stores(account).GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox)
Mailitem = inbox.Items
Private Sub Items_ItemAdd(ByVal item As Object) Handles Mailitem.ItemAdd
Here is my failing C# code:
//looping to fetch all my inboxes
public static void InitialOutlookConfiguration(Outlook.Application myOutlookInstance)
{
Outlook.Accounts myAccounts = myOutlookInstance.GetNamespace("MAPI").Accounts;
foreach(Outlook.Account myAccount in myAccounts)
{
Outlook.MAPIFolder inbox = myAccount.DeliveryStore.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox);
string storeID = myAccount.DeliveryStore.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox).StoreID;
myInboxes.Add(inbox, storeID);
foreach(Outlook.MAPIFolder inbox in myInboxes.Keys)
{
Outlook.Items myInboxItems = inbox.Items;
myInboxItems.ItemAdd += new Outlook.ItemsEvents_ItemAddEventHandler(OnNewItem);
}
}
}
The object raising the events (myInboxItems) must be alive - otherwise it gets released by the Garbage Collector and no events are raised.
The usual pattern is to introduce your own wrapper class that takes the COM object in question (Items) as a constructor parameter, stores it in a class member and sets up an event handler. You can then then create that wrapper class for each Inbox folder and store each wrapper in a list. That list must be declared on the class level to ensure it (and its items) stay alive when InitialOutlookConfiguration() completes.
public List<Outlook.Items> myInboxMailItems = new List<Outlook.Items>();
public Items InboxMails;
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
//watch.Start();
//chequear el orden en que solicitamos la ejecucion de la configuracion
//this.Application.Startup += new Outlook.ApplicationEvents_11_StartupEventHandler(OnOutlookOutlookStartup);
Outlook.Accounts myAccounts = this.Application.GetNamespace("MAPI").Accounts;
foreach (Outlook.Account myAccount in myAccounts)
{
Outlook.MAPIFolder inbox = myAccount.DeliveryStore.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox);
myInboxMailItems.Add(inbox.Items);
foreach (Outlook.Items i in myInboxMailItems)
{
i.ItemAdd += new Outlook.ItemsEvents_ItemAddEventHandler(test);
}
}
}
public void test(object i)
{
System.Windows.Forms.MessageBox.Show("Eureka!");
}
Just in case anybody sticks with the same issue, thanks Dimitry for your garbage collector insight!
I am using VSTO to create an event when an email is sent. The goal is change Attachments.
I already have other addins that run in the ItemSend event, but the problem is, I want my addin to run first. As I've read, there is no execution order for the Outlook addins sent event, but there must be some order even if only by name or guid...
I tried this solution (the problem is, if I have 2 mail windows open, the first window doesn´t run the event ... :( there is some overwrite event problem)
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
this.Application.Inspectors.NewInspector += new InspectorsEvents_NewInspectorEventHandler(Custom_Inspector);
//This run in the end off all ItemSend Events.... :(
//this.Application.ItemSend += new Microsoft.Office.Interop.Outlook.ApplicationEvents_11_ItemSendEventHandler(MyFunction2);
}
private void Custom_Inspector(Inspector Inspector)
{
if (Inspector != null && Inspector.CurrentItem != null && Inspector.CurrentItem is Outlook.MailItem)
{
Outlook.MailItem mailItem = Inspector.CurrentItem as Outlook.MailItem;
if (mailItem.EntryID == null)
{
((ItemEvents_10_Event)mailItem).Send += new ItemEvents_10_SendEventHandler(MyFunction);
}
}
}
void MyFunction(ref bool Cancel)
{
MailItem mailItemContext = ((Inspector)this.Application.ActiveWindow()).CurrentItem as MailItem;
if (mailItemContext != null)
{
//my custom code here
}
}
this.Application.Inspectors.NewInspector += new InspectorsEvents_NewInspectorEventHandler(Custom_Inspector);
To get the NewInspector event of the Inspectors class fired you need to keep the source object alive, i.e. prevent it from swiping by the garbage collector. So, I'd recommend declaring the an instance of the Inspectors class at the global scope - at the class level.
The Outlook object model doesn't provide anything for changing the order of events. From my experience add-ins are loaded based on the ProgID value (sorted in the alphabetical order) and events are fired in the reverse order, i.e. a LIFO queue.
Eugene 100000 thanks! in reality Outlook Order Plugin Events by alphabetical reverse.
But by the way, how set NewInspector in top class? i need to define inside class ThisAddIn a prop call:
public partial class ThisAddIn
{
public Microsoft.Office.Interop.Outlook.Inspectors _inspector;
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
_inspector = this.Application.Inspectors;
_inspector.NewInspector += new InspectorsEvents_NewInspectorEventHandler(Custom_Inspector);
}
}
I am developing an Outlook AddIn. One part of it that I organize appointments in a specific folder. I want to capture if an element gets deleted (in this case moving out of "my" folder counts as deleted).
I found the article https://stackoverflow.com/questions/10579240/how-to-capture-a-c-sharp-outlook-addin-appointment-delete-event and his/her solution helped a lot, but I have a huge problem: the event only fires in that "session", where my folder was created, not when I get the folder object from outlook.
My code looks like this:
private Outlook.MAPIFolder _CalendarMAPIFolder = null;
private Outlook.MAPIFolderEvents_12_Event _CalendarFolder = null;
private Outlook.Items _CalendarItems = null;
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
Outlook.MAPIFolder calendarFolder =
this.Application.GetNamespace("mapi").GetDefaultFolder(Outlook.OlDefaultFolders.olFolderCalendar);
// get my-Folder (if not found, create it)
try
{
_CalendarMAPIFolder = calendarFolder.Folders["my-Folder"];
}
catch
{
_CalendarMAPIFolder = calendarFolder.Folders.Add("my-Folder");
}
_CalendarItems = _CalendarMAPIFolder.Items;
_CalendarFolder = _CalendarMAPIFolder as Outlook.MAPIFolderEvents_12_Event;
if (_CalendarFolder == null)
{
MessageBox.Show("can not cast MAPIFolder to Folder");
}
_CalendarFolder.BeforeItemMove += new Outlook.MAPIFolderEvents_12_BeforeItemMoveEventHandler(Folder_BeforeItemMove);
Debug.Print("events registered");
}
public void Folder_BeforeItemMove(
Object Item,
Outlook.MAPIFolder MoveTo,
ref bool Cancel)
{
Outlook.AppointmentItem aitem = Item as Outlook.AppointmentItem;
string s = "";
if (aitem != null) s = aitem.Subject;
//Cancel = false;
MessageBox.Show("Test! " + s);
}
Does anyone have a solution?
Thank you ;)
Edit: I still have no clue :(((((((
By definition, BeforeItemMove only fires when the user (Outlook client) initiates an Item be moved. It will not fire for synchronization events (i.e. Exchange sync).
If you are connecting Outlook to an Exchange Server, you should look at EWS (Exchange Web Services) if you wish to be notified of folder change events outside the client application (i.e. session). EWS offers push, pull, or streaming notification options. You would attach a notification to "Item deletion" operation.
It was all Microsofts fault! It was a Bug, I just needed to Update Outlook!