Writing a Facebook event synchroniser for Outlook 2007+ Part 2
Now for the juicy post, this will cover the nuts and bolts of putting it all together. I may make a part 3 once I have the time to clean up the code, add logging and other things. The aim of the project after this post is simply to be able to sync and change your RSVP status of a event (if they update their API! See below.
(Installer/Source available at end of post)
API Restrictions
Facebook have decided that only an admin of a event can RSVP through the API, sorry guys this is something that cannot be done through the API! A search through the Facebook developer forum for RSVP results in many annoyed people.
http://bugs.developers.facebook.com/show_bug.cgi?id=4462
There are already close on 2500 votes for them to allow this feature, hopefully they remove the restrictions soon…
Toolbar
To start off I just want a simple toolbar which I can put a few buttons on, and it needs to remember is position so the user has a consistent experience, no one likes a toolbar that will not stay where you put it!
When you register a toolbar in office you can register it as a temporary toolbar, so it will not persist after outlook is closed, or a permanent toolbar. The problem with the permanent one is you have to loop through all explorers and inspectors then the associated toolbars within them to find your toolbar if you ever want to remove it. I find it much easier to rebuild the toolbar on start-up, the above application settings are used simply to remember the toolbar position.
I have found it easier to create a static class with a Install method which creates the toolbar and registers all events etc. When creating your toolbar buttons if you want them to have a image you need to know the faceId, which is a pain. The best site I have found is: http://www.kebabshopblues.co.uk/2007/01/04/visual-studio-2005-tools-for-office-commandbarbutton-faceid-property/
I have referenced this blog post many many times when creating toolbars. I have started off creating two buttons configuration and log now we have out toolbar:
Create the configuration dialog
The first thing you will notice there is no WPF window template available for a VSTO add-in. I have not bothered to figure out how to install one, instead I just add a WPF user control then change the type to a window. Then remove the redundant inheritance from the code behind leaving just the partial class.
<Window x:Class=”FacebookSync.Views.ConfigurationView”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
Height=”300″ Width=”300″>
<Grid>
</Grid>
</Window>
namespace FacebookSync.Views
{
public partial class ConfigurationView
{
public ConfigurationView()
{
InitializeComponent();
}
}
}
If we add some text to our window now:
<TextBlock Text=”Hello world” />
Now we can handle the Click event on our configuration:
private static void ConfigButtonClick(object sender, CommandBarEventArgs e)
{
var configurationViewModel = new ConfigurationViewModel(Globals.ThisAddIn.Settings);
var configurationView = new ConfigurationView {DataContext = configurationViewModel};
configurationView.ShowDialog();
}
And the result we have a WPF window running inside outlook! One cool thing over Winforms is the WPF windows get the Outlook Icon
Configuration View Model
I have written a Settings class which creates a appointment far into the future in your outlook calendar which I can store simple settings. I wanted to store settings within outlook so it keeps your settings within exchange. Here is an example of a setting.
public SyncMode SyncMode
{
get
{
string syncModeSetting = _settings.ReadSetting(“SyncMode”);
int syncMode;
if (syncModeSetting == null || !int.TryParse(syncModeSetting, out syncMode))
return SyncMode.Manual;
return (SyncMode)syncMode;
}
set
{
_settings.WriteSetting(“SyncMode”, ((int)value).ToString());
OnPropertyChanged(“SyncMode”);
}
}
Retrieving events from Facebook
The API does not return the Rsvp status of an a event when you get the list of events, so we have to make 4 calls to Facebook with a filter on the Rsvp status then wrap the facebookevent with our own facebookEvent class which has a Rsvp field. This all happens on a background thread so we don’t have to worry about slowing down Outlook.
public IList<UserFacebookEvent> GetEvents()
{
var api = new facebook.API
{
ApplicationKey = ApiKey,
Secret = Secret
};
api.ConnectToFacebook();
var events = new List<UserFacebookEvent> ();
events.AddRange(
api
.events
.get(null, null, DateTime.Now, null, “attending”)
.Select(e => new UserFacebookEvent(e, RsvpStatus.Attending)));
events.AddRange(
api
.events
.get(null, null, DateTime.Now, null, “unsure”)
.Select(e => new UserFacebookEvent(e, RsvpStatus.Unsure)));
events.AddRange(
api
.events
.get(null, null, DateTime.Now, null, “declined”)
.Select(e => new UserFacebookEvent(e, RsvpStatus.Declined)));
events.AddRange(
api
.events
.get(null, null, DateTime.Now, null, “not_replied”)
.Select(e => new UserFacebookEvent(e, RsvpStatus.NotReplied)));
return events;
}
I think I really need some better class names, but I will do that in part 3 when i clean it all up and add the missing bits.
Dealing with event Start and End Time
After a few hours of getting very frustrated with not being able to convert the time property you find out that the API gives the start and end time in Epoch time BUT not UTC or GMT they give it to you in Pacific Standard Time…
http://forum.developers.facebook.com/viewtopic.php?id=18409
This thread has a simple way to get around it.
var facebookTimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(“Pacific Standard Time”);
var startTime = TimeZoneInfo.ConvertTime(new DateTime(1970, 1, 1).AddSeconds(facebookEvent.start_time),
TimeZoneInfo.Utc,
facebookTimeZoneInfo);
var endTime = TimeZoneInfo.ConvertTime(new DateTime(1970, 1, 1).AddSeconds(facebookEvent.end_time),
TimeZoneInfo.Utc,
facebookTimeZoneInfo);
RSVP Panel in Appointments
I have a few more adapters and wrappers for different things to make my life easier when I start adding features and turning this into a decent add-in! The FacebookEventSynchroniser class is the other main class, it uses a singleton pattern and funnily enough is in control of synchronising events. I have attached the solution for anyone to play around with, feedback would be great.
Out first step is to create a Winforms user control AND a WPF user control, unlike the windows you still need a windows forms control to host in a outlook form region. I am not going to cover the RsvpPanel as it is pretty simple XAML.
One problem I have had with the ElementHost Winforms control is controls sometimes fail to paint, so you have a empty pane staring at you. I have found a really good way to get around this problem.
public UIElement Child
{
get
{
return elementHost1.Child;
}
set
{
elementHost1.Child = value;
elementHost1.Width++;
elementHost1.Dock = DockStyle.Fill;
}
}
The key is the elementHost1.Width++; calling Invalidate() or Refresh() did not do it, the only thing that caused the control to repaint was to resize it. Really odd, but this fixes it every time!
Now there are a few ways I have found to create Task Panes, the first has a nice tutorial and it is good if you want a taskpane for every outlook inspector (Inspector is generally the forms you see when you open a outlook item). The tutorial is available here: http://msdn.microsoft.com/en-us/library/bb296010.aspx
Because I only want my pane to appear for AppointmentItem’s only, so I will create a new Ribbon.
On your new Ribbon set the RibbonType property to Microsoft.Outlook.Appointment, then select group1, rename it to something better, set the Visible property to false and set the Position to “AfterOfficeId GroupShow”.
After that select the tab, and change the OfficeId to TabAppointment so your group appears on the appointment tab!
So lets then setup all the code behind on the Ribbon:
public partial class FacebookRsvpPanel : OfficeRibbon
{
private AppointmentViewModel _appointmentViewModel;
private WpfControlHost _control;
private CustomTaskPane _rsvpPane;
private Inspector _inspector;
public FacebookRsvpPanel()
{
InitializeComponent();
}
private void FacebookRsvpPanel_Load(object sender, RibbonUIEventArgs e)
{
//Get inspector
_inspector = Context as Inspector;
if (_inspector == null) return;
((InspectorEvents_Event)_inspector).Close += InspectorClose;
//Verify the item is a appointment item
var appointment = _inspector.CurrentItem as AppointmentItem;
if (appointment == null) return;
var appointmentViewModel = new AppointmentViewModel(appointment);
if (!appointmentViewModel.IsFacebookEvent) return;
_appointmentViewModel = appointmentViewModel;
SetupRsvpPane(_inspector);
}
private void SetupRsvpPane(Inspector inspector)
{
var wpfPanel = new RsvpPanel(_appointmentViewModel)
{
DataContext = _appointmentViewModel
};
_control = new WpfControlHost
{
Child = wpfPanel
};
_rsvpPane = Globals.ThisAddIn.CustomTaskPanes.Add(_control, "Facebook RSVP", inspector);
_rsvpPane.Width = Properties.Settings.Default.RsvpPaneWidth;
_rsvpPane.Visible = true;
_rsvpPane.VisibleChanged += RsvpPaneVisibleChanged;
facebookGroup.Visible = true;
}
void InspectorClose()
{
((InspectorEvents_Event)_inspector).Close -= InspectorClose;
_inspector = null;
if (_rsvpPane != null)
Globals.ThisAddIn.CustomTaskPanes.Remove(_rsvpPane);
}
void RsvpPaneVisibleChanged(object sender, EventArgs e)
{
showRsvpPanel.Checked = _rsvpPane.Visible;
}
private void showRsvpPanel_Click(object sender, RibbonControlEventArgs e)
{
_rsvpPane.Visible = ((RibbonToggleButton)sender).Checked;
}
}
There are a few other things we hit when we start actually changing the RSVP’s, you have to ask for permission to RSVP, so we will do that as well.
We actually have to extend the Toolkit to include the create_rsvp enum so we can actually get the permission. ![]()
Office 2010
I decided to install the Technical Preview of office 2010 on my machine, so when I try to run the add-in I get this nice error, takes me back to trying to develop office 2003 add-ins with office 2007..
And here is a nice blog post how to work around the issue =)
http://blogs.msdn.com/andreww/archive/2008/02/01/add-ins-for-office-14.aspx
Short version:
- Set all VSTO/Outlook references to copy local.
- Create new Solution configuration, I called it Office 2010.
- Set the Start Action to Start external program, and point it at your Outlook.exe in the Office 14 directory.
And there you go, F5 now works!
Finished product
To stop this popping up when you first start outlook log into Facebook with internet explorer and tick remember my password. Of course the first time you access the app you will have to click allow. Because this is an ActiveX control it must run on the UI thread, it will block the outlook UI.
Configuration window (WPF window =D)
As I said, no RSVP support in the API yet =( If they act on Bug# 4462 then all it requires is enabling those radio buttons and the feature is completely written!
But you can click on the view event button to open the Facebook event in your default browser.
Future Updates
I plan on (depending on time and motivation) cleaning up the code some more and making sure that the Facebook login window does not block the Outlook UI when you first start outlook. Also giving the option to connect to Facebook on demand (put a connect button on the toolbar).
Install now
If you have a better way to deal with this, please let me know!
I have deployed it Via click once, feel free to try it out and give me feedback. Because I don’t have a code signing cert you will have to install my temporary certificate into the Trusted Root Certification Authority store. Download the cert here
Once you have installed the cert, just run the installer from here.
Source
I have removed the facebook AppId and Secret from the code. So you will have to register as a facebook developer, create a new app and get the codes from there. Or contact me and I can email them to you.
| Print article | This entry was posted by Jake Ginnivan on July 27, 2009 at 10:52 pm, and is filed under Uncategorized. Follow any responses to this post through RSS 2.0. You can leave a response or trackback from your own site. |
about 6 months ago
pfx password?
about 6 months ago
Should be the same as the project name. Forgot to mention that.