'Outlook item change events in C# misfired
I'm developing an Outlook add-in, that communicates with a server via PHP.
I'm synchronizing data between Outlook items and server data.
Whenever the user makes a change to an item (like drag'n'drop in the calendar of an appointment to another date, or change some values / notes etc.) the item change event is getting fired, and a synchronization happens. The server sometimes sends back some data, that is, then again, written into the Outlook item, but in this case the event is deactivated, so there's no cascading. This is all good, but here comes the bad thing:
I've noticed something really odd. After the event is fired, and everything is correct, Outlook sometimes decides to fire the event again one more time (or even multiple times), after a couple of seconds (ranging from ~3 seconds to ~22 seconds).
This is very unpredictable, and super annoying, since the synchronization relies on the items LastModificationTime which is changed by these random item changes.
Any ways of disabling these events, or at least any ways to differentiate them from actual user actions?
I also monitored the events with the addin made by Add-In-Express, and I also same some strange activity there.
I tried it on multiple PCs, with different versions of Outlook / Windows installed, and it pretty much happens everywhere.
Here's where I set up the events:
public static Outlook.ItemsEvents_ItemChangeEventHandler AppointmentChangeHandler;
public static Outlook.ItemsEvents_ItemChangeEventHandler TaskChangeHandler;
public static Outlook.Items appointments = null;
public static Outlook.Items tasks = null;
public void SetupEventHandlers()
{
Outlook.Application app = Globals.ThisAddIn.Application;
Outlook.NameSpace ns = app.GetNamespace("mapi");
Outlook.MAPIFolder calendar = null;
Outlook.MAPIFolder tasksfolder = null;
try
{
calendar = OutlookHelper.GetMAPIFolderByName("Calendar Where I want my events to work");
if (calendar != null)
{
appointments = calendar.Items;
AppointmentChangeHandler = new Outlook.ItemsEvents_ItemChangeEventHandler(Item_ItemChange);
appointments.ItemChange += AppointmentChangeHandler;
}
}
catch (Exception ex)
{
//failed to get calendar, and to add the itemchange event
}
try
{
tasksfolder = ns.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderTasks);
tasks = tasksfolder.Items;
TaskChangeHandler = new Outlook.ItemsEvents_ItemChangeEventHandler(Item_ItemChange);
tasks.ItemChange += TaskChangeHandler;
}
catch (Exception ex)
{
//failed to get tasks folder, and to add the itemchange event
}
}
And here's the event handler:
public static void Item_ItemChange(object Item)
{
if (Item.LastModificationTime() > Item.LastSync().AddSeconds(2) && !ProgrammaticChange) //I try to do something here: checking if the lastmodification time is more than 2 seconds after the last synchronization time, but as i said, it sometimes adds 22 seconds, sometimes 0...
{
ProgrammaticChange = true; //closing the door, so no cascading happens
SyncItem(Item); //this can change values on the outlook items, that could eventually trigger another event, but the boolean flag is true, so the event will not happen
ProgrammaticChange = false; //opening the door for new events
}
}
Solution 1:[1]
ItemChange
can fire multiple times even for a seemingly single change. One example would be creating an item in an OST file, which then gets uploaded to Exchange - the server tweaks the item and the change is then downloaded to the client side firing the event.
Do not use ItemChange
event as a sole sync entry point - what happens if the change is made when your addin is not running? Use it only as a hint that the sync needs to run sooner rather than later.
More than that, do not run anything that can take a long time in an event handler - if it takes too long to run, the event might not fire next time for another item. Add the item's entry id (but not the item itself!) to a list that you can process later either on a timer or in a separate thread.
Keep in mind that OOM cannot be used on a secondary thread (otherwise an exception will be raised). You can retrieve all the data on the primary thread so that your sync does all the heavy lifting on a secondary thread without touching the Outlook object. If you need to access any Outlook data n a secondary thread, Extended MAPI (C++ or Delphi) or Redemption (I am its author - it wraps Extended MAPI and can be used from any language) are your only options. In the latter case (Redemption), you can cache the value of the Namespace.MAPIOBJECT
property (it returns IMAPISession
MAPI interface) on the main thread, then on a secondary thread create an instance of the RDOSession object and set the RDOSession.MAPIOBJECT
property to the value saved on the primary thread. You can then open the item by its entry id using RDOSession.GetMessageFromID
.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |