Globals.ThisAddIn.Application.ActiveInspector == null although a valid Outlook.AppointmentItem is displayed why? - vsto

i am developing an outlook plugin using VSTO.
in the method:
this.Load += new Microsoft.Office.Tools.Ribbon.RibbonUIEventHandler(this.MyApp_Load);
In MyApp_Load of my Ribbon I use the following code:
if (Globals.ThisAddIn.isLoggedIn())
{
btnMyApp.Visible= true;
Outlook.Inspector inspector =
Globals.ThisAddIn.Application.ActiveInspector();
if (inspector != null && inspector.CurrentItem != null)
{
When I open up an AppointmentItem in Outlook I can debug the code above. Unfortunately the ActiveInspector() == null although a valid AppointmentItem is being displayed.
Why?

That method is called before the inspector is displayed. You must use RibbonUI passed as an argument to your onLoad callback and cast RibbonUI.Context to the Inspector interface.

Related

Using mailitem.PrintOut() to print a single page?

I'm working on a simple Outlook 2016/2019 VSTO plugin.
When an email is selected and a ribbon button is pressed, it needs to print just the first page of the email to the default printer. mailitem.PrintOut(); works, but will print the whole email. Is there a way to specify the first page only?
var m = e.Control.Context as Inspector;
var mailitem = m.CurrentItem as MailItem;
if (mailitem != null)
{
mailitem.PrintOut();
}
Update: See my answer for the code I used to get this working.
The Outlook object model doesn't provide any property or method for that. You need to parse the message body on your own and use .net mechanisms for printing this piece on your own.
Note, you may try using the Word object model for printing the message bodies (a specific range of pages). The Document.PrintOut method prints all or part of the specified document. Optional parameters allow specifying the page range.
The Outlook object model provides three main ways for working with item bodies:
Body - a string representing the clear-text body of the Outlook item.
HTMLBody - a string representing the HTML body of the specified item.
Word editor - the Microsoft Word Document Object Model of the message being displayed. The WordEditor property of the Inspector class returns an instance of the Document class from the Word object model which you can use to deal with the message body.
You can read more about all these ways in the Chapter 17: Working with Item Bodies.
As #Eugene said, there's no way to specify a single page using mailItem.PrintOut.
I've finally managed to find a way to do this. I save the document as a .doc file in the temp directory, then using Microsoft.Office.Interop.Word to setup the page margins / size and then send the current page to the printer. Hopefully this helps someone as I couldn't find any working examples for c#!
private void btnPrintOnePage_Click(object sender, RibbonControlEventArgs e)
{
string randFile = Path.GetTempPath() + "POP_" + RandomString(35) + ".doc";
var m = e.Control.Context as Inspector;
var mailitem = m.CurrentItem as MailItem;
if (mailitem != null)
{
mailitem.SaveAs(randFile, OlSaveAsType.olDoc);
Word.Application ap = new Word.Application();
Word.Document document = ap.Documents.Open(randFile);
document.PageSetup.PaperSize = Word.WdPaperSize.wdPaperA4;
document.PageSetup.TopMargin = 25;
document.PageSetup.RightMargin = 25;
document.PageSetup.BottomMargin = 25;
document.PageSetup.LeftMargin = 25;
Word.WdPrintOutRange printRange = Word.WdPrintOutRange.wdPrintCurrentPage;
document.PrintOut(false,null,printRange);
document.Close(false, false, false);
File.Delete(randFile);
}
}
public static string RandomString(int length)
{
Random random = new Random();
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[random.Next(s.Length)]).ToArray());
}

VSTO Outlook Plugin: Cannot get AppointmentItem in Item_Change event when recurring appointment is dragged and dropped by user

I want to catch the change event on an AppointmentItem. I use Outlook 2017 for tests.
To achieve I use:
I attached the events like this:
public void AttachEvents()
{
_CalendarItems.ItemAdd += Item_Add;
_CalendarItems.ItemChange += Item_Change;
_DeletedItems.ItemAdd += Item_Delete_Add;
The Item_Change method looks like this:
public void Item_Change(Object item)
{
if (item != null && item is Outlook.AppointmentItem)
{
Outlook.AppointmentItem myAppointment = item as Outlook.AppointmentItem;
To test the code I created a recurring appointment series. I double-clicked on appointment in the calendar and entered some title and body and saved.
Now I started my code and inspected the item.
Unfortunately, item points to the series and NOT to the individual appointment when item changed is initiated.
How can I retrieve the actual AppointmentItem when Item_Changed is initiated?
Related Stackoverflow Posting: Outlook Addin: Moving Appointment in Calendar does not reflect new date/time in AppointmentItem (catch Calendar.ItemChange) But still there is no solution to this
More on this topic:
https://www.add-in-express.com/forum/read.php?FID=5&TID=15384
https://social.msdn.microsoft.com/Forums/sqlserver/en-US/4ec55891-fb64-408f-b1cf-4bf05765b866/outlook-get-original-time-of-recurring-exception-item-that-is-opened-with-drag-drop?forum=vsto
Exceptions are not actual appointments - they are stored as embedded message attachments on the master appointments. You get the master appointment, and you would need to access its exceptions to see what changed.
There is a tricky solution to that.
Assumption: The user uses the calendar view to change the item. As the event is thrown by the calendar view this should be true in all cases:
_CalendarItems = calendarFolder.Items;
_CalendarItems.ItemChange += Item_Change;
[...]
now we can use the CalendarView to calculate the selected Startdate and compare it to all Exceptions stored in the RecurrencePattern ...
if (myAppointment.IsRecurring)
{
// in case of recurring appointments at this point we always get
// only a reference to the series master NOT the occurrence
// Assumption: The user clicked on the AppointmentItem in the calendar view
// So we can calculate the selected Start Time from this selection range
// then compare this against all Exceptions in the OccurrencePattern of the recurring pattern
// if we find one AppointmentItem in the Exceptions which has the same DateTime then we found the correct one.
//
Outlook.Application application = new Outlook.Application();
Outlook.Explorer explorer = application.ActiveExplorer();
Outlook.Folder folder = explorer.CurrentFolder as Outlook.Folder;
Outlook.View view = explorer.CurrentView as Outlook.View;
// get the current calendar view
if (view.ViewType == Outlook.OlViewType.olCalendarView)
{
Outlook.CalendarView calView = view as Outlook.CalendarView;
Outlook.RecurrencePattern pattern = myAppointment.GetRecurrencePattern();
for (int i = 1; i <= pattern.Exceptions.Count; i++)
{
Outlook.Exception myException = pattern.Exceptions[i];
Outlook.AppointmentItem exceptionItem = myException.AppointmentItem;
DateTime itemDateStart = exceptionItem.Start;
if (itemDateStart == calView.SelectedStartTime)
{
updateMyPluginMeeting(exceptionItem);
return; // the use may only select on AppointmentItem so we can skip the rest
}
}
}
}
If you know any better solution to this let me know.

How detect when user choose NEW or OPEN mail in Outlook VSTO

I'm programming a VSTO in Outlook 2016 and I would like to enable/disable buttons in a Ribbon, based on the user's action of START A NEW MESSAGE or just OPEN/READ a message.
My problem is HOW detect when the user pressed NEW MAIL or just open a sent/received one message.
Could anyone help me?
Thanks!
This tutorial actually deals with this exact scenario:
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
inspectors = this.Application.Inspectors;
inspectors.NewInspector +=
new Microsoft.Office.Interop.Outlook.InspectorsEvents_NewInspectorEventHandler(Inspectors_NewInspector);
}
Specifically, you attach to this.Appliaction.Inspectors. The tutorial takes the opportunity to modify the Subject and Body properties of the new MailItem:
void Inspectors_NewInspector(Microsoft.Office.Interop.Outlook.Inspector Inspector)
{
Outlook.MailItem mailItem = Inspector.CurrentItem as Outlook.MailItem;
if (mailItem != null)
{
if (mailItem.EntryID == null)
{
mailItem.Subject = "This text was added by using code";
mailItem.Body = "This text was added by using code";
}
}
}

Email sent from C# OOM stays in Outbox if Outlook is closed until next Outlook start

I'm trying to send emails from a .NET application using Outlook Object Model.
My application displays the Outlook message window so the user can see what we're sending and edit it first. When the user hits the Send button, the Outlook window closes, and the message gets sent. This works perfectly as long as the Outlook application is already running.
If the Outlook application isn't already running, the message gets stuck in the Outbox, and will not send until I start Outlook. When I start Outlook, I can see the message sitting in the Outbox folder for a few seconds, then it gets sent.
I need to show the New Message form to Outlook user to select the recipient(s) and possibly edit the message before sending.
Note: I know that this question was already asked here Email sent with Outlook Object Model stays in Outbox until I start Outlook
and the solution exists, but it is not provided (only the small hint is provided) and unfortunately I cannot ask for clarification / code example because I have not enough "reputation".
I tried to write my own implementation of the hint provided, but the SyncEnd event is fired only when Outlook is already open (just to remind, the question is about the case then Outlook is closed).
My code below. What is wrong?
using Microsoft.Office.Interop.Outlook;
using OutlookApp = Microsoft.Office.Interop.Outlook.Application;
class Mailer
{
AutoResetEvent mailSentEvent = new AutoResetEvent(false);
public void CreateMail()
{
OutlookApp outlookApp = null;
MailItem mailItem = null;
try
{
outlookApp = new OutlookApp();
mailItem = outlookApp.CreateItem(OlItemType.olMailItem);
mailItem.Subject = "Test Message";
mailItem.Body = "This is the message.";
string reportPath = #"C:\temp\aaaaa.pdf";
mailItem.Attachments.Add(reportPath);
mailItem.Display(true);
StartSync(outlookApp);
bool result = mailSentEvent.WaitOne();
}
catch (System.Exception)
{
throw;
}
finally
{
if (mailItem != null) Marshal.ReleaseComObject(mailItem);
if (outlookApp != null) Marshal.ReleaseComObject(outlookApp);
}
}
private static SyncObject _syncObject = null;
private void StartSync(OutlookApp outlookApp)
{
var nameSpace = outlookApp.GetNamespace("MAPI");
_syncObject = nameSpace.SyncObjects[1];
_syncObject.SyncEnd += new Microsoft.Office.Interop.Outlook.SyncObjectEvents_SyncEndEventHandler(OnSyncEnd);
_syncObject.Start();
}
private void OnSyncEnd()
{
mailSentEvent.Set();
}
}
the SyncEnd event is fired only when Outlook is already open
That is not true. The SyncObjects collection contains all Send\Receive groups. You need to iterate over all objects in the collection and call the Start method, for example:
Set sycs = nsp.SyncObjects
For i = 1 To sycs.Count
Set syc = sycs.Item(i)
strPrompt = MsgBox("Do you wish to synchronize " &; syc.Name &;"?", vbYesNo)
If strPrompt = vbYes Then
syc.Start
End If
Next

x-header missing for Outlook MeetingItems when received

I am using the following approach in a VSTO Outlook Addin (using Addin-Express library) to set a custom x-header attribute on mails and other items you can send from Microsoft Outlook, like meetings. It's a security classification which is evaluated by a mail gateway appliance for making sure encryption is activated on outgoing mails, depending on the classification. Inside the organization, it will just be displayed on the receiving side, which is Outlook 2016 desktop client.
Before sending, I set the x-header property like this:
string headerNamespace = "http://schemas.microsoft.com/mapi/string /{00020386-0000-0000-C000-000000000046}/";
public void SetHeader(PropertyAccessor acc, string header, string value)
{
acc.SetProperty(headerNamespace + "x-mycustomheader", value);
}
I always receive the header for mails, but not for Meetings. I know there is an associated appointment, but I tried reading the header from any object I could think of
On the receiving end, I get the current item from the explorer or inspector window and try to retrieve that header. The attribute is not visible in OutlookSpy and it's not inside the transport header. Is it possible that Outlook stored it somewhere else, or has it been removed? This process is working fine for MailItem types.
I stripped some parts like releasing the com objects for better readability. OutlookMeetingItem2 is a wrapper class. I have two alternative methods for reading the header, one is the PropertyAccessor and the other is used here, both can't find the header attribute.
using (OutlookMessageItemWrapper outlookItem = OutlookMessageItemFactory.GetMessageItem(currentItem))
{
object outlookitemMapi = outlookItem.MAPIOBJECT;
classificationString = mapi.GetHeader(outlookItem.MAPIOBJECT, Constants.CLASSIFICATIONHEADER);
if (classificationString == "" && outlookItem is OutlookMeetingItem2)
{
OutlookMeetingItem2 meetingItem = outlookItem as OutlookMeetingItem2;
MeetingItem meeting = meetingItem.Item;
object mapiObject = meeting.MAPIOBJECT;
classificationString = mapi.GetHeader(meeting.MAPIOBJECT, Constants.CLASSIFICATIONHEADER);
if (classificationString == "")
{
AppointmentItem appointment = meeting.GetAssociatedAppointment(false);
if (appointment != null)
{
classificationString = mapi.GetHeader(appointment.MAPIOBJECT, Constants.CLASSIFICATIONHEADER);
}
}
}
}