Outlook VSTO AddIn How to avoid RaceOnRCWCleanup - outlook

What I am trying to achieve is to also handle drag and drop events on the calendar properly using the AppProperty Change event on the Inspector:
I update the currentAppointmentItem whenever the user interacts with the interface (Explorer.SelectionChange, NewInspector, CloseInspector, etc.)
I update the currentInspector whenever the user interacts with the interface (SelectionChange, NewInspector, CloseInspector)
Update means that I try to set/unset the event handlers appropriately and to Marshal.ReleaseComObject accordingly. Finally to null the reference.
But when the user just clicks on an AppointmentItem in the calendar no Inspector window is created. Thus I wouldn't be able to catch AppPropertyChange Events. So I decided to call GetInspector on the selected AppointmentItem in case it is not null. I try to use this to receive changes on the AppProperty Event so I can handle drag and drop events on the calendar properly
The problem: From the Microsoft documentation I understand whenever you lose a reference to currentAppointmentItem you should also use Marshal.ReleaseComObject otherwise you risk other problems.
Now I experience exceptions which I cannot catch: RaceOnRCWCleanup ... it seems that I try to release a COM object which is still in use (probably by Outlook). How can I avoid that? Is it correct to Marshal.ReleaseComObject(currentAppointmentItem)
I registered on the SelectionChange Event on the Outlook.Explorer. In there I try to register the currentAppointment with:
[...]
log.Info("Selection_Change");
if (currentExplorer == null)
{
return;
}
try
{
log.Info("Selection_Change: " + currentExplorer.Caption);
Outlook.MAPIFolder selectedFolder = currentExplorer.CurrentFolder;
if (currentExplorer.Selection.Count > 0)
{
Object selObject = currentExplorer.Selection[1];
if (selObject is Outlook.AppointmentItem)
{
currentAppointmentItem = (Outlook.AppointmentItem)selObject;
Inspectors_NewInspector(currentAppointmentItem.GetInspector);
}
[...]
Please Note: INspectors_NewInspector is also called on the Inspectors Collection.
The code of NewInspector is like
void Inspectors_NewInspector(Microsoft.Office.Interop.Outlook.Inspector Inspector)
{
try
{
log.Info("Inspectors_NewInspector");
// This function (apparently) gets kicked off whenever a user opens a new or existing item
// in Outlook (Calendar appointment, Email, etc).
// We can intercept it, modify it's properties, before letting our Ribbon know about its existance.
//
if (Inspector != null)
{
log.Info("Inspectors_NewInspector: " + Inspector.Caption);
unregisterCurrentInspector();
currentInspector = Inspector;
object item = Inspector.CurrentItem;
if (item == null)
return;
if (!(item is Outlook.AppointmentItem))
return;
unregisterCurrentAppointmentItem();
currentAppointmentItem = (Outlook.AppointmentItem)item;
currentAppointmentItem.PropertyChange += AppPropertyChanged; // Handle situations where the
// user tries to convert an appointment w/ an agreedo protocol to a recurring appointment.
// This needs to be avoided .
currentAppointmentItem.CustomPropertyChange += AppPropertyChanged;
}
((Microsoft.Office.Interop.Outlook.InspectorEvents_10_Event)Inspector).Close += Inspector_Close;
} catch (Exception ex)
{
log.Error(ex.Message);
}
}
the unregisterCurrentApppointmentItem :
private void unregisterCurrentAppointmentItem()
{
try
{
log.Info("unregisterCurrentAppointmentItem");
if (currentAppointmentItem != null)
{
currentAppointmentItem.PropertyChange -= AppPropertyChanged; // Handle situations where the
currentAppointmentItem.CustomPropertyChange -= AppPropertyChanged;
Marshal.ReleaseComObject(currentAppointmentItem);
currentAppointmentItem = null;
}
} catch (Exception ex)
{
log.Error(ex.Message);
}
}
the unregisterCurrentInspector:
private void unregisterCurrentInspector()
{
log.Info("unregisterCurrentInspector");
if (currentInspector != null)
{
((Microsoft.Office.Interop.Outlook.InspectorEvents_10_Event)currentInspector).Close -= Inspector_Close;
Marshal.ReleaseComObject(currentInspector);
currentInspector = null;
}
}
Any advice on this?
What I already tried / taken into account:
Outlook Addin: Moving Appointment in Calendar does not reflect new date/time in AppointmentItem (catch Calendar.ItemChange)
VSTO Outlook Plugin: Cannot get AppointmentItem in Item_Change event when recurring appointment is dragged and dropped by user

First of all, there is no need to simulate the NewInspector event. Instead, you need to set up event handlers correctly. It seems you just need to implement an inspector or explorer wrappers. See Implement a wrapper for inspectors and track item-level events in each inspector for more information.
it seems that I try to release a COM object which is still in use (probably by Outlook). How can I avoid that? Is it correct to Marshal.ReleaseComObject(currentAppointmentItem)
Yes, it is. But you should really use this method against objects retrieved in your code by calling properties and methods. You SHOULD NOT release objects passed as parameters by the Office applications. Take a look a the When to release COM objects in Office add-ins developed in .NET article which explains possible pitfalls and give answers to the most widely spread questions.

Why do you even need the Inspector object? Are you only using the Inspector.Close event?
Use AppointmentItem.Close / Write events.

Related

Outlook VSTO Handling SelectionChange correctly (currently doubleclick crashes Addin)

From what I understand you need to track Activation and Deactivation of the Explorers. During activation, you need to add SelectionChange event handlers for the current explorer.
This seems to work perfectly for single clicks on AppointmentItems. But it crashes the Addin when double-clicking on an appointment series and selecting a single Appointment.
Here is the source:
On class level
private Outlook.Explorer currentExplorer = null;
private Outlook.AppointmentItem currentAppointmentItem = null;
within Startup:
currentExplorer = this.Application.ActiveExplorer();
((Outlook.ExplorerEvents_10_Event)currentExplorer).Activate +=
new Outlook.ExplorerEvents_10_ActivateEventHandler(
Explorer_Activate);
currentExplorer.Deactivate += new
Outlook.ExplorerEvents_10_DeactivateEventHandler(
Explorer_Deactivate);
The event handlers:
void Explorer_Activate()
{
currentExplorer.SelectionChange += new Outlook.ExplorerEvents_10_SelectionChangeEventHandler(Selection_Change);
}
void Explorer_Deactivate()
{
currentExplorer.SelectionChange -= new Outlook.ExplorerEvents_10_SelectionChangeEventHandler(Selection_Change); ;
}
private void Close_Explorer()
{
}
private void Selection_Change()
{
Outlook.MAPIFolder selectedFolder = currentExplorer.CurrentFolder;
if (currentExplorer.Selection.Count > 0)
{
Object selObject = currentExplorer.Selection[1];
if (selObject is Outlook.AppointmentItem)
{
currentAppointmentItem = (Outlook.AppointmentItem)selObject;
}
else
{
currentAppointmentItem = null;
}
}
}
What am I overlooking? Is the form of deregistering a problem?
Try to add try/catch blocks to the event handlers. The Outlook object model can give you unpredictable results sometimes. It is worth adding them and find where an exception is thrown.
currentExplorer.Selection.Count
Also, you may subscribe to the SelectionChange event in the NewExplorer event and don't switch between explorers when they are activated or deactivated. The event is fired whenever a new explorer window is opened, either as a result of user action or through program code.
The only thing which I added was a handler for NewInspector and InspectorClose events along with Marshal.ReleaseComObject(). The only thing which I can imagine that double clicking while debugging I got in some kind of race condition (because double clicking also triggers the Selection_Change event). But this is only a guess.
You do not need to add and remove event handlers as an explorer is activated / deactivated. Are you trying to support multiple explorers? In that case, create a wrapper class that hold the Explorer object as it member and uses its methods as event handlers.

Inconsistent Outlook Addin Event Firing

On my development machine I have the FormInitializing and FormShowing events firing before RibbonLoad. I created a setup package in VS 2010 and installed on a vanilla Windows 7 Ultimate with Outlook 2010 installed.
The addin wasn't appearing on my meeting request form. So I setup remote debugger and to my astonishment the RibbonLoad is firing before the two form events mentioned above. A null exception is being throw b\c the code in the RibbonLoad relies on the FormRegion already being loaded. Can anyone offer any insight?
There is no defined order for certain Outlook events - the Ribbon UI and the Inspector UI are completely different components, despite them both being display in the same window. The Outlook runtime may trigger Ribbon and Inspector events in different orders. It would be your job to synchronize the two events (RibbonLoad and FormInitializing) if you need some initialization done. You cannot assume that the ordering will always be the same.
I notice this same behavior when ThisAddIn.Startup fires before ThisAddIn.CreateRibbonExtensibilityObject, but sometimes after depending on how Outlook triggers the sequencing. You can just use a static variable with sync locking to ensure your initialization code is only triggered once.
Here is an example I used to synchronize the Startup event with the RibbonLoad event:
public partial class ThisAddIn
{
static bool formInitialized = false;
static readonly object padLock = new Object();
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
lock(padLock) { if (!formInitialized ) { InitializeForm(); } }
// startup code
}
private void InitializeForm()
{
// init code
formInitialized = true;
}
protected override IRibbonExtensibility CreateRibbonExtensibilityObject()
{
lock(padLock) { if (!formInitialized) InitializeForm(); }
// Create ribbon UI
}
}

ViewModels and IsolatedStorageSettings

Im working on a MVVM Windows phone app that displays weather info.
When the app loads up it opens MainPage.xaml. It makes a call the the service to get weather info and binds that data to the UI. Both Fahrenheit and Celcius info are returned but only one is displayed.
On the setting page, the user can select to view the temp in either Fahrenheit or Celcius.
The user can change this setting at any time and its stored in IsolatedStorageSettings.
The issue Im having is this:
when the user navigates to the Settings page and changes their preference for either Fahrenheit or Celcius, this change is not reflected on the main page.
This issue started me thinking about this in a broader context. I can see this being an issue in ANY MVVM app where the display depends on some setting in IsolatedStorage. Any time any setting in the IsoStore is updated, how does the ViewModels know this? When I navigate back in the NavigationStack from the settings page back to MainPage how can I force a rebind of the page?
The data in my model hasnt changed, only the data that I want to display has changed.
Am I missing something simple here?
Thanks in advance.
Alex
Probably you have code like this:
public double DisplayTemperature
{
get { return (IsCelsium) ? Celsium : Fahrenheit; }
}
And IsCelsium is:
public double IsCelsium
{
get { return (bool)settings["IsCelsium"]; }
set { settings["IsCelsium"] = value; }
}
So you need to add NotifyPropertyChanged event to notify UI to get new values from DisplayTemperature property:
public double IsCelsium
{
get { return (bool)settings["IsCelsium"]; }
set
{
settings["IsCelsium"] = value;
NotifyPropertyChanged("DisplayTemperature");
}
}
Take a look at Caliburn Micro. You could implement something similar or use CM itself. When using CM I don't even think about this stuff, CM makes it so simple.
When your ViewModel inherits from Screen there are life-cycle events that fire that you can override. For example, OnInitialize fires the very first time the ViewModel is Activated and OnActivate fires every time the VM is activated. There's also OnViewAttached and OnViewLoaded.
These methods are the perfect place to put logic to populate or re-populate data.
CM also has some special built in features for allowing one to easily tombstone a single property or an entire object graph into Iso or phone state.
ok, so Ive come up with a solution. Before I get to it, let me provide some background. The app that Im working on uses both MVVM Light and WP7Contrib. That being the case, I am using Funq for DI and the MVVMLight Toolkit. After I posted my initial question, I gave the question a bit more thought. I remembered a video that I watched a while back from MIX2011 called Deep Dive MVVM with Laurent Bugnion
http://channel9.msdn.com/Events/MIX/MIX11/OPN03
In it, he talks about just this problem (view models not living at the same time) on Windows Phone. The part in question starts around the 19 minute mark.
Anyway, after I remembered that and realized that the ViewModel locator is exposed in App.xaml, this became a trivial problem to solve. When the user changes the Fahrenheit/Celcius option on the setting page, I simply get a reference to the MainViewModel via the ViewModelLocator and reset the collection that is bound to the UI thus causing the bindings to update.
public bool AddOrUpdateValue(string Key, Object value)
{
bool valueChanged = false;
// If the key exists
if (settings.Contains(Key))
{
// If the value has changed
if (settings[Key] != value)
{
// Store the new value
settings[Key] = value;
valueChanged = true;
}
}
// Otherwise create the key.
else
{
settings.Add(Key, value);
valueChanged = true;
}
return valueChanged;
}
public bool ImperialSetting
{
get
{
return GetValueOrDefault<bool>(ImperialSettingKeyName, ImperialSettingDefault);
}
set
{
if (AddOrUpdateValue(ImperialSettingKeyName, value))
{
Save();
RaisePropertyChanged("ImperialSettingText");
var vml = new ViewModelLocator();
vml.MainViewModel.Cities = (App.Current as App).Cities;
}
}
}
It was a mistake on my part not to realize that I could get access to the viewModel via the ViewModelLocator. Hopefully this post saves someone else the time I burned on this issue.

How to handle key press events with the QPlainTextEdit

I've been developing with QT for around a week now and am pleased to say that I'm picking it up really fast. I'm an intermediate C++ programmer but picking up some parts of QT is proving to be challenging. I need to process key press events from the QPlainTextEdit when the user presses enter and I presume that the solution will involve sub classing the widget. Can any of you smart guys give me a potential implementable solution?
To really understand Qt and event handling there are two key areas of the documentation you should read. The first is the overview on The Event System and the second is a very important bit which is a cleverly hidden link on that page for QCoreApplication::notify. They should really move that to the main page of the Event System documentation as it really makes things quite clear (to me at least).
If you only need to handle some messages sent to the control - like the key-presses - there is no need to subclass it. You can alternatively use the event filtering mechanism. Here is a simple example:
Provide virtual eventFilter method in one of your QObject-based classes (e.g. the window form class).
bool MyWindow::eventFilter(QObject *watched, QEvent *event)
{
if(watched == ui->myTargetControl)
{
if(event->type() == QKeyEvent::KeyPress)
{
QKeyEvent * ke = static_cast<QKeyEvent*>(event);
if(ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter)
{
// [...]
return true; // do not process this event further
}
}
return false; // process this event further
}
else
{
// pass the event on to the parent class
return QMainWindow::eventFilter(watched, event);
}
}
Install your class as the event filter for the target control. Form constructor is usually a good place for this code. In the following snippet this refers to the instance of class in which you implemented the eventFilter method.
ui->myTargetControl->installEventFilter(this);
i would try subclassing QPlainTextEdit and reimplementing QWidget::keyPressEvent:
void YourTextEdit::keyPressEvent ( QKeyEvent * event )
{
if( event->key() == Qt::Key_Return )
{
// optional: if the QPlainTextEdit should do its normal action
// even when the return button is pressed, uncomment the following line
// QPlainTextEdit::keyPressEvent( event )
/* do your stuff here */
event->accept();
}
else
QPlainTextEdit::keyPressEvent( event )
}
please try :
if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter){
//do something
}
in your keyPressEvent() function.

Subscription to DTE events doesn't seem to work - Events don't get called

I've made an extension inside a package and I am calling the following code (occurs when a user presses a button in the toolbar):
DocumentEvents documentEvents = (DTE2)GetService(typeof(DTE));
_dte.Events.DebuggerEvents.OnEnterBreakMode += DebuggerEvents_OnEnterBreakMode;
_dte.Events.DebuggerEvents.OnEnterDesignMode += DebuggerEvents_OnEnterDesignMode;
_dte.Events.DebuggerEvents.OnContextChanged += DebuggerEvents_OnContextChanged;
_dte.Events.DocumentEvents.DocumentSaved += new _dispDocumentEvents_DocumentSavedEventHandler(DocumentEvents_DocumentSaved);
_dte.Events.DocumentEvents.DocumentOpened += new _dispDocumentEvents_DocumentOpenedEventHandler(DocumentEvents_DocumentOpened);
void DocumentEvents_DocumentOpened(Document Document)
{
}
void DocumentEvents_DocumentSaved(Document Document)
{
}
void DebuggerEvents_OnEnterBreakMode(dbgEventReason Reason, ref dbgExecutionAction ExecutionAction)
{
}
void DebuggerEvents_OnContextChanged(Process NewProcess, Program NewProgram, Thread NewThread, StackFrame NewStackFrame)
{
}
private void DebuggerEvents_OnEnterDesignMode(dbgEventReason reason)
{
}
The first and the major problem is that the subscription to the event doesn't work. I've tried:
Opening new documents
Detaching from debug (thus supposedly triggering OnEnterDesignMode
Saving a document
None of these seem to have any effect and the callback functions were never called.
The second issue is that the subscription to the event line works USUALLY (the subscription itself, the callback doesn't work as described above) but after a while running the subscription line, e.g:
_dte.Events.DebuggerEvents.OnEnterBreakMode -= DebuggerEvents_OnEnterBreakMode;
Causes an exception:
Exception occured!
System.Runtime.InteropServices.InvalidComObjectException: COM object that has been separated from its underlying RCW cannot be used.
at System.StubHelpers.StubHelpers.StubRegisterRCW(Object pThis, IntPtr pThread)
at System.Runtime.InteropServices.UCOMIConnectionPoint.Unadvise(Int32 dwCookie)
at EnvDTE._dispDebuggerEvents_EventProvider.remove_OnEnterDesignMode(_dispDebuggerEvents_OnEnterDesignModeEventHandler A_1)
Any ideas will be welcome
Thanks!
Vitaly
Posting an answer that I got from MSDN forums, by Ryan Molden, in case it helps anyone:
I believe the problem here is how the
CLR handles COM endpoints (event
sinks). If I recall correctly when
you hit the
_applicationObject.Events.DebuggerEvents
part of your 'chain' the CLR will
create a NEW DebuggerEvents object for
the property access and WON'T cache
it, therefor it comes back to you, you
sign up an event handler to it (which
creates a strong ref between the
TEMPORARY object and your object due
to the delegate, but NOT from your
object to the temporary object, which
would prevent the GC). Then you don't
store that object anywhere so it is
immediately GC eligible and will
eventually be GC'ed.
I changed the code to store DebuggerEvents as a field and it all started to work fine.
Here is what #VitalyB means using code:
// list where we will place events.
// make sure that this variable is on global scope so that GC does not delete the evvents
List<object> events = new List<object>();
public void AddEvents(EnvDTE dte)
{
// create an event when a document is open
var docEvent = dte.Events.DocumentEvents;
// add event to list so that GC does not remove it
events.Add(docEvent );
docEvent.DocumentOpened += (document)=>{
Console.Write("document was opened!");
};
// you may add more events:
var commandEvent = dte.Events.CommandEvents;
events.Add(commandEvent );
commandEvent.AfterExecute+= etc...
}

Resources