Outlook Add-in Code Review

  • Thread starter Thread starter JRomeo
  • Start date Start date
J

JRomeo

Hi.

It's my first time ever writing an add-in. It's for Outlook 2003 using VSTO
SE. It works well. However, we're about to deploy this to about 1000 users,
and before we do, I'd like to see if anyone can catch any gaping holes in my
code. It's a single C# class called ThisAddin.cs. It does the following:

- Adds a control to the standard toolbar of each Appointment Inspector
- Clicking the control grabs the values of some of the inspectors
ItemProperties, opens a IE window, and passes them in a URL
- Closing the inspector removes the control and releases the objects and
related events

Warnings, gotchas, best practices: all comments welcome. Here it is. Thanks
in advance.

-----------


using System;
using System.Windows.Forms;
using Diagnostics = System.Diagnostics;
using Microsoft.VisualStudio.Tools.Applications.Runtime;
using Outlook = Microsoft.Office.Interop.Outlook;
using Office = Microsoft.Office.Core;
using SHDocVw;

namespace OutlookConfCallerBtn
{
public partial class ThisAddIn
{
Diagnostics.Process newIE;
Office.CommandBars cmdBars;
Office.CommandBar stdBar;
Office.CommandBarButton Btn_ConfRoomSchdlr;
Outlook.Inspectors openInspectors;
Outlook.AppointmentItem item;

/// <summary>
/// Do the setup on add-in startup
/// </summary>
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
openInspectors = this.Application.Inspectors;
openInspectors.NewInspector += new
Outlook.InspectorsEvents_NewInspectorEventHandler(newInspector_Event);
}

/// <summary>
/// Release the objects on shutdown
/// </summary>
private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
{
// Drop the NewInspector and Btn_ConfRoomSchdlr event handlers
openInspectors.NewInspector -= new
Microsoft.Office.Interop.Outlook.InspectorsEvents_NewInspectorEventHandler(newInspector_Event);

// Kill the class-level objects in play
openInspectors = null;
cmdBars = null;
stdBar = null;
Btn_ConfRoomSchdlr = null;
item = null;
newIE = null;
}

/// <summary>
/// Handle the Inspector.NewInspector event
/// This adds the command bar control to all Appointment inspectors
/// </summary>
private void newInspector_Event(Outlook.Inspector new_Inspector)
{
// Check what type of inspector we have
Outlook.AppointmentItem item = new_Inspector.CurrentItem as
Outlook.AppointmentItem;

// For appointment items, add a custom control
if (item != null)
{
try
{

// Get the standard toolbar for this inspector
cmdBars = new_Inspector.CommandBars;
stdBar = cmdBars["standard"];

// Add the control
Btn_ConfRoomSchdlr =
(Office.CommandBarButton)stdBar.Controls.Add(1, missing, missing, 12, true);
Btn_ConfRoomSchdlr.Style =
Office.MsoButtonStyle.msoButtonIconAndCaption;
Btn_ConfRoomSchdlr.Caption = "Reserve Room(s)";
Btn_ConfRoomSchdlr.Tag = "Reserve Room(s)";
Btn_ConfRoomSchdlr.BeginGroup = true;
Btn_ConfRoomSchdlr.Picture = getImage();

// Set the event handlers for the button click and
Inspector.Close
((Outlook.InspectorEvents_Event)new_Inspector).Close +=
new Outlook.InspectorEvents_CloseEventHandler(closeInspector_Event);
Btn_ConfRoomSchdlr.Click += new
Office._CommandBarButtonEvents_ClickEventHandler(clickButton_Event);
}

catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}

/// <summary>
/// Handle the Button.Click event. This opens the conference room
scheduler
/// </summary>
private void clickButton_Event(Office.CommandBarButton ctrl, ref
bool cancel)
{
// Set up the reference time data
System.Collections.Hashtable timeHash = new
System.Collections.Hashtable();

int hr = 5;
String min = "00";
String mdn = "am";

// Build the times on the fly, from 5:00 am - 9:30 pm
for (int i = 1; i <= 34; i++)
{
String tKey = "_" + hr.ToString() + min + mdn;
timeHash.Add(tKey, i.ToString());
if (i % 2 != 0) { min = "30"; }
else
{
min = "00";
if (hr == 12) { hr = 1; }
else
{
hr++;
if (hr == 11)
{
mdn = "pm";
}
}
}
}

// Assemble the Start Time and End Time values and open a URL
item =
this.Application.ActiveInspector().CurrentItem as Outlook.AppointmentItem;
String [] StartGroup =
item.ItemProperties["Start"].Value.ToString().Split(new Char[] {' '});
String [] EndGroup =
item.ItemProperties["End"].Value.ToString().Split(new Char[] { ' ' });
String [] StartTime = StartGroup[1].Split(new Char[] { ':' });
String [] EndTime = EndGroup[1].Split(new Char[] { ':' });
String selDate = StartGroup[0];
String sTimeKey = ("_" + StartTime[0] + StartTime[1] +
StartGroup[2]).ToLower();
String eTimeKey = ("_" + EndTime[0] + EndTime[1] +
EndGroup[2]).ToLower();

// Get the time values from timeHash, otherwise use -1
String sTimeID = (timeHash.ContainsKey(sTimeKey) ?
timeHash[sTimeKey].ToString() : "-1");
String eTimeID = (timeHash.ContainsKey(eTimeKey) ?
timeHash[eTimeKey].ToString() : "-1");
String ConfSchdURL =
"http://devapps/confroom/index.cfm?event=wizard";
ConfSchdURL += "&StartTime=" + sTimeID + "&EndTime=" +
eTimeID + "&SelDate=" + selDate;

// MessageBox.Show(sTimeKey + ":" + sTimeID + " " + eTimeKey
+ ":" + eTimeID);
newIE = System.Diagnostics.Process.Start("IExplore.exe",
ConfSchdURL);

}

/// <summary>
/// Handle the Inspector.Close event
/// </summary>
void closeInspector_Event()
{
// Drop the Btn_ConfRoomSchdlr event handler
Btn_ConfRoomSchdlr.Click -= new
Office._CommandBarButtonEvents_ClickEventHandler(clickButton_Event);
}

/// <summary>
/// Add the converted image to an ImageList
/// </summary>
private stdole.IPictureDisp getImage()
{
stdole.IPictureDisp tempImage = null;
try
{
System.Drawing.Icon newIcon = Properties.Resources.confchair;
ImageList newImageList = new ImageList();
newImageList.Images.Add(newIcon);
tempImage = ConvertImage.Convert(newImageList.Images[0]);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return tempImage;
}

#region VSTO generated code

/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InternalStartup()
{
this.Startup += new System.EventHandler(ThisAddIn_Startup);
this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
}

#endregion
}

// Convert the icon file to an Image object
sealed public class ConvertImage : System.Windows.Forms.AxHost
{
private ConvertImage()
: base(null)
{
}
public static stdole.IPictureDisp Convert(System.Drawing.Image image)
{
return
(stdole.IPictureDisp)System.Windows.Forms.AxHost.GetIPictureDispFromPicture(image);
}
}
}
 
I see 2 things offhand.

If you intend to ever support Outlook 2007 you will need to move your UI
creation to the Inspector.Activate() event, in NewInspector() the object
reference passed is a weak object reference and not good for much else other
than checking Inspector.CurrentItem.Class or .MessageClass. In most cases
Inspector.CommandBars is not valid in NewInspector in Outlook 2007 (of
course for Outlook 2007 you'd really want to support the ribbon instead of
using the CommandBars interface).

Second, your code will not correctly handle cases where more than 1
Inspector is open at a time. For cases like that the best practice is to
create a collection (list, hashtable, etc.) of wrapper classes. Each wrapper
class encapsulates an open Inspector and has event handlers for the
Inspector events. That allows each Inspector's UI and events to be handled
individually. There are many examples of Inspector wrapper classes and
collections on my Web site and on www.outlookcode.com among other places.




JRomeo said:
Hi.

It's my first time ever writing an add-in. It's for Outlook 2003 using
VSTO
SE. It works well. However, we're about to deploy this to about 1000
users,
and before we do, I'd like to see if anyone can catch any gaping holes in
my
code. It's a single C# class called ThisAddin.cs. It does the following:

- Adds a control to the standard toolbar of each Appointment Inspector
- Clicking the control grabs the values of some of the inspectors
ItemProperties, opens a IE window, and passes them in a URL
- Closing the inspector removes the control and releases the objects and
related events

Warnings, gotchas, best practices: all comments welcome. Here it is.
Thanks
in advance.

-----------


using System;
using System.Windows.Forms;
using Diagnostics = System.Diagnostics;
using Microsoft.VisualStudio.Tools.Applications.Runtime;
using Outlook = Microsoft.Office.Interop.Outlook;
using Office = Microsoft.Office.Core;
using SHDocVw;

namespace OutlookConfCallerBtn
{
public partial class ThisAddIn
{
Diagnostics.Process newIE;
Office.CommandBars cmdBars;
Office.CommandBar stdBar;
Office.CommandBarButton Btn_ConfRoomSchdlr;
Outlook.Inspectors openInspectors;
Outlook.AppointmentItem item;

/// <summary>
/// Do the setup on add-in startup
/// </summary>
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
openInspectors = this.Application.Inspectors;
openInspectors.NewInspector += new
Outlook.InspectorsEvents_NewInspectorEventHandler(newInspector_Event);
}

/// <summary>
/// Release the objects on shutdown
/// </summary>
private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
{
// Drop the NewInspector and Btn_ConfRoomSchdlr event handlers
openInspectors.NewInspector -= new
Microsoft.Office.Interop.Outlook.InspectorsEvents_NewInspectorEventHandler(newInspector_Event);

// Kill the class-level objects in play
openInspectors = null;
cmdBars = null;
stdBar = null;
Btn_ConfRoomSchdlr = null;
item = null;
newIE = null;
}

/// <summary>
/// Handle the Inspector.NewInspector event
/// This adds the command bar control to all Appointment inspectors
/// </summary>
private void newInspector_Event(Outlook.Inspector new_Inspector)
{
// Check what type of inspector we have
Outlook.AppointmentItem item = new_Inspector.CurrentItem as
Outlook.AppointmentItem;

// For appointment items, add a custom control
if (item != null)
{
try
{

// Get the standard toolbar for this inspector
cmdBars = new_Inspector.CommandBars;
stdBar = cmdBars["standard"];

// Add the control
Btn_ConfRoomSchdlr =
(Office.CommandBarButton)stdBar.Controls.Add(1, missing, missing, 12,
true);
Btn_ConfRoomSchdlr.Style =
Office.MsoButtonStyle.msoButtonIconAndCaption;
Btn_ConfRoomSchdlr.Caption = "Reserve Room(s)";
Btn_ConfRoomSchdlr.Tag = "Reserve Room(s)";
Btn_ConfRoomSchdlr.BeginGroup = true;
Btn_ConfRoomSchdlr.Picture = getImage();

// Set the event handlers for the button click and
Inspector.Close
((Outlook.InspectorEvents_Event)new_Inspector).Close +=
new Outlook.InspectorEvents_CloseEventHandler(closeInspector_Event);
Btn_ConfRoomSchdlr.Click += new
Office._CommandBarButtonEvents_ClickEventHandler(clickButton_Event);
}

catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}

/// <summary>
/// Handle the Button.Click event. This opens the conference room
scheduler
/// </summary>
private void clickButton_Event(Office.CommandBarButton ctrl, ref
bool cancel)
{
// Set up the reference time data
System.Collections.Hashtable timeHash = new
System.Collections.Hashtable();

int hr = 5;
String min = "00";
String mdn = "am";

// Build the times on the fly, from 5:00 am - 9:30 pm
for (int i = 1; i <= 34; i++)
{
String tKey = "_" + hr.ToString() + min + mdn;
timeHash.Add(tKey, i.ToString());
if (i % 2 != 0) { min = "30"; }
else
{
min = "00";
if (hr == 12) { hr = 1; }
else
{
hr++;
if (hr == 11)
{
mdn = "pm";
}
}
}
}

// Assemble the Start Time and End Time values and open a URL
item =
this.Application.ActiveInspector().CurrentItem as Outlook.AppointmentItem;
String [] StartGroup =
item.ItemProperties["Start"].Value.ToString().Split(new Char[] {' '});
String [] EndGroup =
item.ItemProperties["End"].Value.ToString().Split(new Char[] { ' ' });
String [] StartTime = StartGroup[1].Split(new Char[] {
':' });
String [] EndTime = EndGroup[1].Split(new Char[] {
':' });
String selDate = StartGroup[0];
String sTimeKey = ("_" + StartTime[0] + StartTime[1] +
StartGroup[2]).ToLower();
String eTimeKey = ("_" + EndTime[0] + EndTime[1] +
EndGroup[2]).ToLower();

// Get the time values from timeHash, otherwise use -1
String sTimeID = (timeHash.ContainsKey(sTimeKey) ?
timeHash[sTimeKey].ToString() : "-1");
String eTimeID = (timeHash.ContainsKey(eTimeKey) ?
timeHash[eTimeKey].ToString() : "-1");
String ConfSchdURL =
"http://devapps/confroom/index.cfm?event=wizard";
ConfSchdURL += "&StartTime=" + sTimeID + "&EndTime=" +
eTimeID + "&SelDate=" + selDate;

// MessageBox.Show(sTimeKey + ":" + sTimeID + " " + eTimeKey
+ ":" + eTimeID);
newIE = System.Diagnostics.Process.Start("IExplore.exe",
ConfSchdURL);

}

/// <summary>
/// Handle the Inspector.Close event
/// </summary>
void closeInspector_Event()
{
// Drop the Btn_ConfRoomSchdlr event handler
Btn_ConfRoomSchdlr.Click -= new
Office._CommandBarButtonEvents_ClickEventHandler(clickButton_Event);
}

/// <summary>
/// Add the converted image to an ImageList
/// </summary>
private stdole.IPictureDisp getImage()
{
stdole.IPictureDisp tempImage = null;
try
{
System.Drawing.Icon newIcon =
Properties.Resources.confchair;
ImageList newImageList = new ImageList();
newImageList.Images.Add(newIcon);
tempImage = ConvertImage.Convert(newImageList.Images[0]);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return tempImage;
}

#region VSTO generated code

/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InternalStartup()
{
this.Startup += new System.EventHandler(ThisAddIn_Startup);
this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
}

#endregion
}

// Convert the icon file to an Image object
sealed public class ConvertImage : System.Windows.Forms.AxHost
{
private ConvertImage()
: base(null)
{
}
public static stdole.IPictureDisp Convert(System.Drawing.Image
image)
{
return
(stdole.IPictureDisp)System.Windows.Forms.AxHost.GetIPictureDispFromPicture(image);
}
}
}
 
Nice. That's what I was looking for. Thx.


--
Tip: Never eat yellow snow.


Ken Slovak - said:
I see 2 things offhand.

If you intend to ever support Outlook 2007 you will need to move your UI
creation to the Inspector.Activate() event, in NewInspector() the object
reference passed is a weak object reference and not good for much else other
than checking Inspector.CurrentItem.Class or .MessageClass. In most cases
Inspector.CommandBars is not valid in NewInspector in Outlook 2007 (of
course for Outlook 2007 you'd really want to support the ribbon instead of
using the CommandBars interface).

Second, your code will not correctly handle cases where more than 1
Inspector is open at a time. For cases like that the best practice is to
create a collection (list, hashtable, etc.) of wrapper classes. Each wrapper
class encapsulates an open Inspector and has event handlers for the
Inspector events. That allows each Inspector's UI and events to be handled
individually. There are many examples of Inspector wrapper classes and
collections on my Web site and on www.outlookcode.com among other places.




JRomeo said:
Hi.

It's my first time ever writing an add-in. It's for Outlook 2003 using
VSTO
SE. It works well. However, we're about to deploy this to about 1000
users,
and before we do, I'd like to see if anyone can catch any gaping holes in
my
code. It's a single C# class called ThisAddin.cs. It does the following:

- Adds a control to the standard toolbar of each Appointment Inspector
- Clicking the control grabs the values of some of the inspectors
ItemProperties, opens a IE window, and passes them in a URL
- Closing the inspector removes the control and releases the objects and
related events

Warnings, gotchas, best practices: all comments welcome. Here it is.
Thanks
in advance.

-----------


using System;
using System.Windows.Forms;
using Diagnostics = System.Diagnostics;
using Microsoft.VisualStudio.Tools.Applications.Runtime;
using Outlook = Microsoft.Office.Interop.Outlook;
using Office = Microsoft.Office.Core;
using SHDocVw;

namespace OutlookConfCallerBtn
{
public partial class ThisAddIn
{
Diagnostics.Process newIE;
Office.CommandBars cmdBars;
Office.CommandBar stdBar;
Office.CommandBarButton Btn_ConfRoomSchdlr;
Outlook.Inspectors openInspectors;
Outlook.AppointmentItem item;

/// <summary>
/// Do the setup on add-in startup
/// </summary>
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
openInspectors = this.Application.Inspectors;
openInspectors.NewInspector += new
Outlook.InspectorsEvents_NewInspectorEventHandler(newInspector_Event);
}

/// <summary>
/// Release the objects on shutdown
/// </summary>
private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
{
// Drop the NewInspector and Btn_ConfRoomSchdlr event handlers
openInspectors.NewInspector -= new
Microsoft.Office.Interop.Outlook.InspectorsEvents_NewInspectorEventHandler(newInspector_Event);

// Kill the class-level objects in play
openInspectors = null;
cmdBars = null;
stdBar = null;
Btn_ConfRoomSchdlr = null;
item = null;
newIE = null;
}

/// <summary>
/// Handle the Inspector.NewInspector event
/// This adds the command bar control to all Appointment inspectors
/// </summary>
private void newInspector_Event(Outlook.Inspector new_Inspector)
{
// Check what type of inspector we have
Outlook.AppointmentItem item = new_Inspector.CurrentItem as
Outlook.AppointmentItem;

// For appointment items, add a custom control
if (item != null)
{
try
{

// Get the standard toolbar for this inspector
cmdBars = new_Inspector.CommandBars;
stdBar = cmdBars["standard"];

// Add the control
Btn_ConfRoomSchdlr =
(Office.CommandBarButton)stdBar.Controls.Add(1, missing, missing, 12,
true);
Btn_ConfRoomSchdlr.Style =
Office.MsoButtonStyle.msoButtonIconAndCaption;
Btn_ConfRoomSchdlr.Caption = "Reserve Room(s)";
Btn_ConfRoomSchdlr.Tag = "Reserve Room(s)";
Btn_ConfRoomSchdlr.BeginGroup = true;
Btn_ConfRoomSchdlr.Picture = getImage();

// Set the event handlers for the button click and
Inspector.Close
((Outlook.InspectorEvents_Event)new_Inspector).Close +=
new Outlook.InspectorEvents_CloseEventHandler(closeInspector_Event);
Btn_ConfRoomSchdlr.Click += new
Office._CommandBarButtonEvents_ClickEventHandler(clickButton_Event);
}

catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}

/// <summary>
/// Handle the Button.Click event. This opens the conference room
scheduler
/// </summary>
private void clickButton_Event(Office.CommandBarButton ctrl, ref
bool cancel)
{
// Set up the reference time data
System.Collections.Hashtable timeHash = new
System.Collections.Hashtable();

int hr = 5;
String min = "00";
String mdn = "am";

// Build the times on the fly, from 5:00 am - 9:30 pm
for (int i = 1; i <= 34; i++)
{
String tKey = "_" + hr.ToString() + min + mdn;
timeHash.Add(tKey, i.ToString());
if (i % 2 != 0) { min = "30"; }
else
{
min = "00";
if (hr == 12) { hr = 1; }
else
{
hr++;
if (hr == 11)
{
mdn = "pm";
}
}
}
}

// Assemble the Start Time and End Time values and open a URL
item =
this.Application.ActiveInspector().CurrentItem as Outlook.AppointmentItem;
String [] StartGroup =
item.ItemProperties["Start"].Value.ToString().Split(new Char[] {' '});
String [] EndGroup =
item.ItemProperties["End"].Value.ToString().Split(new Char[] { ' ' });
String [] StartTime = StartGroup[1].Split(new Char[] {
':' });
String [] EndTime = EndGroup[1].Split(new Char[] {
':' });
String selDate = StartGroup[0];
String sTimeKey = ("_" + StartTime[0] + StartTime[1] +
StartGroup[2]).ToLower();
String eTimeKey = ("_" + EndTime[0] + EndTime[1] +
EndGroup[2]).ToLower();

// Get the time values from timeHash, otherwise use -1
String sTimeID = (timeHash.ContainsKey(sTimeKey) ?
timeHash[sTimeKey].ToString() : "-1");
String eTimeID = (timeHash.ContainsKey(eTimeKey) ?
timeHash[eTimeKey].ToString() : "-1");
String ConfSchdURL =
"http://devapps/confroom/index.cfm?event=wizard";
ConfSchdURL += "&StartTime=" + sTimeID + "&EndTime=" +
eTimeID + "&SelDate=" + selDate;

// MessageBox.Show(sTimeKey + ":" + sTimeID + " " + eTimeKey
+ ":" + eTimeID);
newIE = System.Diagnostics.Process.Start("IExplore.exe",
ConfSchdURL);

}

/// <summary>
/// Handle the Inspector.Close event
/// </summary>
void closeInspector_Event()
{
// Drop the Btn_ConfRoomSchdlr event handler
Btn_ConfRoomSchdlr.Click -= new
Office._CommandBarButtonEvents_ClickEventHandler(clickButton_Event);
}

/// <summary>
/// Add the converted image to an ImageList
/// </summary>
private stdole.IPictureDisp getImage()
{
stdole.IPictureDisp tempImage = null;
try
{
System.Drawing.Icon newIcon =
Properties.Resources.confchair;
ImageList newImageList = new ImageList();
newImageList.Images.Add(newIcon);
tempImage = ConvertImage.Convert(newImageList.Images[0]);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return tempImage;
}

#region VSTO generated code

/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InternalStartup()
{
this.Startup += new System.EventHandler(ThisAddIn_Startup);
this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
}

#endregion
}

// Convert the icon file to an Image object
sealed public class ConvertImage : System.Windows.Forms.AxHost
{
private ConvertImage()
: base(null)
{
}
public static stdole.IPictureDisp Convert(System.Drawing.Image
image)
{
return
(stdole.IPictureDisp)System.Windows.Forms.AxHost.GetIPictureDispFromPicture(image);
}
}
}
 
Back
Top