Welcome to X4U
Minimize 

Hello visitor.

This is the personal homepage of Helmut Obertanner.
I'm a computer freak and electronic enthusiast and I live in Munich in Germany - the hometown of the famous Oktoberfest.
Why the name X4U? In my case it's a synonym for everything is possible.
I'm an experienced full time C# developer and doing so since .Net Framework 1.0.
As technical enthusiast I started Office development using .Net technologies.
As I searched answers and found help in the Internet I decided to share my knowledge in newsgroups and forums too. For participation, helping other developers, writing how-to's and articles and doing consulting and trainings I received the Microsoft MVP Award for the 3rd time.

As Outlook C# specialist I founded the website Outlooksharp.de
Beside software development, I'm building electronic devices for customers.
Prototypes with SMD PCBs and complete electronic devices with USB connections are my typical portfolio.
You got an Idea and have no plan how to put it to work - give me a call and I tell you if I can help.
A complete solution starts from defining the requirements, design the schematic and PCB, build a functional prototype, program the firmware, program the windows software, design and build a housing prototype.
Usually I work with very cheap PIC 18F Microcontrollers, these multifunctional devices are programmed in C language. 

I'm located in Germany with satisfied customers around the globe.
Enjoy the content of my site and feel free to give me your feedback.
You're welcome.
X4U Blog
 
Dec29

Written by:Helmut Obertanner
12/29/2009 8:54 PM 

Vor kurzem musste ich einem Kunden helfen Geschäftsregeln für Outlook Objekte(Kontakte) zu implementieren. Glücklicherweise habe ich dafür schon mal ein Programmierbeispiel auf der Webseite Outlookcode.com veröffentlicht. Dieses Beispiel ist aber schon ziemlich alt und als Vorlage diente ein sogenanntes "Shared" COM Add-in. Deshalb habe ich mich dazu entschlossen ein aktualisiertes Beispiel hier auf dem Blog zu veröffentlichen und VSTO-Technologie dazu zu benutzen.

Inspector Wrapper

Aller Anfang bildet wie immer ein Inspector Wrapper als Vorlage. Vor kurzem habe ich darüber geschrieben – hier kann man das Nachlesen: http://www.outlooksharp.de/Home/tabid/36/EntryId/45/Outlook-InspectorWrapper-erklart.aspx.

Die Grundlage der meisten Outlook-Applikationen ist das korrekte bearbeiten der verschiedenen Outlook Datenobjekte(Item) und deren Fenster(Inspector). Wenn in Outlook ein Item erstellt oder bearbeitet und in einem Formular angezeigt wird, wird dies von dem dazugehörigen Inspector überwacht. Der vorgestellte inspector Wrapper code macht es einfach die verschiedenen Datenobjekte und Formulare sauber in der Anwendung zu verarbeiten. Das aktuelle Datenobjekt, welches sich hinter einem Inspector verbirgt, ist über dessen CurrentItem-Eigenschaft erreichbar. Als Szenario in diesem Beispiel sollen für Kontakte Geschäftsregeln erzwungen werden.
Ein Outlook ContactItem stellt verschiedene Benachrichtigungen zur Verfügung welche dazu benutzt werden können, um ein kleines Framework Sicherstellung von Geschäftsregeln zu programmieren. Es kann sowohl das Speichern eines Objekts als auch das Schliessen eines Formulars überwacht und abgebrochen warden. Dazu muss man die Nachrichten write und close der entsprechenden Objekte überwachen. Die write(schreiben) und close(schliessen) Benachrichtigungen eines ContactItems verfügen über eine Cancel(Abbrechen) Eigenschaft welche als Referenztyp der Methode übergeben wird. Wird die Eigenschaft Cancel auf true(wahr) gesetzt, wird der entsprechende Vorgang abgebrochen – das Speichern der Daten oder das Schliessen des Formulars wird abgebrochen. Zunächst erstellen wir ein neues Outlook Add-in.

Abbildung 1: Erstellen eines Outlook Add-ins

Nach der Fertigstellung des Assistenten erhält man den üblichen VSTO Anwendungscode. Hier wird sogleich der InspectorWrapper implementiert welcher dazu dient alle individuellen Formularfenster zu verarbeiten. Die Basisklasse dazu nennt sich InspectorWrapper.cs und implementiert alle Nachrichten des Inspector Objekts. Möchte man Code für ein Kontakt-Objekt implementieren leitet man dazu eine Klasse von diesem InspectorWrapper ab und implementiert spezifischen Code für Kontakt-Objekte(ContactItem). In dieser Klasse lassen sich dann die Nachrichten für die Objekte monitoren und die Geschäftslogik implementieren.
Hier sehen Sie den Code für die InspectorWrapper Basisklasse:

///<summary>

/// Eventhandler used to correctly clean up resources

///</summary>

///<param name="id">The unique id of the Inspector instance</param>

internaldelegatevoidInspectorWrapperClosedEventHandler(Guid id);

 

///<summary>

/// The base class for all InspectorWrappers

///</summary>

internalabstractclassInspectorWrapper {

 

///<summary>

/// Event notifier for the InspectorWrapper.Closed event.

/// Is raised when an Inspector has been closed.

///</summary>

publiceventInspectorWrapperClosedEventHandler Closed;

 

///<summary>

/// The unique Id the identifies the Inspector Window

///</summary>

publicGuid Id { get; privateset; }

 

///<summary>

/// The Outlook Inspector Instance

///</summary>

public Outlook.Inspector Inspector { get; privateset; }

 

///<summary>

/// .ctor

///</summary>

///<param name="inspector">The Outlook Inspector instance that should be handled</param>

public InspectorWrapper(Outlook.Inspector inspector) {

Id = Guid.NewGuid();

Inspector = inspector;

// register for Inspector events here

((Outlook.InspectorEvents_10_Event)Inspector).Close += new Outlook.InspectorEvents_10_CloseEventHandler(Inspector_Close);

((Outlook.InspectorEvents_10_Event)Inspector).Activate += new Outlook.InspectorEvents_10_ActivateEventHandler(Activate);

((Outlook.InspectorEvents_10_Event)Inspector).Deactivate += new Outlook.InspectorEvents_10_DeactivateEventHandler(Deactivate);

((Outlook.InspectorEvents_10_Event)Inspector).BeforeMaximize += new Outlook.InspectorEvents_10_BeforeMaximizeEventHandler(BeforeMaximize);

((Outlook.InspectorEvents_10_Event)Inspector).BeforeMinimize += new Outlook.InspectorEvents_10_BeforeMinimizeEventHandler(BeforeMinimize);

((Outlook.InspectorEvents_10_Event)Inspector).BeforeMove += new Outlook.InspectorEvents_10_BeforeMoveEventHandler(BeforeMove);

((Outlook.InspectorEvents_10_Event)Inspector).BeforeSize += new Outlook.InspectorEvents_10_BeforeSizeEventHandler(BeforeSize);

// Only Outlook 2007((Outlook.InspectorEvents_10_Event)Inspector).PageChange += new Outlook.InspectorEvents_10_PageChangeEventHandler(PageChange);

 

// Initialize is called to give the derived Wrappers a chance to do initialization

Initialize();

}

 

///<summary>

/// Eventhandler for the Inspector close event

///</summary>

privatevoid Inspector_Close() {

// call the Close Method - the derived classes can implement cleanup code

// by overriding the Close method

Close();

// unregister Inspector events

((Outlook.InspectorEvents_10_Event)Inspector).Close -= new Outlook.InspectorEvents_10_CloseEventHandler(Inspector_Close);

((Outlook.InspectorEvents_10_Event)Inspector).Activate -= new Outlook.InspectorEvents_10_ActivateEventHandler(Activate);

((Outlook.InspectorEvents_10_Event)Inspector).Deactivate -= new Outlook.InspectorEvents_10_DeactivateEventHandler(Deactivate);

((Outlook.InspectorEvents_10_Event)Inspector).BeforeMaximize -= new Outlook.InspectorEvents_10_BeforeMaximizeEventHandler(BeforeMaximize);

((Outlook.InspectorEvents_10_Event)Inspector).BeforeMinimize -= new Outlook.InspectorEvents_10_BeforeMinimizeEventHandler(BeforeMinimize);

((Outlook.InspectorEvents_10_Event)Inspector).BeforeMove -= new Outlook.InspectorEvents_10_BeforeMoveEventHandler(BeforeMove);

((Outlook.InspectorEvents_10_Event)Inspector).BeforeSize -= new Outlook.InspectorEvents_10_BeforeSizeEventHandler(BeforeSize);

// Only Outlook 2007((Outlook.InspectorEvents_10_Event)Inspector).PageChange -= new Outlook.InspectorEvents_10_PageChangeEventHandler(PageChange);

// clean up resources and do a GC.Collect();

Inspector = null;

GC.Collect();

GC.WaitForPendingFinalizers();

// raise the Close event.

if (Closed != null) Closed(Id);

}

 

///<summary>

/// Method is called after the internal initialization of the Wrapper

///</summary>

protectedvirtualvoid Initialize() { }

 

///<summary>

/// Method gets called when another Page of the Inspector has been selected

///</summary>

///<param name="ActivePageName">The active page name by reference</param>

protectedvirtualvoid PageChange(refstring ActivePageName) { }

 

///<summary>

/// Method gets called before the Inspector is resized

///</summary>

///<param name="Cancel">To prevent resizing set Cancel to true</param>

protectedvirtualvoid BeforeSize(refbool Cancel) { }

 

///<summary>

/// Method gets called before the Inspector is moved around

///</summary>

///<param name="Cancel">To prevent moving set Cancel to true</param>

protectedvirtualvoid BeforeMove(refbool Cancel) { }

 

///<summary>

/// Method gets called before the Inspector is minimized

///</summary>

///<param name="Cancel">To prevent minimizing set Cancel to true</param>

protectedvirtualvoid BeforeMinimize(refbool Cancel) { }

 

///<summary>

/// Method gets called before the Inspector is maximized

///</summary>

///<param name="Cancel">To prevent maximizing set Cancel to true</param>

protectedvirtualvoid BeforeMaximize(refbool Cancel) { }

 

///<summary>

/// Method gets called when the Inspector is deactivated

///</summary>

protectedvirtualvoid Deactivate() { }

 

///<summary>

/// Method gets called when the Inspector is activated

///</summary>

protectedvirtualvoid Activate() { }

 

///<summary>

/// Derived classes can do a cleanup by overriding this method.

///</summary>

protectedvirtualvoid Close() { }

 

///<summary>

/// This Fabric method returns a specific InspectorWrapper or null if not handled.

///</summary>

///<param name="inspector">The Outlook Inspector instance</param>

///<returns>Returns the specific Wrapper or null</returns>

publicstaticInspectorWrapper GetWrapperFor(Outlook.Inspector inspector) {

 

// retrieve the message class using late binding

string messageClass = (string)inspector.CurrentItem.GetType().InvokeMember("MessageClass", BindingFlags.GetProperty, null, inspector.CurrentItem, null);

 

// depending on a messageclass you can instantiate different Wrappers

// explicitely for a given MessageClass

// using a switch statement

switch (messageClass) {

case"IPM.Contact":

returnnewContactItemWrapper(inspector);

case"IPM.Journal":

returnnewContactItemWrapper(inspector);

case"IPM.Note":

returnnewMailItemWrapper(inspector);

case"IPM.Post":

returnnewPostItemWrapper(inspector);

case"IPM.Task":

returnnewTaskItemWrapper(inspector);

}

 

// or check if the messageclass begins with a specific fragment

//if (messageClass.StartsWith ("IPM.Contact.XXXX")){

// return new CustomItemWrapper(inspector);

//}

 

// or check the interface type of the Item

if (inspector.CurrentItem is Outlook.AppointmentItem) {

returnnewAppointmentItemWrapper(inspector);

}

 

// or check the interface type of the Item

if (inspector.CurrentItem is Outlook.ContactItem) {

returnnewContactItemWrapper(inspector);

}

 

// no wrapper found

returnnull;

}

}

 

Als nächstes sieht man die Implementierung der ContactItemWrapper-Klasse. Sie ist von der InspectorWrapper-Klasse abgeleitet und ist ab jetzt für alle Kontakt-Objekte in Outlook zuständig. Bis jetzt nichts neues. Wie lassen sich die Geschäftsregeln implementieren? Studiert man den Code etwas genauer kann man sehen, dass in der Initialize-Methode die Benachrichtigungen des Kontakts abgehöhrt werden - und zwar die open, write and close Nachrichten. In C# benötigt die close-Nachricht leider eine Spezialbehandlung. Das Problem ist, dass das ContactItem bereits eine Close() Methode hat und der Generator für die sog. Primary Interop Assemblies nicht zwischen der Close Nachricht und der Close Methode unterscheiden kann. Deshalb muss man hier als Syntax speziell das ItemEvents_10_Event Interface verwenden. Sobald die Nachrichtenregistrierung abgeschlossen ist, wird eine Methode namens SetupBusinessRules aufgerufen. Dies wird aber nochmals im Detail erklärt. Zunächst hier mal der Beispielcode der Klasse ContactItemWrapper.

internalclassContactItemWrapper : InspectorWrapper {

 

///<summary>

/// .ctor

///</summary>

///<param name="inspector">The Outlook Inspector instance that should be handled</param>

public ContactItemWrapper(Outlook.Inspector inspector)

: base(inspector) {

}

 

///<summary>

/// The Object instance behind the Inspector (CurrentItem)

///</summary>

public Outlook.ContactItem Item { get; privateset; }

 

///<summary>

/// Method is called when the Wrapper has been initialized

///</summary>

protectedoverridevoid Initialize() {

// Get the Item of the current Inspector

Item = (Outlook.ContactItem)Inspector.CurrentItem;

 

// Register for the Item events

Item.Open += new Outlook.ItemEvents_10_OpenEventHandler(Item_Open);

Item.Write += new Outlook.ItemEvents_10_WriteEventHandler(Item_Write);

((Outlook.ItemEvents_10_Event)Item).Close += new Microsoft.Office.Interop.Outlook.ItemEvents_10_CloseEventHandler(Item_Close);

 

 

SetupBusinessRules();

}

 

///<summary>

/// This Method is called when the Item is going to be closed

///</summary>

///<param name="Cancel"></param>

void Item_Close(refbool Cancel) {

if (!Item.Saved) {

Cancel = !Validate();

}

}

 

///<summary>

/// This Method is called when the Item is saved.

///</summary>

///<param name="Cancel">When set to true, the save operation is cancelled</param>

void Item_Write(refbool Cancel) {

if (!Item.Saved) {

Cancel = !Validate();

}

}

 

///<summary>

/// This Method is called when the Item is visible and the UI is initialized.

///</summary>

///<param name="Cancel">When you set this property to true, the Inspector is closed.</param>

void Item_Open(refbool Cancel) {

//TODO: Implement something

}

 

 

///<summary>

/// The Close Method is called when the Inspector has been closed.

/// Do your cleanup tasks here.

/// The UI is gone, can't access it here.

///</summary>

protectedoverridevoid Close() {

// unregister events

Item.Write -= new Outlook.ItemEvents_10_WriteEventHandler(Item_Write);

Item.Open -= new Outlook.ItemEvents_10_OpenEventHandler(Item_Open);

((Outlook.ItemEvents_10_Event)Item).Close -= new Microsoft.Office.Interop.Outlook.ItemEvents_10_CloseEventHandler(Item_Close);

 

_BusinessRules.Clear();

_BusinessRules = null;

 

// required, just stting to NULL may keep a reference in memory of the Garbage Collector.

Item = null;

GC.Collect();

GC.WaitForPendingFinalizers();

}

 

 

#region business rule check methods

 

List<X4UBusinessRule> _BusinessRules = newList<X4UBusinessRule>();

 

privatevoid SetupBusinessRules() {

_BusinessRules.Add(newX4UBusinessRule("LastNameUppercase", "The Office Location is required and must be Uppercase.", this.BusinessRuleCheck_OfficeLocationRequiredAndUppercase));

_BusinessRules.Add(newX4UBusinessRule("CompanyName3Letters", "The Company Name must have a minimum length of 3.", this.BusinessRuleCheck_CompanyNameMinimum3Letters));

}

 

publicbool Validate() {

// if every business rule is valid we return true

if (_BusinessRules.TrueForAll(X4UBusinessRule.IsRuleValid)) returntrue;

 

// no ?

// show a warning to the user and

StringBuilder message = newStringBuilder(500);

message.AppendLine("You can't save this Item, because the following requirements are not met:");

foreach (X4UBusinessRule rule in _BusinessRules) {

if (!rule.IsValid()) message.AppendLine(rule.Description);

}

 

// display a message to the user what's wrong.

MessageBox.Show (newOutlookWin32Window(Inspector), message.ToString());

 

returnfalse;

}

 

publicbool BusinessRuleCheck_OfficeLocationRequiredAndUppercase() {

return (!string.IsNullOrEmpty (Item.OfficeLocation) && Item.OfficeLocation == Item.OfficeLocation.ToUpper());

}

 

publicbool BusinessRuleCheck_CompanyNameMinimum3Letters() {

return (Item.CompanyName != null && Item.CompanyName.Length > 2);

}

 

#endregion

 

}

 

Definieren von Geschäftsregeln

Zunächst wird eine KLasse definiert, welche eine Geschäftsregel beschreiben soll. Sie hat als Eigenschaften einen Namen und eine Beschreibung welche später dem Benutzer angezeigt werden soll, falls die Regel nicht eingehalten wird. Außerdem hat sie einen Zeiger auf eine Methode, welche die eigentliche Überprüfung der Regel durchführt. Die Signatur solch einer Methode wird dazu in einem Delegaten definiert.

///<summary>

/// Delegate declares the Method signature that checks the BusinessRule and returns true or false

///</summary>

///<returns>True if the rule is ok</returns>

publicdelegateboolBusinessRuleCheckMethod();

 

Im Konstruktor der Geschäftsregel werden die Eigenschaften und der Funktionszeiger übergeben. Es gibt noch eine weitere Statische Methode(IsRuleValid), welche von einer Liste als Prüfmethode aufgerufen werden kann, dazu später mehr Infos. Hier zunächst die Klasse für die Geschäftsregel.

///<summary>

/// This class defines a business rule

///</summary>

publicclassX4UBusinessRule {

 

///<summary>

/// The Name of the business rule

///</summary>

publicstring Name { get; privateset; }

 

///<summary>

/// A description displayed to the user if the rule is not met

///</summary>

publicstring Description { get; privateset; }

 

///<summary>

/// A function pointer that points to the method that checks if the business rule is valid

///</summary>

publicBusinessRuleCheckMethod IsValid { get; privateset; }

 

///<summary>

/// .ctor

///</summary>

///<param name="name">The name</param>

///<param name="description">The description</param>

///<param name="ruleValidator">Function pointer to the validation method</param>

public X4UBusinessRule(string name, string description, BusinessRuleCheckMethod ruleValidator) {

IsValid = ruleValidator;

Name = name;

Description = description;

}

 

///<summary>

/// This method executes the method that the rule points to and returns the result

///</summary>

///<param name="rule">The businesrule to check</param>

///<returns>True if the rule is valid</returns>

publicstaticbool IsRuleValid(X4UBusinessRule rule) {

return rule.IsValid();

}

}

Als nächstes sehen wir uns an wie die Geschäftsregeln in der Klasse ContactItemWrapper initialisiert werden. Nur zur Demonstration habe ich zunächst zwei einfache Regeln definiert und ausprogrammiert. Und so sehen diese aus:

publicbool BusinessRuleCheck_OfficeLocationRequiredAndUppercase() {

return (!string.IsNullOrEmpty (Item.OfficeLocation) && Item.OfficeLocation == Item.OfficeLocation.ToUpper());

}

 

publicbool BusinessRuleCheck_CompanyNameMinimum3Letters() {

return (Item.CompanyName != null && Item.CompanyName.Length > 2);

}

 

Die erste Methode überprüft die OfficeLocation-Eigenschaft des Kontakts. Sie darf nicht leer sein und darf nur aus Großbuchstaben bestehen. Die zweite Funktion überprüft die Eigenschaft CompanyName – diese muss mindestens drei Zeichen enthalten. Man kann verschiedenste Regeln implementieren, mann muss nur darauf achten dass die jeweilige Methode true zurückliefert wenn die Regel erfolgreich überprüft ist.

Validierung

Wie und wann werden diese Regeln überprüft? Dafür wird eine Validate-Methode bereitgestellt welche immer in den write und close Benachrichtigungen aufgerufen wird. Die Regeln werden in einer Liste gespeichert, dadurch können alle Regeln mit einem Methodenaufruf (TrueForAll) überprüft werden. Ist nur eine Regel nicht erfolgreich validiert, wird dem Benutzer eine Nachricht angezeigt mit dem entsprechenden Hinweis auf die Ursache der Meldung. Hier ist die Validate-Methode.

publicbool Validate() {

// if every business rule is valid we return true

if (_BusinessRules.TrueForAll(X4UBusinessRule.IsRuleValid)) returntrue;

 

// no ?

// show a warning to the user and

StringBuilder message = newStringBuilder(500);

message.AppendLine("You can't save this Item, because the following requirements are not met:");

foreach (X4UBusinessRule rule in _BusinessRules) {

if (!rule.IsValid()) message.AppendLine(rule.Description);

}

 

// display a message to the user what's wrong.

MessageBox.Show (newOutlookWin32Window(Inspector), message.ToString());

 

returnfalse;

}

 

Das ist sehr einfach zu implementieren. Die Validate-Methode wird in der Item.Write und in der Item.Close Benachrichtigung aufgerufen. In diesen Methoden wird der Saved-Status des Kontaktes überprüft. Wenn der Kontakt nicht modifiziert wurde ist eine Regelüberprüfung nicht notwendig. Liefert die Validate-Methode false, wird die write- oder close Operation abgebrochen. Als nächstes kann man die Methoden der Benachrichtigungen sehen:

///<summary>

/// This Method is called when the Item is going to be closed

///</summary>

///<param name="Cancel"></param>

void Item_Close(refbool Cancel) {

if (!Item.Saved) {

Cancel = !Validate();

}

}

 

///<summary>

/// This Method is called when the Item is saved.

///</summary>

///<param name="Cancel">When set to true, the save operation is cancelled</param>

void Item_Write(refbool Cancel) {

if (!Item.Saved) {

Cancel = !Validate();

}

}

 

Wie werden die Geschäftsregeln mit den Funktionszeigern initialisiert? Dies wird mit einer einzigen Codezeile erreicht.
Einfach eine neue Instanz einer BusinessRule-Klasse erzeugen. Dem Konstruktor wird der Name, die Beschreibung und die Methode zum Überprüfen als Parameter übergeben.

List<X4UBusinessRule> _BusinessRules = newList<X4UBusinessRule>();

 

privatevoid SetupBusinessRules() {

_BusinessRules.Add(newX4UBusinessRule("OfficeLocationRequiredAndUppercase", "The Office Location is required and must be uppercase.", this.BusinessRuleCheck_OfficeLocationRequiredAndUppercase));

_BusinessRules.Add(newX4UBusinessRule("CompanyName3Letters", "The Company Name must have a minimum length of 3.", this.BusinessRuleCheck_CompanyNameMinimum3Letters));

}

Sobald ein Kontakt erzeugt oder bearbeitet - und dieser gespeichert oder geschlossen wird, werden die Geschäftsregeln überprüft. Wird dabei eine Misachtung festgestellt wird diese, wie im Bild zu sehen, dem Benutzer angezeigt.

Abbildung 2: Ein hinweis zeigt dem Benutzer welche Regeln misachtet wurden

Beispielcode

Der Beispielcode zu diesem Blog kann hier heruntergeladen werden:
Visual Studio 2010

Referenzen

Outlook Inspector Object: http://msdn.microsoft.com/en-us/library/bb176679.aspx
Outlook ContactItem Object: http://msdn.microsoft.com/en-us/library/bb176651.aspx
InspectorWrapper: http://www.outlooksharp.de/Home/tabid/36/EntryId/45/Outlook-InspectorWrapper-erklart.aspx

Tags:

Your name:
Your email:
(Optional) Email used only to show Gravatar.
Your website:
Title:
Comment:
Security Code
Enter the code shown above in the box below
Add Comment  Cancel 
X4U Blog
 
Home  |  Hardware  |  Software  |  Services  |  Resources  |  Impressum