none
Recurrence Pattern - Occurence Delted RRS feed

  • Question

  • Hi All,

    Part 1:

    I have hooked up the calendar monitor successfully:

       http://adriandev.blogspot.com/2008/01/listening-to-calendar-events-with.html

            public CalendarMonitor monitor;

    private void ThisAddIn_Startup(object sender, System.EventArgs e) { monitor = new CalendarMonitor(this.Application.ActiveExplorer()); monitor.AppointmentDeleting += new EventHandler<CancelEventArgs<Outlook.AppointmentItem>>(Util.newAppointment_BeforeDelete); monitor.AppointmentCancelled += new EventHandler<EventArgs<Outlook.AppointmentItem>>(Util.newAppointment_BeforeDelete); }


    Which works great when deleting normal appointments or even the entire series.

    I'm now trying to catch when an Occurence of a recurrent meeting is deleted.  I understand that the occurence is not an actual meeting but an extension of the master appointment.  (http://www.knowoutlook.com/help/14/recurring-appointments.html#144393).

    I do see the CalendarItems_ItemChange firing . . . however after deleting an occurrence: 

      RecurrencePattern p = appointment.GetRecurrencePattern();

     the p.Exceptions.Count is still 0.

    Is there something that I'm missing?

    Part 2:

    Also once I catch this I need to access the itemProperties of the appointmentItem.  Is the Exception.ItemProperties a copy of the AppointmentItem properties for the recurring item?  

    What I'm attempting to do is assign a unique id for each recurring meeting.  

    eg:

                Outlook.RecurrencePattern p = newAppointment.GetRecurrencePattern();
                Outlook.AppointmentItem ap = p.GetOccurrence(DateTime.Parse("8/16/2014 6:00 PM"));
                ap.ItemProperties.Add("UniqueID", Outlook.OlUserPropertyType.olText, false, null);
                ap.ItemProperties["UniqueID"].Value = (-1).ToString();
                ap.Save();
                Marshal.ReleaseComObject(ap);
                ap = null;


                ap = p.GetOccurrence(DateTime.Parse("8/23/2014 6:00 PM"));
                ap.ItemProperties.Add("UniqueID", Outlook.OlUserPropertyType.olText, false, null);
                ap.ItemProperties["UniqueID"].Value = (-2).ToString();
                ap.Save();
                Marshal.ReleaseComObject(ap);
                ap = null;

                ap = p.GetOccurrence(DateTime.Parse("8/30/2014 6:00 PM"));
                ap.ItemProperties.Add("UniqueID", Outlook.OlUserPropertyType.olText, false, null);
                ap.ItemProperties["UniqueID"].Value = (-3).ToString();
                ap.Save();
                Marshal.ReleaseComObject(ap);
                ap = null;

    Will the above work?

    Tuesday, August 12, 2014 5:33 PM

Answers

  • Okay, I was finally able to knock this out.

    There were a few different things going on which I am going to hilite.  I'm being as verbose as possible because there's not a lot of information on this so hopefully it'll help you out.

    When I thought I was releasing the reference, sometimes there was another reference being held on to (presumably by somewhere else in the code).  

    Since I instantiate my objects for every function, I used the following procedure (which I put in my UTIL class) to ensure that everything was released:

            public static void ReleaseComObject<T>(ref T vic)
            {
                if (vic != null && Marshal.IsComObject(vic))
                {
                    int i = 0;
                    do
                    {
                        i = Marshal.ReleaseComObject(vic);
                    } while (i > 0);
                    vic = default(T);
                }
            }

    A sample of how to use this:

    //Global COM Objects:
            Outlook.Inspector inspector;
            Outlook.Explorer explorer;
            Outlook.NameSpace session;
            Outlook.Recipient CurrentUser;
            Outlook.AppointmentItem newAppointment;
       static object MyOutlookItem; //the outlook appointment item.
    
    // the factory which I instantiate the item
     public partial class <NAME>Factory
            {
                // Occurs before the form region is initialized.
                // To prevent the form region from appearing, set e.Cancel to true.
                // Use e.OutlookItem to get a reference to the current Outlook item.
                private void <NAME>_FormRegionInitializing(object sender, Microsoft.Office.Tools.Outlook.FormRegionInitializingEventArgs e)
                {
                    MyOutlookItem = e.OutlookItem;
                    this.Manifest.FormRegionType = Microsoft.Office.Tools.Outlook.FormRegionType.Separate;
       
                }
            }
    
    //form region showing where this 
    
    private void <NAME>_FormRegionShowing(object sender, EventArgs e)
            {
      inspector = Globals.ThisAddIn.Application.ActiveInspector();
                explorer = Globals.ThisAddIn.Application.ActiveExplorer();
                session = explorer.Session;
                CurrentUser = session.CurrentUser;
                newAppointment = (Outlook.AppointmentItem)MyOutlookItem;
    }
    
    
           private void <NAME>_FormRegionClosed(object sender, EventArgs e)
            {
                Util.ReleaseComObject(ref newAppointment);
                Util.ReleaseComObject(ref MyOutlookItem);
                Util.ReleaseComObject(ref inspector);
                Util.ReleaseComObject(ref CurrentUser);
                Util.ReleaseComObject(ref session);
                Util.ReleaseComObject(ref explorer);
    
            }
    

    *** Note that you MUST re-instantiate the object again after you call the ReleaseObject or you'll get an error message. ***

    Before this point I was having the ExceptionCount = 0.  As long as there is *ANY* reference to the object, you will not get an updated object (gotta love COM).  After properly closing all of the references, I was finally able to see the exceptions being added as expected.

    Within the appointmentChanged item (as you ARE just changing the master appointment) you have to loop over the exceptions.  The Exception.ItemProperties returned a DISP_E_MEMBERNOTFOUND (0x80020003) error when trying to access them.  I was able to utilize the master's ID, find the recurrences from there, and then use the dates to delete the one(s) I needed.  

       private void CalendarItems_ItemChange(object anItem)
            {
               
    
                AppointmentItem appointment = (AppointmentItem)anItem;
                List<DateTime> VictimStartTimes = new List<DateTime>();
    
                if (appointment != null)
                {
                    try
                    {
                        if (appointment.IsRecurring)
                        {
                            RecurrencePattern p = appointment.GetRecurrencePattern();
    
                            foreach (Microsoft.Office.Interop.Outlook.Exception vic in p.Exceptions)
                            {
                                try
                                {
                                    if (vic.Deleted)
                                    {
    //note, OriginalDate doesn't store the time, only MM/DD/YYYY 12:00:00 AM
                                        VictimStartTimes.Add(vic.OriginalDate);
                                    }
                                }
                                catch { }
    
                                int i = 0;
                                
                                do
                                {
                                    i = Marshal.ReleaseComObject(vic);
                                } while (i > 0);
    
                            }
    
    
                            Util.ReleaseComObject(ref p);
                            if (VictimStartTimes.Count > 0)
                            {
                                System.Windows.Forms.Cursor.Current = System.Windows.Forms.Cursors.WaitCursor;
    
    /*You have to get all of the occurences of the meeting, and delete the ones where the dates match.  This is the only way I found that you could delete a specific recurrence */
                              Util.DeleteOccurences(int.Parse(appointment.ItemProperties["MyCustomID"].Value.ToString()), VictimStartTimes);
                                System.Windows.Forms.Cursor.Current = System.Windows.Forms.Cursors.Default;
                            }
                        }
    
                        if (this.AppointmentModified != null)
                        {
                            this.AppointmentModified(this, new EventArgs<AppointmentItem>(appointment));
                        }
                       
    
                        if (this.AppointmentCancelled != null && appointment.MeetingStatus == OlMeetingStatus.olMeetingCanceled)
                            this.AppointmentCancelled(this, new EventArgs<AppointmentItem>(appointment));
    
                    }
                    finally
                    {
    
                        Util.ReleaseComObject(ref appointment);
                    }
                }
            }


    And that's pretty much it.  I appreciate the responses, and thanks again for the help.


    • Marked as answer by Ged325 Wednesday, August 13, 2014 9:47 PM
    • Edited by Ged325 Wednesday, August 13, 2014 9:48 PM
    Wednesday, August 13, 2014 9:46 PM

All replies

  • Hello,

    Yes, it will. But you don't release all underlying COM objects properly. For example:

    ap.ItemProperties.Add("UniqueID", Outlook.OlUserPropertyType.olText, false, null);

    The ItemProperties property returns an instance of the ItemPropeties class which should be released after.  Then the Add method of the ItemProperties class returns an instance of the ItemProperty class which should be released also.

    Use System.Runtime.InteropServices.Marshal.ReleaseComObject to release an Outlook object when you have finished using it. Then set a variable to Nothing in Visual Basic (null in C#) to release the reference to the object. You can read more about this in the Systematically Releasing Objects article in MSDN.

    The RecurrencePattern.Exceptions page in MSDN states the following:

    When you work with recurring appointment items, you should release any prior references, obtain new references to the recurring appointment item before you access or modify the item, and release these references as soon as you are finished and have saved the changes. This practice applies to the recurring AppointmentItem object, and any Exception or RecurrencePattern object.

    Note that even after you release your reference and attempt to obtain a new reference, if there is still an active reference, held by another add-in or Outlook, to one of the above objects, your new reference will still point to an out-of-date copy of the object. Therefore, it is important that you release your references as soon as you are finished with the recurring appointment.

    The Exceptions collection contains the group of Exception objects that define the exceptions to that series of appointments. Exception objects are added to the Exceptions object whenever a property in the corresponding AppointmentItem object is altered. Why do you expect to find the Count property not equal to zero after deleting an appointment item?

    Tuesday, August 12, 2014 6:00 PM
  • Hi Eugene,

    I will make the corrections and test that part again.  So essentially any sub class needs to be released as well?  Should the sub classes (EG: ItemProperty ) be released before or after the save?

    With regards to the exceptions if I'm deleting an occurence of the recurring appointment I would expect to see an exception in the pattern.  

    My Workflow being:  

    1)  Create a recurring appointment (in this case weekly pattern / internval 1 / Mask of Saturday / Occurences 3 . . . gives me 8/16/2014 , 8/23/2014, 8/30/2014)

    2)  Instantiate the itemproperties

    3)  Delete an occurence (middle - 8/23/2014) of the item 

    4)  The on change event fires, brings back the master appointment

    5)  I would expect to see the exception for 8/23/2014 with the deleted flag in the exceptions.

    If that's incorrect or if there's something I'm missing please let me know.  

    Thanks again for the quick reply.

    Tuesday, August 12, 2014 6:26 PM
  • It is actually not a subclass. The property ItemProperties returns an instance of the corresponding class. You need to release all Outlook *objects* in the code.

    An exception is created if you change any property from the occurrences (for example, the start date). The item is not added to the exceptions collection when you delete it.

    Tuesday, August 12, 2014 6:34 PM
  • Sorry about the terminology.  Thanks for correcting.

    So is there a way to detect the deletion of an occurrence?  How do I access the item properties of the deleted occurrence?



    • Edited by Ged325 Tuesday, August 12, 2014 6:37 PM
    Tuesday, August 12, 2014 6:36 PM
  • Try to handle the ItemRemove event of the Items class which is fired when an item is deleted from the specified collection. Be aware, this event does not run when the last item in  a Personal Folders file (.pst) is deleted, or if 16 or more items are deleted  at once from a PST file, Microsoft Exchange mailbox, or an Exchange public folder.
    Tuesday, August 12, 2014 8:17 PM
  • The ItemRemove event doesn't fire when I removed an occurrence.  From my understanding I'm not actually deleting anything correct?  It's just an Exception?  


    • Edited by Ged325 Tuesday, August 12, 2014 8:29 PM
    Tuesday, August 12, 2014 8:28 PM
  • Okay, I was finally able to knock this out.

    There were a few different things going on which I am going to hilite.  I'm being as verbose as possible because there's not a lot of information on this so hopefully it'll help you out.

    When I thought I was releasing the reference, sometimes there was another reference being held on to (presumably by somewhere else in the code).  

    Since I instantiate my objects for every function, I used the following procedure (which I put in my UTIL class) to ensure that everything was released:

            public static void ReleaseComObject<T>(ref T vic)
            {
                if (vic != null && Marshal.IsComObject(vic))
                {
                    int i = 0;
                    do
                    {
                        i = Marshal.ReleaseComObject(vic);
                    } while (i > 0);
                    vic = default(T);
                }
            }

    A sample of how to use this:

    //Global COM Objects:
            Outlook.Inspector inspector;
            Outlook.Explorer explorer;
            Outlook.NameSpace session;
            Outlook.Recipient CurrentUser;
            Outlook.AppointmentItem newAppointment;
       static object MyOutlookItem; //the outlook appointment item.
    
    // the factory which I instantiate the item
     public partial class <NAME>Factory
            {
                // Occurs before the form region is initialized.
                // To prevent the form region from appearing, set e.Cancel to true.
                // Use e.OutlookItem to get a reference to the current Outlook item.
                private void <NAME>_FormRegionInitializing(object sender, Microsoft.Office.Tools.Outlook.FormRegionInitializingEventArgs e)
                {
                    MyOutlookItem = e.OutlookItem;
                    this.Manifest.FormRegionType = Microsoft.Office.Tools.Outlook.FormRegionType.Separate;
       
                }
            }
    
    //form region showing where this 
    
    private void <NAME>_FormRegionShowing(object sender, EventArgs e)
            {
      inspector = Globals.ThisAddIn.Application.ActiveInspector();
                explorer = Globals.ThisAddIn.Application.ActiveExplorer();
                session = explorer.Session;
                CurrentUser = session.CurrentUser;
                newAppointment = (Outlook.AppointmentItem)MyOutlookItem;
    }
    
    
           private void <NAME>_FormRegionClosed(object sender, EventArgs e)
            {
                Util.ReleaseComObject(ref newAppointment);
                Util.ReleaseComObject(ref MyOutlookItem);
                Util.ReleaseComObject(ref inspector);
                Util.ReleaseComObject(ref CurrentUser);
                Util.ReleaseComObject(ref session);
                Util.ReleaseComObject(ref explorer);
    
            }
    

    *** Note that you MUST re-instantiate the object again after you call the ReleaseObject or you'll get an error message. ***

    Before this point I was having the ExceptionCount = 0.  As long as there is *ANY* reference to the object, you will not get an updated object (gotta love COM).  After properly closing all of the references, I was finally able to see the exceptions being added as expected.

    Within the appointmentChanged item (as you ARE just changing the master appointment) you have to loop over the exceptions.  The Exception.ItemProperties returned a DISP_E_MEMBERNOTFOUND (0x80020003) error when trying to access them.  I was able to utilize the master's ID, find the recurrences from there, and then use the dates to delete the one(s) I needed.  

       private void CalendarItems_ItemChange(object anItem)
            {
               
    
                AppointmentItem appointment = (AppointmentItem)anItem;
                List<DateTime> VictimStartTimes = new List<DateTime>();
    
                if (appointment != null)
                {
                    try
                    {
                        if (appointment.IsRecurring)
                        {
                            RecurrencePattern p = appointment.GetRecurrencePattern();
    
                            foreach (Microsoft.Office.Interop.Outlook.Exception vic in p.Exceptions)
                            {
                                try
                                {
                                    if (vic.Deleted)
                                    {
    //note, OriginalDate doesn't store the time, only MM/DD/YYYY 12:00:00 AM
                                        VictimStartTimes.Add(vic.OriginalDate);
                                    }
                                }
                                catch { }
    
                                int i = 0;
                                
                                do
                                {
                                    i = Marshal.ReleaseComObject(vic);
                                } while (i > 0);
    
                            }
    
    
                            Util.ReleaseComObject(ref p);
                            if (VictimStartTimes.Count > 0)
                            {
                                System.Windows.Forms.Cursor.Current = System.Windows.Forms.Cursors.WaitCursor;
    
    /*You have to get all of the occurences of the meeting, and delete the ones where the dates match.  This is the only way I found that you could delete a specific recurrence */
                              Util.DeleteOccurences(int.Parse(appointment.ItemProperties["MyCustomID"].Value.ToString()), VictimStartTimes);
                                System.Windows.Forms.Cursor.Current = System.Windows.Forms.Cursors.Default;
                            }
                        }
    
                        if (this.AppointmentModified != null)
                        {
                            this.AppointmentModified(this, new EventArgs<AppointmentItem>(appointment));
                        }
                       
    
                        if (this.AppointmentCancelled != null && appointment.MeetingStatus == OlMeetingStatus.olMeetingCanceled)
                            this.AppointmentCancelled(this, new EventArgs<AppointmentItem>(appointment));
    
                    }
                    finally
                    {
    
                        Util.ReleaseComObject(ref appointment);
                    }
                }
            }


    And that's pretty much it.  I appreciate the responses, and thanks again for the help.


    • Marked as answer by Ged325 Wednesday, August 13, 2014 9:47 PM
    • Edited by Ged325 Wednesday, August 13, 2014 9:48 PM
    Wednesday, August 13, 2014 9:46 PM