달력

32024  이전 다음

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

'vsto'에 해당되는 글 1건

  1. 2008.10.09 Listening to Calendar Events with Outlook 2007 and VSTO

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

Friday, January 11, 2008
Listening to Calendar Events with Outlook 2007 and VSTO
Keywords: .NET, Outlook 2007, Visual Studio 2008, VSTO
I've recently being trying to listen to events from a user's calendars so that I can track when appointments are added, removed and modified, and ran into a couple of problems.

Problem 1: Events intermittently stop firing

I was listening to events on the MAPIFolder object and the Calendar's Items collection and they would work for a while and at some point would stop firing.

I eventually traced this down to what I believe is a COM reference counting issue. I wasn't storing a reference to these objects, so I guess there was no implicit AddRef by the interop library. As such, there was no need for the runtime to keep the object around.

Hanging on to these references appears to be resolving this issue, so I stand by my assumptions.

Problem 2: How do we know what has been deleted?

The events I was hooking up to were on the ItemsEvents_Event interface off the MAPIFolder.Items property. There are thee events on this interface:


ItemAdd

ItemChange

ItemRemove


The first two have a single parameter which will be the AppointmentItem that is either being added or changed. Great! The last one doesn't have any parameters - all it tells you is that an item has been removed, but doesn't give you any indication what has been deleted. Besides that, its actually too late anyway, the item has already been deleted (you cannot cancel it).

So, I am now hooking up to the BeforeItemMove event on the MAPIFolderEvents_12_Event interface off the MAPIFolder object. This gives me a reference to the AppointmentItem being moved, the folder in which it is being moved to, and also allows me to cancel it.

To detect that it is being deleted, just check the folder it is being moved to is null (hard-delete such as Shift-Del) or the deleted items folder (soft-delete).

Solution

I have created a class that will monitor a user's default calendar and fires three events:

AppointmentAdded - gives you the AppointmentItem instance added.

AppointmentModified - gives you the AppointmentItem instance changed.

AppointmentDeleting - gives you the AppointmentItem that is about to be deleted and allows you to cancel the operation if needed.


Sample usage:

The following code is for use within the ThisAddIn class. It outputs to console when an item is added, modified, or about to be deleted. It also displays a dialog box confirming an item should be deleted.
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
  CalendarMonitor monitor = new CalendarMonitor(this.Application.Session);
  monitor.AppointmentAdded +=
      new EventHandler<EventArgs<AppointmentItem>>(monitor_AppointmentAdded);
  monitor.AppointmentModified +=
      new EventHandler<EventArgs<AppointmentItem>>(monitor_AppointmentModified);
  monitor.AppointmentDeleting +=
      new EventHandler<CancelEventArgs<AppointmentItem>>(monitor_AppointmentDeleting);
}

private void monitor_AppointmentAdded(object sender, EventArgs<AppointmentItem> e)
{
  Debug.Print("Appointment Added: {0}", e.Value.GlobalAppointmentID);
}

private void monitor_AppointmentModified(object sender, EventArgs<AppointmentItem> e)
{
  Debug.Print("Appointment Modified: {0}", e.Value.GlobalAppointmentID);
}

private void monitor_AppointmentDeleting(object sender, CancelEventArgs<AppointmentItem> e)
{
  Debug.Print("Appointment Deleting: {0}", e.Value.GlobalAppointmentID);
  DialogResult dr = MessageBox.Show("Delete appointment?", "Confirm",
  MessageBoxButtons.YesNo, MessageBoxIcon.Question);
  if (dr == DialogResult.No)
  {
      e.Cancel = true;
  }
}

There are two other classes used: EventArgs and CancelEventArgs. These generics are used by the events to pass strongly typed appointments.

EventArgs.cs:
using System;
using System.Collections.Generic;

namespace OutlookAddIn2
{
  public class EventArgs<T> : EventArgs
  {
      private T _value;

      public EventArgs(T aValue)
      {
          _value = aValue;
      }

      public T Value
      {
          get { return _value; }
          set { _value = value; }
      }
  }
}

CancelEventArgs.cs:
using System;
using System.Collections.Generic;

namespace OutlookAddIn2
{
  public class CancelEventArgs<T> : EventArgs<T>
  {
      private bool _cancel;

      public CancelEventArgs(T aValue)
          : base(aValue)
      {
      }

      public bool Cancel
      {
          get { return _cancel; }
          set { _cancel = value; }
      }
  }
}

CalendarMonitor.cs:
using System;
using System.Collections.Generic;
using Microsoft.Office.Interop.Outlook;

namespace OutlookAddIn2
{
  public class CalendarMonitor
  {
      private NameSpace _session;
      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(NameSpace aSession)
      {
          _folderPaths = new List<string>();
          _calendarFolders = new List<MAPIFolder>();
          _calendarItems = new List<Items>();

          _session = aSession;
          _deletedItemsFolder = aSession.GetDefaultFolder(
              OlDefaultFolders.olFolderDeletedItems);

          HookupDefaultCalendarEvents();
      }

      private void HookupDefaultCalendarEvents()
      {
          MAPIFolder folder = _session.GetDefaultFolder(
              OlDefaultFolders.olFolderCalendar);
          if (folder != null)
          {
              HookupCalendarEvents(folder);
          }
      }

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

          if (_folderPaths.Contains(aCalendarFolder.FolderPath) == false)
          {
              Items items = aCalendarFolder.Items;
              //
              // Store folder path to prevent double ups on our listeners.
              //
              _folderPaths.Add(aCalendarFolder.FolderPath);
              //
              // Store a reference to the folder and to the items collection
              // so that it remains alive for as long as we want. This keeps
              // the ref count up on the underlying COM object and prevents
              // it from being intermittently released (then the events don't
              // get fired).
              //
              _calendarFolders.Add(aCalendarFolder);
              _calendarItems.Add(items);
              //
              // Add listeners for the events we need.
              //
              ((MAPIFolderEvents_12_Event)aCalendarFolder).BeforeItemMove +=
                  new MAPIFolderEvents_12_BeforeItemMoveEventHandler(Calendar_BeforeItemMove);
              items.ItemChange +=
                  new ItemsEvents_ItemChangeEventHandler(CalendarItems_ItemChange);
              items.ItemAdd +=
                  new ItemsEvents_ItemAddEventHandler(CalendarItems_ItemAdd);
          }
      }

      private void CalendarItems_ItemAdd(object anItem)
      {
          if (anItem is AppointmentItem)
          {
              if (this.AppointmentAdded != null)
              {
                  this.AppointmentAdded(this,
                      new EventArgs<AppointmentItem>((AppointmentItem)anItem));
              }
          }
      }

      private void CalendarItems_ItemChange(object anItem)
      {
          if (anItem is AppointmentItem)
          {
              if (this.AppointmentModified != null)
              {
                  this.AppointmentModified(this,
                      new EventArgs<AppointmentItem>((AppointmentItem)anItem));
              }
          }
      }

      private void Calendar_BeforeItemMove(object anItem, MAPIFolder aMoveToFolder,
          ref bool Cancel)
      {
          if ((aMoveToFolder == null) || (IsDeletedItemsFolder(aMoveToFolder)))
          {
              if (anItem is AppointmentItem)
              {
                  if (this.AppointmentDeleting != null)
                  {
                      //
                      // Listeners to the AppointmentDeleting event can cancel
                      // the move operation if moving to the deleted items folder.
                      //
                      CancelEventArgs<AppointmentItem> args =
                          new CancelEventArgs<AppointmentItem>((AppointmentItem)anItem);
                      this.AppointmentDeleting(this, args);
                      Cancel = args.Cancel;
                  }
              }
          }
      }

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

20 comments:

Anonymous said...
Thanks so much for this! I would have never figured it out.

23 April 2008 04:12
Adrian Brown said...
I've been looking up other people's posts for help for years. I'm glad I've helped at least one person!

23 April 2008 09:02
Anonymous said...
Hi Adrian, thank you so much for posting this code. I have finally managed to get Stephen Toub code "Custom Calendar Providers for Outlook 2003" to work but I have one doubt, which I need your help for. I need to be able delete an event from Outlook calendar when an event from my webservice is deleted. Would I be able to use your code to accomplish this???

24 April 2008 17:52
Anonymous said...
Hi Adrian, I was reading through your code "CalendarMonitor" class, you have a variable called "MAPIFolderEvents_12_Event", can you tell me what this is as it's not been declared anywhere in your code????

24 April 2008 22:33
Adrian Brown said...
The code here only listens to events of a Calendar in Outlook (it won't delete, add, move anything itself), specifically 2007. I believe there are some small differences between 2007 and 2003, but you should still be able to use this code as a basis.

As for the MAPIFolderEvents_12_Event reference, this is an interface implemented by the MAPIFolder object. From memory, I think you have to explicitly cast the MAPIFolder object to MAPIFolderEvents_12_Event to get access to the events. You should find this defined in the Outlook COM interop library that is referenced as part of the Visual Studio Outlook AddIn Project.

25 April 2008 12:27
Anonymous said...
Hi Adrian, I'm glad to see I'm not the only one that had this issue. I was able to accomplish removing an appointment from the db when the user deleted an appointment from the calendar by returning all the items in the Deleted Items folder and then looping through those items to return the AppointmentItem for each item in the folder by using the following code.


Outlook.MAPIFolder deletedItemsFolder =
Application.Session.GetDefaultFolder(OlDefaultFolders.olFolderDeletedItems);

Outlook.Items deletedAppointments = deletedItemsFolder.Items;

foreach (AppointmentItem outlookAppointment in deletedAppointments)
{
if (outlookAppointment != null)
{
object objAppointmentID = GetUserProperty(outlookAppointment, APPOINTMENT_ID_KEY);
if (objAppointmentID == null) { return; }
int appointmentID = int.Parse(objAppointmentID.ToString());

Appointment appointment = Appointment.GetAppointment(appointmentID);

appointment.Delete();

If you see any issues with doing it this way please respond so I can fix them.

Thanks

20 June 2008 22:19
Adrian Brown said...
The only issue I can see with this method is you are assuming that all deleted items go to the deleted items folder.

Appointments can be deleted directly (Shift-Del is the shortcut I think) and this won't get picked up by that code.

Cheers,
Adrian.

26 June 2008 09:12
Anonymous said...
Hi Adrian,
this post is really helped me a lot. This is great code i was looking for. i have small issue in my outlook add-in, when user changes the time of the appointment item through the calender dragging, it fires item change event. I want to cancel Item Change event in certain conditions. Is it possible to cancel Item Change change event.

Thanks & Regards,
Sandeep

26 August 2008 11:57
Adrian Brown said...
Sandeep,

Good question, but I don't know. From what I have seen, it looks unlikely.

Cheers,
Adrian.

27 August 2008 16:27
Sandeep said...
hi Adrian,
Your code works great if i make any change in appointment or click on delete Appointment. If i send meeting invitation and and then cancel meeting it doesn't capture this event. I think BeforeItemMove should capture this event, but it doesn't. Can you please tell me how to capture cancel meeting event?

Thanks & Regards,
Sandeep

27 August 2008 22:14
WAQ said...
Hi Adrian,

A very useful post.

Just a question for you see if you can help. How we can trap the item before delete event in outlook 2000 or if there is any other work around we can use by implementing some other events?

Thanks in advance

10 September 2008 00:42
Adrian Brown said...
I'm sorry WAQ, I don't know with Outlook 2000.

10 September 2008 09:04
Alex said...
This post has been removed by the author.
12 September 2008 23:46
Alex said...
Hi Adrian, loving the code you've posted although for some reason the MAPIFolderEvents_12_Event.BeforeItemMove event doesn't seem to fire on a custom calendar. Any ideas why this might be?

Thanks in advance,

Alex

13 September 2008 00:37
EagleSigma said...
Great Post Adrian! Thanks to you I was able to actually get my application running the way I wanted.

Just one thing is bugging me: If I create or edit an appointment using Outlook 2007 everthing works as expected and I'm able to retrieve the "e.Value.Organizer" value. However, if a user does the same using Outlook web access 2003 - Premium or Basic, that value comes back with the vale "Error."

Any guidedance or suggestions woould be greatly appreciated. Thanks again.

14 September 2008 05:43
Adrian Brown said...
Hi Alex,

The code posted won't work on a custom calendar because it is only listening to events on the default calendar.

You'll have to loop through all of the MAPIFolders looking for anything that is a calendar.

Cheers,
Adrian.

15 September 2008 17:21
Adrian Brown said...
Hi eaglesigma,

Yeah, I haven't really played around much with OWA. It wasn't a requirement for me, so I deliberately chose to ignore it. :o)

Sorry, and good luck!
Adrian.

15 September 2008 17:23
Alex said...
Hi Adrian, I've actually modified the code you posted to take in an array of MAPIFolders when constructing the CalendarMonitor object so it it is listening to events on the my calendar. The item events fire but not the folder event as mentioned before...?

15 September 2008 17:45
Adrian Brown said...
Alex,

Hmmm... can't see why the folder event does not fire. I don't think I've tried it with a custom calendar myself and haven't really looked at this Outlook stuff for quite a while now.

I'm really sorry but I don't have the time to look into this right away.

If you have any luck getting it to work or find out why it doesn't, please let me know. Otherwise I'll try and get to it sometime over the next week or so (which is probably way too late for you).

Cheers,
Adrian.

15 September 2008 17:58
William said...
Hello Adrian,

The modify event seems to be firing too early.

Here is the issue:

Sometimes when the calendar has multiple updates the entry shows up
on the calendar view but when I
access the properties, such as Start or Subject, it does not have the new values- it has the previous values that were present before the modify event fired.

The tricky part is that on the calendar view, the tooltip balloon shows the correct and latest data-but if you double click the item to open it, it still has the the old data. Somehow, the tooltip control is able to access the latest data before the item actually receives the changes.

The problem is that my code checks for the Start time and Subject to decide how to process the item; so when I get the Modify event and my code launches, it doesn't do anything because the two fields are still the same.

With 5 to 10 minutes eventually all the items in the calendar receive the latest data. However, at that point no event is triggered to let me process the modified entries.

What can I use in my Modify event that will force the public folder to give me the latest properties for the item?
Thanks you for your help.

William

9 October 2008 03:21

Posted by tornado
|