I created an adjoining Form Region for Outlook appointment items (Using Visual Studio 2019 for Office 2019) and I have inputs on that form from which I capture the values into UserProperties.
Everything works fine until I get to the point where I have added some recipients, sent the meeting request, open it again, remove a recipient, and then try to send an update.
The problem is that on the close event of the form region, I retrieve all my input fields' values, store them in UserProperties, and then fire the item's .Save() method, which throws an exception.
When this happens, my UserPropery values are not saved, but the appointments body and the recipient list does indeed save.
When the appointment item is being deleted, the save method will also throw an exception which is understandable. I wrapped it in a try..catch for that.
An example of my code:
private void AdditionalAppointmentInfo_FormRegionClosed(object sender, System.EventArgs e)
{
if (customItem) //In the FormRegionShowing Event, I check for UserProps to see if this is an item created by this add-in
{
try
{
item.UserProperties["MyUserProperty"].Value = MyTextBox.Text;
item.Save(); //Throws exception here when a recipient gets removed.
}
catch (System.Exception ex){}
}
else
{
try
{
item.UserProperties.Add("IsCustomItem", OlUserPropertyType.olText).Value="1"; //set a property to indicate that this item was created using this add-in
item.UserProperties.Add("MyUserProperty", OlUserPropertyType.olText).Value = MyTextBox.Text;
}
catch (System.Exception){}
}
}
I can understand an exception when the appointment item gets deleted, but not one when you remove a recipient.
So while UserProperties works for my intended purposes, is it even the right thing to be using if you want a FormRegion controls' values to persist? Or is there some other way to store persistent user data without having to manually trigger the save method?
You must use the OutlookItem property to get an Outlook item.
Related
I need to synchronize Outlook contacts to another service. I can subscribe to create, change, and delete events as follows:
Outlook.MAPIFolder folderContacts = this.Application.ActiveExplorer().Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderContacts);
Outlook.Items contacts = folderContacts.Items;
contacts.ItemAdd += Contacts_ItemAdd;
contact.ItemChange += Contacts_ItemChange;
contacts.ItemRemove += Contacts_ItemRemove;
This works perfectly for create and change as I get the item in the event handler:
private void Contacts_ItemAdd(object Item)
{
Outlook.ContactItem contact = (Outlook.ContactItem)Item;
...
}
However, in case of remove event, I don't get the information of the removed item.
private void Contacts_ItemRemove() {
// how to get deleted item or at least it's EntryID?
}
So how do I get the EntryID of the removed item? I use this ID to identify the item in the other service.
You need to maintain the list of items in each folder. And in the ItemRemove event handler you can compare out the list of existing items with yours. I'd recommend reading the following series of articles which gives an example for the NewMailEx event in Outlook (sometimes it is not fired at all, so developers should looks for possible workarounds like that):
Outlook NewMail event unleashed: the challenge (NewMail, NewMailEx, ItemAdd)
Outlook NewMail event: solution options
Outlook NewMail event and Extended MAPI: C# example
Outlook NewMail unleashed: writing a working solution (C# example)
All MAPI based notifications are raised after the action has already occurred. By the time you get the ItemRemove event, the message is already gone.
On the Extended MAPI level (C++ or Delphi only), when an item is deleted, the store provider raises the following fnevTableModified / TABLE_ROW_DELETED notification (you can see it in OutlookSpy (I am its author) if you click IMAPIFolder button and look at the log at the bottom of the GetContentsTable tab). Only PR_INSTANCE_KEY property is available:
ulEventType: fnevTableModified
tab.ulTableEvent: TABLE_ROW_DELETED
tab.propIndex: (PR_INSTANCE_KEY, cb:4, lpb: 0F 3E D3 A4
tab.propPrior: (PR_NULL, null)
tab.row: (cValues : 0
)
You can only make this work by retrieving PR_INSTANCE_KEY for all items in the folder beforehand so that you can map PR_ENTRYID <-> PR_INSTANCE_KEY.
Outlook Object Model does not expose PR_INSTANCE_KEY in the ItemRemove event. If using Redemption is an option (I am also its author), its RDOItems.ItemRemove event passes the instance key as a parameter. PR_INSTANCE_KEY for all items in a folder can be cached beforehand in a single call using RDOItems.MAPITable.ExecSQL method.
I develop an outlook adding using Visual studio 2013 and Addin express v.7.7.4087.
I wanting to handle the ItemSend() and save the item to drafts and close the inspector automatically, We can do everything apart from closing the inspector, because it is not allowed to close the inspector inside the method of ItemSend(), I have use a windows.forms.timer (close the mailitem inside timer) to do this but ends up with an error where it says “RCW content has been separated from …………” error
Are there any alternative ways of doing this?
In redemption (for outlook), is there a way of doing the same thing?
private void adxOutlookAppEvents1_ItemSend(object sender, DXOlItemSendEventArgs e)
{
Outlook.MailItem mailItem = null;
Outlook.Recipients recipients = null;
mailItem = e.Item as Outlook.MailItem;
try
{
// Some code goes here
if (editButtonClicked || swOffline)
{
e.Cancel = true;
if (swOffline)
{
mailItem.Save();
timer.Start();
}
}
}
catch (Exception ex)
{
}
finally
{
}
}
The timer will work fine as long as you keep in the MailItem object referenced. It looks like your mailItem variable is declared on the local level, where it will be garbage collected.
What code do you use for closing the inspector window? Could you please be more specific?
Are there any alternative ways of doing this?
The Close method of the Inspector class works like a charm.
In redemption (for outlook), is there a way of doing the same thing?
It doesn't provide anything for that. The library is based on the Extended MAPI and don't know anything about the Outlook UI.
My C# application needs to work close to Outlook. I've implemented a method that allows to load Outlook if it is not on yet. After this loading method I need to start working with emails, read Inbox folder etc. . . obviously, all these behaviours must be executed when Outlook is on, in particular if the main Outlook windows is ready.
Looking at the OOM I've found the application Startup event, and I think I could use it to be sure the application is ready...but I don't know how to use it.
To share the idea of what I'm trying to do, here there is the code (simplified):
Main:
OutlookProvider p= new OutlookProvider();
p.Connect();
if(p.AppIsOn) {
// TO DO: start working
}
else
throw new Exception("Error; Unable to connect to Outlook.");
OutlookProvider class:
#region Fields
Outlook.Application oApp;
Outlook.MailItem oMail;
bool AppIsOn;
#endregion
OutlookProvider() { AppIsOn= false; }
Connect() {
try {
// try to connect to the possible running Outlook instance
oApp = (Outlook.Application)Marshal.GetActiveObject("Outlook.Application");
AppIsOn= true;
}
catch(Exception exc) {
// Outlook is not running, so I create my own Outlook instance
// here my app is null so an Exception will be thrown
oApp.Startup += new Outlook.ApplicationEvents_11_StartupEventHandler(SetAppIsOn);
oApp= (Outlook.Application)new Outlook.Application();
}
}
void SetAppIsOn() { AppIsOn= true;}
Can I use that event to solve my problem? And if yes how can I implement the Connect() method in order to set my boolean AppIsOn variable?
First of all, you ned to create an instance of the object and only then try to subscribe to the events. You can't set up an event handler when the object is null (not yet initialized). So, the code should look like the following one:
oApp= (Outlook.Application)new Outlook.Application();
oApp.Startup += new Outlook.ApplicationEvents_11_StartupEventHandler(SetAppIsOn);
Anyway, there is no need to handle the Startup event if you automate the host application. Take a look at the C# app automates Outlook (CSAutomateOutlook) sample application which explains how to automate Outlook from C#. The Outlook object model is not asynchronous, so each method or property will take as much time as it needs.
P.S. There is no method to initialize Outlook. The Logon method is only used to log on to a specific profile when Outlook is not already running. If Outlook is not running and you only want to start Outlook with the default profile, do not use the Logon method.
To make sure Outlook is fully initialized, call Namespace.Logon. If Outlook is already running, the call will do nothing.
There is also no reason to call GetActiveObject - Outlook is a singleton, so creating a new object will return the existing object if Outlook is already running.
olApp = new Outlook.Application();
Outlook.Namespace ns = olApp.GetNamespace("MAPI");
ns.Logon();
I had this situation not sure how to best handle this. Input would be appreciated. Imagine I Have such method:
void loaddata()
{
try
{
// EXTRA: I also want to skip below SaveSomething if there was exeption
// last time I called DecryptAndloadXMLdata. This may happen
// if user calls loaddata twice. This is exaclty similar situation
// as app quitting just it happens is user calls loaddata twice
// and during first call there was exception say with DecryptAndloadXMLdata
Savesomething(listOfObjects, xmlPath);//save old data first
xmlPath = newValue;
// some operations
xmlDoc = DecryptAndloadXMLdata(xmlPath);
// some other operations- populate List with data from above XML file
listOfObjects.Add(objectFromXML);
// Here also possibly modify contents of listOfObjects elements
}
catch(Exception ex)
{
xlmPath="";
}
}
Now the thing is when app is quiting I have such feature
to automatically save the List object populated in above
method to a file. Like:
void whenAppisQuitting()
{
Savesomething(listOfObjects, xmlPath);
}
But the problem is. Imagine xmlDoc = loadXMLdata(); throws in above method. What will happen is the List I mentioned won't be populated and when app is quitting empty elements (e.g. empty listOfObjects) will be written to xmlPath - thus damaging my original file because there was unrelated exception say due to encryption in loadXMLData method.
I hope I have made my issue clear. What is the way to deal with such situations? For example what I did you can see I set xmlPath to empty in catch - thus in case of any exception I considered data was not loaded successfully - hence now on application exit I can be calm because nothing will be written to file because its xmlPath ="". Is it reasonable way to solve this issue?
Getting bit confused because this kind of issues now raise error handling to different level - I need to account for all possible kind of failures?
What is the way to deal with such situations?
I would set a flag indicating there was an error when parsing. Setting the path to string.Empty can lead to confusion (IMO). Perhaps an empty string could be a possible value bring passed to your method.
catch(Exception ex)
{
// Log
IsParsingSuccessful = false;
}
And look upon that flag when you want to write:
void AppIsQuitting()
{
if (IsParsingSuccessful)
{
SaveSomething(listOfObjects, xmlPath);
}
}
As a general guidance, you probably should never overwrite files without explicit intent from the user.
As for your error handling, use a simple flag / boolean to indicate valid behavior. Only set that flag to true after everything is processed and only if that flag is true, save the contents to the file.
I'm currently developing an Add-in for MS Outlook 2010 using VS 2013. I am using the NewMailEx event handler to give me the EntryID of new email arriving.
Getting the EntryIDCollection is no issue (and I make sure it's a single EntryID before moving further), but I am unable to use the given ID to find the actual email object (using GetItemFromID()) so that I can access the new email's body. Instead, I just end up with an empty MailItem object after GetItemFromID() is called.
public static Outlook.NameSpace olNameSpace;
...
//EVENT: Item Received (new email arrival)
private static void outLookApp_NewMailEx(string EntryIDCollection)
{
try
{
//THIS part is failing \/ and returning nothing
object item = olNameSpace.GetItemFromID(EntryIDCollection, Type.Missing);
Outlook.MailItem mailItem = item as Outlook.MailItem;
if (mailItem != null)
{
//access email object
}
}
catch (Exception e)
{
MessageBox.Show("Receive mail exception: " + e);
}
}
Do I need to specify the folder for GetItemFromID() even though it's optional?
I'm open to other processes for accessing new email arrivals, but I was just using NewMailEx for the time being to have a simple running example.
Thanks everybody.
Are you sure it is GetItemFromID that returns null? If there is a problem, it will raise an exception rather than return null. Most likely it is the next line (Outlook.MailItem mailItem = item as Outlook.MailItem) that returns null when you cast the returned object to MailItem.
Make sure you are not dealing with ReportItem or MeetingItem objects.
The problem here is the object olNameSpace that is not initialized. Try this:
private void outLookApp_NewMailEx(string EntryIDCollection)
{
olNameSpace = this.Application.GetNamespace("MAPI");
try
{
//THIS part is failing \/ and returning nothing
object item = olNameSpace.GetItemFromID(EntryIDCollection, Type.Missing);
Outlook.MailItem mailItem = item as Outlook.MailItem;
if (mailItem != null)
{
mailItem.Display();
}
}
catch (Exception e)
{
MessageBox.Show("" + e);
}
}
Witty,
What is the actual value passed to the GetItemFromID method?
Do I need to specify the folder for GetItemFromID() even though it's optional?
No, there is no need to specify the second paramter. It is optional.
I'd suggest reading the following series of articles related to handling incoming emails in Outlook:
Handling NewMail, NewMailEx and ItemAdd Outlook events in .NET
Outlook NewMail event unleashed: the challenge (NewMail, NewMailEx, ItemAdd)
Outlook NewMail event: solution options
Outlook NewMail event and Extended MAPI: C# example
Outlook NewMail unleashed: writing a working solution (C# example)
Thanks all for your help.
Ultimately, I couldn't get the GetItemFromID method to work consistently in conjunction with NewMailEx.
However, I do recommend implementing Eugene's recommendation here:
https://www.add-in-express.com/creating-addins-blog/2011/11/10/outlook-newmail-custom-solution/
This consistent scanning of folders works quite well (with 100% dependability in my experimentation). However, if you do experience any latency in using this with Outlook, I recommend increasing the sync delay time and possibly using event handlers such as Folder.Items.ItemAdd and Folders.FolderAdd/FolderChange/FolderRemove to check in between syncs.
My add-in needs to ensure quick processing of new email (for security scanning of links), so it cannot just solely wait in between syncs (in case a link is not scanned before the user reached it), which is why I recommend those other event handlers.
Thanks again,
~B-Witty