|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionIn this article, you will learn how to intercept an Outlook dialog and replace it with your own .NET form. The Outlook Object Model itself exposes no events and objects to replace the built-in dialogs with your own. However, by combining VSTO, P/Invoke, and .NET technologies, you have the ability to replace any kind of Outlook built-in dialog you can imagine. Benefits
Drawbacks
BackgroundThe Microsoft Outlook Object Model (OOM) is powerful, and provides access to many features that use and manipulate the data stored in Outlook and on the Exchange Server. Here are some common options to use external data in Microsoft Outlook:
Importing / Exporting data is time consuming and a reason for synchronization conflicts. The data is outdated and out of sync. Here is a new scenario that shows how to intercept an Outlook built-in dialog and replace it with your own. The ideaWhat you definitely can get from the Outlook Object Model are Explorer and Inspector objects which represent application and data item windows. Luckily, whenever such a window is activated by the user (when someone clicks on it), or when it is deactivated (when another window comes to the front), you will receive events from these objects. You can use these events to get notified when windows are activated or deactivated. This is also true when a user clicks on the "To" or "Cc" button to select a recipient from the Recipient dialog. Whenever the Address / Recipient dialog is shown, your inspector window is deactivated. You can intercept and search for the opened Recipient dialog in the Deactivate event handler, close the Recipient dialog, and open your own .NET form instead. Set up the solutionBefore you can start hacking into Outlook, you have to install the minimum requirements on your development machine. Prerequisites
Create a solutionTo demonstrate this technique, start an Outlook AddIn project. Here in my case, I have Outlook 2007 (German) and Visual Studio 2008 Beta 2 running on Vista 64 bit.
After you have created the project, you will find a skeleton class called namespace CustomAddressDialog
{
public partial class ThisAddIn
{
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
}
private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
{
}
#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
}
}
The Outlook InspectorWrapper templateAs you can read in many articles, one of the most common problems when programming Outlook add-ins is the fact that you can have multiple Explorers and Inspectors opened and closed at any time during the lifetime of your Outlook session. One method of handling this situation correctly is by using an Explorer/Inspector wrapper which encapsulates each of these windows and traps the events and states of the different windows during their lifetime, and which does a proper cleanup to avoid ghost instances and crashes of your Outlook application. This technique is also common for The Inspector/Explorer wrapper template is basically a wrapper class which has a unique ID, holds a reference to the wrapped object inside the class, monitors the object state, and informs the application when the object has been closed. Here it goes: public partial class ThisAddIn
{
// the Outlook Inspectors collection
Outlook.Inspectors _Inspectors;
// the Outlook Explorers collection
Outlook.Explorers _Explorers;
// a collection of wrapped objects
Dictionary<guid,WrappedObject> _WrappedObjects;
/// <summary>
/// The entrypoint for the application
/// </summary>
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
_WrappedObjects = new Dictionary<guid,WrappedObject>();
// Inspectors stuff
_Inspectors = this.Application.Inspectors;
// Any open Inspectors after startup ?
for (int i = _Inspectors.Count; i >= 1; i--)
{
// wrap the Inspector
WrapInspector(_Inspectors[i]);
}
// get notified for new inspectors
_Inspectors.NewInspector += new
Outlook.InspectorsEvents_NewInspectorEventHandler(_Inspectors_NewInspector);
// Explorer stuff
_Explorers = this.Application.Explorers;
// Are there any open Explorers after Startup ?
for (int i = _Explorers.Count; i >= 1; i--)
{
// Wrap the Explorer and do something useful with it
WrapExplorer(_Explorers[i]);
}
// get notified for new application windows
_Explorers.NewExplorer += new
Outlook.ExplorersEvents_NewExplorerEventHandler(_Explorers_NewExplorer);
}
/// <summary>
/// Event sink for the NewExplorer event.
/// </summary>
/// <param name=""""Explorer"""" />The new Explorer instance</param />
void _Explorers_NewExplorer(Outlook.Explorer Explorer)
{
WrapExplorer(Explorer);
}
/// <summary>
/// The Explorer is "wrapped" and used in the application.
/// </summary>
/// <param name=""""explorer"""" />The new Explorer instance</param />
void WrapExplorer(Outlook.Explorer explorer)
{
ExplorerWrapper wrappedExplorer = new ExplorerWrapper(explorer);
wrappedExplorer.Closed += new WrapperClosedDelegate(wrappedObject_Closed);
_WrappedObjects[wrappedExplorer.Id] = wrappedExplorer;
}
/// <summary>
/// Event sink for the NewInspector event.
/// </summary>
/// <param name=""""Inspector"""" />The new Inspector instance</param />
void _Inspectors_NewInspector(Outlook.Inspector Inspector)
{
WrapInspector(Inspector);
}
/// <summary>
/// The Inspector is "wrapped" and used in the application.
/// </summary>
/// <param name=""""inspector"""" />The new Inspector instance</param />
void WrapInspector(Outlook.Inspector inspector)
{
InspectorWrapper wrappedInspector = new InspectorWrapper(inspector);
wrappedInspector.Closed += new WrapperClosedDelegate(wrappedObject_Closed);
_WrappedObjects[wrappedInspector.Id] = wrappedInspector;
}
/// <summary>
/// Event sink for the WrappedInstanceClosed event.
/// </summary>
/// <param name=""""id"""" />The unique ID of the closed object</param />
void wrappedObject_Closed(Guid id)
{
_WrappedObjects.Remove(id);
}
/// <summary>
/// Exitpoint for the application, do the cleanup here.
/// </summary>
/// <param name=""""sender"""" /></param />
/// <param name=""""e"""" /></param />
private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
{
_WrappedObjects.Clear();
_Inspectors.NewInspector -= new
Outlook.InspectorsEvents_NewInspectorEventHandler(_Inspectors_NewInspector);
_Inspectors = null;
_Explorers.NewExplorer -= new
Outlook.ExplorersEvents_NewExplorerEventHandler(_Explorers_NewExplorer);
_Explorers = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
#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
}
The abstract /// <summary>
/// Delegate signature to inform the application about closed objects.
/// </summary>
/// <param name=""""id"""" />The unique ID of the closed object.</param />
public delegate void WrapperClosedDelegate(Guid id);
/// <summary>
/// The Wrapperclass itself has a unique ID and a closed event.
/// </summary>
internal abstract class WrapperClass
{
/// <summary>
/// The event occurs when the monitored item has been closed.
/// </summary>
public event WrapperClosedDelegate Closed;
/// <summary>
/// The unique ID of the wrapped object.
/// </summary>
public Guid Id { get; private set; }
protected void OnClosed()
{
if (Closed != null) Closed(Id);
}
/// <summary>
/// The constructor creates a new unique ID.
/// </summary>
public WrapperClass()
{
Id = Guid.NewGuid();
}
}
The /// <summary>
/// The InspectorWrapper used to monitor the state of an Inspector during its lifetime.
/// </summary>
internal class InspectorWrapper : WrapperClass
{
/// <summary>
/// The Outlook Inspector Instance.
/// </summary>
public Outlook.Inspector Inspector { get; private set; }
/// <summary>
/// Construction code.
/// </summary>
/// <param name=""""inspector"""" />The Inspector Object</param />
public InspectorWrapper(Outlook.Inspector inspector)
{
Inspector = inspector;
ConnectEvents();
}
/// <summary>
/// Register the events to get notified of Inspector statechanges within the application.
/// </summary>
void ConnectEvents()
{
((Outlook.InspectorEvents_10_Event)Inspector).Close +=
new Outlook.InspectorEvents_10_CloseEventHandler(InspectorWrapper_Close);
((Outlook.InspectorEvents_10_Event)Inspector).Activate +=
new Outlook.InspectorEvents_10_ActivateEventHandler(InspectorWrapper_Activate);
((Outlook.InspectorEvents_10_Event)Inspector).Deactivate +=
new Outlook.InspectorEvents_10_DeactivateEventHandler(InspectorWrapper_Deactivate);
}
/// <summary>
/// Unregister the events / cleanup.
/// </summary>
void DisconnectEvents()
{
((Outlook.InspectorEvents_10_Event)Inspector).Close -=
new Outlook.InspectorEvents_10_CloseEventHandler(InspectorWrapper_Close);
((Outlook.InspectorEvents_10_Event)Inspector).Activate -=
new Outlook.InspectorEvents_10_ActivateEventHandler(InspectorWrapper_Activate);
((Outlook.InspectorEvents_10_Event)Inspector).Deactivate -=
new Outlook.InspectorEvents_10_DeactivateEventHandler(InspectorWrapper_Deactivate);
}
/// <summary>
/// Event sink for the Close event. Memory Cleanup and inform the application.
/// </summary>
void InspectorWrapper_Close()
{
DisconnectEvents();
Inspector = null;
GC.Collect();
GC.WaitForPendingFinalizers();
// inform the application to release al references.
OnClosed();
}
/// <summary>
/// Event sink for the Activate event
/// </summary>
void InspectorWrapper_Activate()
{
}
/// <summary>
/// Event sink for the deactivate event
/// </summary>
void InspectorWrapper_Deactivate()
{
}
}
The Explorer wrapper class is similar to the Inspector wrapper class. Refer to the sample solution to see additional details. In fact, what you now have is a small framework which could be used to successfully build your VSTO add-ins. Search the built-in Recipient dialogNow that you have arranged to be informed when your Inspector window becomes inactive (because you will receive the Deactivate event), you can search for the Recipient dialog now. You can't do it with .NET managed code - you have to use the good old Windows API for it. This technique is called P/Invoke, and it's the way to access unmanaged API DLL functions, methods, and callbacks from your managed code. The best online resources for information about P/Invoke are the MSDN Windows API documentation and a website called pinvoke.net. Before you can search for the dialog/window, you have to know what to search for. Luckily, with Visual Studio, you get a small tool called Spy++. You can use this tool to search for windows, messages, and even to find the parent and child windows of any window. Start Microsoft Outlook, create a new mail, and select a recipient. When the built-in Recipient dialog is shown, start the Spy++ tool. It's usually located under "C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\Tools folder" on your hard disk. Now, you can use the "Find Window" function and drag the target onto your Recipient dialog. Using Spy++ to get information about the Recipient dialog:
What you will get is some information about the window you selected. You will get information about the window text, the class name, and the handle. The handle is a dynamically assigned unique address of the window in your system. Because it's dynamically assigned, it changes every time the dialog is opened, and therefore isn't helpful here. The caption (title or window text) changes depending on the application context, and doesn't help us here either. How can you identify the window? The answer is not 42 - it's by the class name and by its child windows. Here now is a small challenge for you: Since I coded this sample with a localized version of Outlook, you have to modify the code to suit your needs and locality. All controls on the Recipient dialog are windows too, they are child windows of the Recipient dialog. The next snippet demonstrates how to use some Windows API functions to:
The method to retrieve a list of all child windows and their window text is encapsulated in a managed method to keep all API calls inside of a class. Let's roll - here's the code for the /// <summary>
/// This class encapsulates all P/Invoke unmanaged functions.
/// </summary>
[SuppressUnmanagedCodeSecurity]
internal class WinApiProvider
{
/// <summary>
/// The FindWindow method finds a window by it's classname and caption.
/// </summary>
/// <param name=""""lpClassName"""" />The classname
/// of the window (use Spy++)</param />
/// <param name=""""lpWindowName"""" />The Caption of the window.</param />
/// <returns>Returns a valid window handle or 0.</returns>
[DllImport("user32", CharSet = CharSet.Auto)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
/// <summary>
/// Retrieves the Windowtext of the window given by the handle.
/// </summary>
/// <param name=""""hWnd"""" />The windows handle</param />
/// <param name=""""lpString"""" />A StringBuilder object
/// which receives the window text</param />
/// <param name=""""nMaxCount"""" />The max length
/// of the text to retrieve, usually 260</param />
/// <returns>Returns the length of chars received.</returns>
[DllImport("user32", CharSet = CharSet.Auto)]
public static extern int GetWindowText(IntPtr hWnd,
StringBuilder lpString, int nMaxCount);
/// <summary>
/// Returns a list of windowtext of the given list of window handles..
/// </summary>
/// <param name=""""windowHandles"""" />A list of window handles.</param />
/// <returns>Returns a list with the corresponding
/// window text for each window.</returns />
public static List<string> GetWindowNames(List<IntPtr><intptr /> windowHandles)
{
List<string> windowNameList = new List<string>();
// A Stringbuilder will receive our windownames...
StringBuilder windowName = new StringBuilder(260);
foreach (IntPtr hWnd in windowHandles)
{
int textLen = GetWindowText(hWnd, windowName, 260);
// get the windowtext
windowNameList.Add(windowName.ToString());
}
return windowNameList;
}
/// <summary>
/// Returns a list of all child window handles for the given window handle.
/// </summary>
/// <param name=""""hParentWnd"""" />Handle of the parent window.</param />
/// <returns>A list of all child window handles recursively.</returns>
public static List<IntPtr> EnumChildWindows(IntPtr hParentWnd)
{
// The list will hold all child handles.
List<intptr /> childWindowHandles = new List<intptr />();
// We will allocate an unmanaged handle
// and pass a pointer to the EnumWindow method.
GCHandle hChilds = GCHandle.Alloc(childWindowHandles);
try
{
// Define the callback method.
EnumWindowProc childProc = new EnumWindowProc(EnumWindow);
// Call the unmanaged function to enum all child windows
EnumChildWindows(hParentWnd, childProc, GCHandle.ToIntPtr(hChilds));
}
finally
{
// Free unmanaged resources.
if (hChilds.IsAllocated)
hChilds.Free();
}
return childWindowHandles;
}
/// <summary>
/// A method to enummerate all child windows of the given window handle.
/// </summary>
/// <param name=""""hWnd"""" />The parent window handle.</param />
/// <param name=""""callback"""" />The callback method
/// which is called for each child window.</param />
/// <param name=""""userObject"""" />A pointer
/// to a userdefined object, e.g a list.</param />
[DllImport("user32")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool EnumChildWindows(IntPtr hWnd,
EnumWindowProc callback, IntPtr userObject);
/// <summary>
/// Callback method to be used when enumerating windows.
/// </summary>
/// <param name=""""hChildWindow"""" />Handle of the next window</param />
/// <param name=""""pointer"""" />Pointer to a GCHandle that holds
/// a reference to the dictionary for our windowHandles.</param />
/// <returns>True to continue the enumeration, false to bail</returns>
private static bool EnumWindow(IntPtr hChildWindow, IntPtr pointer)
{
GCHandle hChilds = GCHandle.FromIntPtr(pointer);
((List<intptr />)hChilds.Target).Add(hChildWindow);
return true;
}
/// <summary>
/// Delegate for the EnumChildWindows method
/// </summary>
/// <param name=""""hWnd"""" />Window handle</param />
/// <param name=""""parameter"""" />Caller-defined variable</param />
/// <returns>True to continue enumerating, false to exit the search.</returns>
public delegate bool EnumWindowProc(IntPtr hWnd, IntPtr parameter);
}
The interesting thing here is how to pass a managed generic list to an unmanaged API function by allocating an unmanaged handle to your managed object. Now, you want to use it in your The Event sink for the Inspector Deactivate method will look like this: /// <summary>
/// Event sink for the Deactivate event
/// </summary>
void InspectorWrapper_Deactivate()
{
// check for a Dialog class
IntPtr hBuiltInDialog = WinApiProvider.FindWindow("#32770", "");
if (hBuiltInDialog != IntPtr.Zero)
{
// ok, found one
// let's see what child windows there are
List
Closing the built-in dialogYou have mastered the first exercise - identify the built-in dialog. You have the handle to it, and now you have to close the dialog. When you have a managed .NET form, this is easy - but if not, it's a little trickier. In the Windows API, two methods are documented:
You can't use either of them. Why? When you are receiving this event, the built-in dialog is not initialized completely and it runs in another thread. But, the whole Windows system is based on a message loop where windows exchange messages to interact together. So, you simply send the built-in dialog a Close message. This is the same effect as pressing ESC on the visible window. The window frees all used resources and closes properly. When the window has been closed, your Inspector window will become active again and you will receive an In the next code block, you will see how to close the window and the activate method that is used to display our own dialog: /// <summary>
/// Event sink for the Deactivate event
/// </summary>
void InspectorWrapper_Deactivate()
{
_showOwnDialogOnActivate = false;
// check for a Dialog class
IntPtr hBuiltInDialog = WinApiProvider.FindWindow("#32770", "");
if (hBuiltInDialog != IntPtr.Zero)
{
// ok, found one
// let's see what childwindows are there
List<intptr /> childWindows = WinApiProvider.EnumChildWindows(hBuiltInDialog);
// Let's get a list of captions for the child windows
List<string> childWindowNames = WinApiProvider.GetWindowNames(childWindows);
// now check some criteria to identify the built-in dialog..
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// !!! This part is only valid for German Outlook 2007 Version !!!
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
if (!childWindowNames.Contains("Nur N&ame")) return;
if (!childWindowNames.Contains("&Mehr Spalten")) return;
if (!childWindowNames.Contains("A&dressbuch")) return;
// you can even check more criteria
// OK - we have the built-in Select Names dialog
WinApiProvider.SendMessage(hBuiltInDialog,
WinApiProvider.WM_SYSCOMMAND, WinApiProvider.SC_CLOSE, 0);
// When our Inspector becomes active again, we should display our own dialog
_showOwnDialogOnActivate = true;
}
}
bool _showOwnDialogOnActivate;
/// <summary>
/// Eventsink for the Activate event
/// </summary>
void InspectorWrapper_Activate()
{
if (_showOwnDialogOnActivate)
{
RecipientDialog customDialog = new RecipientDialog();
customDialog.ShowDialog();
}
}
The picture below shows the design of the .NET form used to replace the built-in dialog.
Basically, it has a To realize the filtering, you can use a
Different data sourcesThe implementation of this dialog is whatever you can imagine - it depends on what your requirements are. Just to give you a start, you will use three different data sources to fill up your new Recipients dialog in this sample.
First, you will access the data of the Contacts folder. In the past, you had one of these options to access the Outlook internal data:
New in Outlook 2007 is a Table object which provides fast access to an Outlook folder's contents. You will use it as shown below to get the contents of the personal Contacts folder and populate the custom dialog. The helper methods are in a class called The implementation looks like this: /// <summary>
/// Class with helpermethods for Outlookspecific functionality
/// </summary>
internal class OutlookUtility
{
/// <summary>
/// Returns the Table Object for the given default folder.
/// </summary>
/// <param name=""""defaultFolder"""" />The Default</param />
/// <param name=""""filter"""" />A filter that
/// could be passed to filter items.</param />
/// <returns>Returns the folder Table object.</returns>
public static Outlook.Table GetFolderTable(Outlook.OlDefaultFolders defaultFolder,
string filter)
{
Outlook.MAPIFolder folder =
Globals.ThisAddIn.Application.Session.GetDefaultFolder(defaultFolder);
return GetFolderTable(folder, filter);
}
/// <summary>
/// Returns the Table Object for the passed folder.
/// </summary>
/// <param name=""""defaultFolder"""" />The Default</param />
/// <param name=""""filter"""" />A filter that
/// could be passed to filter items.</param />
/// <returns>Returns the folder Table object.</returns>
public static Outlook.Table GetFolderTable(Outlook.MAPIFolder folder, string filter)
{
return folder.GetTable(filter, Missing.Value);
}
/// <summary>
/// Prepares the Table object for setting what data to retrieve.
/// </summary>
/// <param name=""""table"""" />The Table object</param />
/// <param name=""""columnNames"""" />An arry of columnnames</param />
public static void SetTableColumns(Outlook.Table table, string[] columnNames)
{
table.Columns.RemoveAll();
foreach (string columnName in columnNames)
{
table.Columns.Add(columnName);
}
}
}
Now, take a closer look at the implementation of the .NET form. As mentioned earlier, you will use a backgroundworker to pump the data into your new dialog. Also, you have to connect the dialog to your Inspector's data, so that the recipients that you have selected shows up in the mail somehow and vice versa. You can achieve this by passing the Inspector's /// <summary>
/// The custom Recipient Dialog
/// </summary>
public partial class RecipientDialog : Form
{
/// <summary>
/// Reference to the Outlook Item Object that should be modified here.
/// </summary>
object _item;
/// <summary>
/// Construction code.
/// The Outlook item will be injected here.
/// </summary>
public RecipientDialog(object item)
{
InitializeComponent();
_item = item;
// Read current data from item and set it into the user interface.
ProcessPropertyTags(false);
}
/// <summary>
/// Loop over all Controls.
/// The name of the Outlook property to use is in the Tag of the UserControl.
/// </summary>
/// <param name=""""write"""" />If false,
/// read the value from Item - if true write it back.</param />
private void ProcessPropertyTags(bool write)
{
foreach (Control c in this.Controls)
{
if (!string.IsNullOrEmpty(c.Tag as string))
// do we have a Tag value in the Control ? means bound to the Outlook Item
{
if (write)
{
OutlookUtility.PropertySet(ref _item, (string)c.Tag, c.Text);
}
else
{
c.Text = OutlookUtility.PropertyGet(ref _item,
(string)c.Tag).ToString();
}
}
}
}
private void OKButton_Click(object sender, EventArgs e)
{
// Read all Data and write it back to the Outlook Item
ProcessPropertyTags(true);
DialogResult = DialogResult.OK;
this.Close();
}
private void Cancel_Click(object sender, EventArgs e)
{
// Close without accepting the data change
DialogResult = DialogResult.Cancel;
this.Close();
}
private void Form_FormClosed(object sender, FormClosedEventArgs e)
{
_item = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
Now, you have the dialog connected to your Inspector and you should fill it with data. You want to maintain a responsive application, so the decision is to use a backgroundworker for your application. You start with Outlook Contacts Folder data. The theory says: create a background thread, get the folder table, loop over the data, and add it to your dataset. While looping over the data, show the progressbar. When finished, enable all user-elements. Study the more advanced code of the backgroundworker process: DataView _dvContacts;
/// <summary>
/// Indicates that the background process has been completed.
/// </summary>
bool _outlookLoaderFinished;
/// <summary>
/// This method is executed asynchronously in a separated thread
/// </summary>
/// <param name=""""sender"""" />The backgroundworker instance.</param />
/// <param name=""""e"""" />Parameter object
/// that could be passed at initialization.</param />
private void _outlookContactLoader_DoWork(object sender, DoWorkEventArgs e)
{
// get the folder table object and filter
// only IPM.Contact items (no distributionlist items)
Outlook.Table contactsTable =
OutlookUtility.GetFolderTable(Outlook.OlDefaultFolders.olFolderContacts,
"[MessageClass] = 'IPM.Contact'");
// we're interrested only in some of the columns
OutlookUtility.SetTableColumns(ref contactsTable, new string[]
{ "EntryID", "FirstName", "LastName",
"CompanyName", "User1", "Email1Address" });
// the itemCount is used for the progressbar
int itemCount = contactsTable.GetRowCount();
int count = 0;
// access the table data and add it to our DataSet
while (!contactsTable.EndOfTable && !e.Cancel)
{
count++;
Outlook.Row row = contactsTable.GetNextRow();
string entryId = row[1] as string;
string firstName = row[2] as string;
string lastName = row[3] as string;
string company = row[4] as string;
string customerId = row[5] as string;
string email = row[6] as string;
_dsContacts.ContactTable.AddContactTableRow(entryId, firstName,
lastName, email, company, customerId);
_outlookContactLoader.ReportProgress(((int)count * 100 / itemCount));
}
}
/// <summary>
/// Event sink for the RunWorkerCompleted event.
/// Is called when the backgroundworker has been finnished.
/// </summary>
private void _outlookContactLoader_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
_outlookLoaderFinished = true;
RefreshUI();
}
/// <summary>
/// Eventsink for the ProgressChanged event.
/// </summary>
private void _outlookContactLoader_ProgressChanged(object sender,
ProgressChangedEventArgs e)
{
UpdateProgress(e.ProgressPercentage);
}
private delegate void UpdateProgressDelegate(int progress);
private void UpdateProgress(int progress)
{
if (ProgressBarStatus.InvokeRequired)
{
this.Invoke(new UpdateProgressDelegate(UpdateProgress), progress);
}
ProgressBarStatus.Value = progress;
ProgressBarStatus.Update();
ResultGrid.DataSource = _dvContacts;
}
private void RefreshUI()
{
bool allLoadersfinished = (_outlookLoaderFinished);
ToButton.Enabled = CcButton.Enabled = BccButton.Enabled = allLoadersfinished;
ProgressBarStatus.Visible = !allLoadersfinished;
_dvContacts.RowFilter = GetRowFilterText(SearchTextComboBox.Text);
ResultGrid.DataSource = _dvContacts;
}
private string GetRowFilterText(string searchText)
{
if (string.IsNullOrEmpty(searchText)) searchText = "*";
return "[FirstName] LIKE '*" + searchText +
"*' OR [LastName] LIKE '*" + searchText +
"*' OR [CompanyName] LIKE '*" + searchText +
"*' OR [EmailAddress] LIKE '*" + searchText + "*'";
}
Take a break now. You should have an initial functional add-in now, and the design goals are reached to this point | ||||||||||||||||||||