3
votes

I am trying to implement Adrian Brown's very nice Outlook Add-In code and it works 2 out of 3 times. ItemAdd and ItemChange events are firing as expected, but the event handler for MAPIFolderEvents_12_Event.BeforeItemMove does not appear to be doing anything - I don't even hit a breakpoint on the first line of the event handler.

More Code for Clarity

This is the CalendarMonitor class; it monitors ItemAdd, ItemChange events on the Items collection of the folder, as well as BeforeItemMove on the MAPIFolder:

public class CalendarMonitor
{
    private Explorer _explorer;
    private List<string> _folderPaths;
    private List<MAPIFolder> _calendarFolders;
    private List<Items> _calendarItems;
    private MAPIFolder _deletedItemsFolder;

    public event EventHandler<EventArgs<AppointmentItem>> AppointmentAdded;
    public event EventHandler<EventArgs<AppointmentItem>> AppointmentModified;
    public event EventHandler<CancelEventArgs<AppointmentItem>> AppointmentDeleting;

    public CalendarMonitor(Explorer explorer)
    {
        _folderPaths = new List<string>();
        _calendarFolders = new List<MAPIFolder>();
        _calendarItems = new List<Items>();

        _explorer = explorer;
        _explorer.BeforeFolderSwitch += Explorer_BeforeFolderSwitch;

        var session = _explorer.Session;
        try
        {
            _deletedItemsFolder = session.GetDefaultFolder(OlDefaultFolders.olFolderDeletedItems);
            HookupDefaultCalendarEvents(session);
        }
        finally
        {
            Marshal.ReleaseComObject(session);
            session = null;
        }
    }

    private void HookupDefaultCalendarEvents(_NameSpace session)
    {
        var folder = session.GetDefaultFolder(OlDefaultFolders.olFolderCalendar);
        if (folder == null) return;

        try
        {
            HookupCalendarEvents(folder);
        }
        finally
        {
            Marshal.ReleaseComObject(folder);
            folder = null;
        }
    }

    private void Explorer_BeforeFolderSwitch(object obj, ref bool cancel)
    {
        var folder = (obj as MAPIFolder);
        if (folder == null) return;

        try
        {
            // Hookup events to any other Calendar folder opened.
            if (folder.DefaultItemType == OlItemType.olAppointmentItem)
                HookupCalendarEvents(folder);
        }
        finally
        {
            Marshal.ReleaseComObject(folder);
            folder = null;
        }
    }

    private void HookupCalendarEvents(MAPIFolder calendarFolder)
    {
        if (calendarFolder.DefaultItemType != OlItemType.olAppointmentItem)
        {
            throw new ArgumentException("The MAPIFolder must use AppointmentItems as the default type.");
        }

        // Ignore other user's calendars.
        if (_folderPaths.Contains(calendarFolder.FolderPath) || (!IsUsersCalendar(calendarFolder))) return;

        var items = calendarFolder.Items;

        // Store folder path to prevent repeating listeners
        _folderPaths.Add(calendarFolder.FolderPath);

        // Store a reference to the folder & items to prevent garbage collection
        _calendarFolders.Add(calendarFolder);
        _calendarItems.Add(items);

        // Add listeners
        ((MAPIFolderEvents_12_Event)calendarFolder).BeforeItemMove += Calendar_BeforeItemMove;
        items.ItemChange += CalendarItems_ItemChange;
        items.ItemAdd += CalendarItems_ItemAdd;
    }

    private void CalendarItems_ItemAdd(object obj)
    {
        var appointment = (obj as AppointmentItem);
        if (appointment == null) return;

        try
        {
            if (AppointmentAdded != null)
                AppointmentAdded(this, new EventArgs<AppointmentItem>(appointment));
        }
        finally
        {
            Marshal.ReleaseComObject(appointment);
            appointment = null;
        }
    }

    private void CalendarItems_ItemChange(object obj)
    {
        var appointment = (obj as AppointmentItem);
        if (appointment == null) return;

        try
        {
            if (AppointmentModified != null)
                AppointmentModified(this, new EventArgs<AppointmentItem>(appointment));
        }
        finally
        {
            Marshal.ReleaseComObject(appointment);
            appointment = null;
        }
    }

    private void Calendar_BeforeItemMove(object obj, MAPIFolder moveToFolder, ref bool cancel)
    { 
        if ((moveToFolder != null) && (!IsDeletedItemsFolder(moveToFolder))) return;

        var appointment = (obj as AppointmentItem);
        if (appointment == null) return;

        try
        {
            if (AppointmentDeleting == null) return;

            // Listeners to the AppointmentDeleting event can cancel the move operation if moving
            // to the deleted items folder.
            var args = new CancelEventArgs<AppointmentItem>(appointment);
            AppointmentDeleting(this, args);
            cancel = args.Cancel;
        }
        finally
        {
            Marshal.ReleaseComObject(appointment);
            appointment = null;
        }
    }

    private bool IsUsersCalendar(MAPIFolder folder)
    {
        // This is based purely on my observations so far - a better way?
        return (folder.Store != null);
    }

    private bool IsDeletedItemsFolder(MAPIFolder folder)
    {
        return (folder.EntryID == _deletedItemsFolder.EntryID);
    }

    public AppointmentItem Item { get; set; }
}

New Information:

I have done some additional "troubleshooting" and come up with more information: on a whim, I created a new calendar in Outlook (while debugging) and lo and behold the BeforeItemMove event fires just like I expect it to when deleting an appointment in the new calendar, but it still doesn't work in the original.

If I exit the debug session and restart, neither calendar's event functions as expected, despite working fine earlier. Any new calendar's BeforeItemMove event will work fine, until I close Outlook - then it's back to not responding.

I am hoping that this additional information will provide insight to those wiser than I. Any assistance is greatly appreciated.

2
If the break point isn't being hit and an event isn't firing, have you checked the Modules window to see which DLLs and there VERSION are being loaded?Jeremy Thompson
Could you be more specific? I couldn't find a "Modules" window.Scott Baker
Under the Visual Studio's Debug Menu > Windows > ModulesJeremy Thompson

2 Answers

1
votes

calFolder variable must be declared on the global/class level to avoid being released by the Garbage Collector.

0
votes

Where did you declare the source object? Is it alive when the event should be fired?

Anyway, you may consider developing an inspector wrapper. See Developing an Inspector Wrapper for Outlook 2010 and How to: Implement a Wrapper for Inspectors and Track Item-Level Events in Each Inspector for more information.

The sample project in C# and VB.NET is also available - Outlook 2010: Developing an Inspector Wrapper.