Databinding and Add-Ins


There are many advantages to using the .NET Framework add-in technology to factor application logic and UI, including security isolation, discovery, activation, communication, lifetime management, and versioning.


WPF builds on the .NET Framework add-in technology to enable visual add-ins ie add-ins that provide UI for host applications to display. All that you need to do is define the contract to specify what types of things the host wants the add-in’s UI to do. The end result for the user is that they see both host and add-in UI intermingled without knowing about the complex machinery underneath to make it work.


And, to some degree, developers might approach the development of add-ins the same way. For example, when a WPF host application displays a UI provided by an add-in, developers might intuitively expect features like data binding, commanding etc to just work. Features like this, though, need a little extra work to, err, get working because features like these weren’t originally designed to work across the isolation boundary between host and add which is the space between the host’s application domain and the add-ins application domain. (This use of application domains enables security isolation.)


The attached sample demonstrates how to implement host/add-in applications to support data binding. Specifically, the sample shows a host application that manages a data object (Person) and provides it to UI add-ins that either re-visualize the data or allow editing of the data, or both. Here’s how the application looks at run time.



To understand the sample, you’ll need to be familiar with .NET Framework and WPF add-in support. This SDK document provides a good starting point.


You’ll need to play with the sample to see how it really works but the 10000′ view is:



  • There is one contract for the Add-In

  • There is one contract for the Person data object

  • The host instantiates the Person data object and binds some UI to it.

  • The host activates and displays a UI provided by the add-in, to which the host passes the Person object. The add-in UI also binds to the Person object.

  • When the host UI that is bound to the data object is updated, the changes are communicated to the add-in UIs that are also bound to the data object.

  • When the editable add-in UI that is bound to the Person data object is updated, the changes are communicated back to the host.

To the host and add-in UIs, all they see is a Person data object that implements INotifyPropertyChanged which is the data binding interface that essentially ensures bound UI are updated when the data objects they are bound to are updated. The add-in pipeline to make this work is a little more involved, technically, but is the same as using INotifyPropertyChanged, in concept.


You’ll find the best way to grok the sample is to download it and see it running.


Enjoy!

WPFAddInDataBindingSample.zip

Comments (5)

  1. dannycoates says:

    This is a great sample, but I’m having a couple problems with it. One of them I figured out by reading the SDK documentation, the other I can’t seem to figure out.

    The first one was an "Invalid Window Handle" on exit, after running "Unload UI Add-Ins" from the menu. This I resolved by adding a method to the contract to shutdown the Add-in Dispatcher as suggested in the SDK documentation:

    "All dispatchers (see Dispatcher) created by the add-in UI must be shut down manually before the owner add-in is unloaded if the host application continues execution. The contract can implement methods that allow the host application to signal the add-in before the add-in is unloaded, thereby allowing the add-in UI to shut down its dispatchers." at http://msdn.microsoft.com/en-us/library/bb909794.aspx

    The other problem happens when the add-ins are loaded, then unloaded, then reloaded again. After that the data "binding" is broken. Updates to the host form are not making it to the add-ins, but an update to the editable add-in changes the host. I was able to follow the execution until I got to HostSideAdaptersAdapter.cs line 75 where the call to this.propertyChangedNotifier.PropertyChangeNotification(e.PropertyName) throws a ‘System.AppDomainUnloadedException’ "The target application domain has been unloaded."

    Are there any workarounds for this? Any insight would appreciated 🙂

  2. dannycoates says:

    With a little more work I figured out that the event handler that is attached on line 45 of the HostSideAdaptersAdapter.cs doesn’t get removed when the add-ins are unloaded. So what it looks like its doing the next time the event fires it tries to reach the propertyChangedNotifier.PropertyChangeNotification method from the unloaded AppDomain. When I remove the event handler from personView before calling Shutdown() the data binding works on reloaded add-ins.

  3. jgalasyn says:

    Hi Danny,

    Thanks for the feedback. Did that resolve your issue?

    Jim Galasyn

    Programming Writer

    Developer Division User Education

    Thank you for contacting the WPF team. By taking the time to write, you are helping us to improve the quality of our documentation and samples. Please accept our apologies for any inconvenience this may have caused you. This response is provided "AS IS" with no warranties, and confers no rights.

  4. dannycoates says:

    Yes, Mostly.

    I’ve started a thread on the WPF forum with another question http://forums.msdn.microsoft.com/en/wpf/thread/2b2e0fe7-5dbb-4ac5-9fdf-f834ac144b81

    Thanks,

    Danny

  5. jliebo says:

    Great sample — but it shows the same issue I have with my AddIn framework.  If I click on ‘Load UI Addins’ three times in your app, it throws the following exception.  The same occurs for me if I open an AddIn eight times.  I’m about to abandon AddIn framework because of it — hoping you might have some insight.  Many thanks!

    System.Runtime.InteropServices.COMException was unhandled by user code

     Message="Exception from HRESULT: 0x88980406"

     Source="PresentationCore"

     ErrorCode=-2003303418

     StackTrace:

          at System.Windows.Media.Composition.DUCE.Channel.SyncFlush()

          at System.Windows.Media.MediaContext.CompleteRender()

          at System.Windows.Interop.HwndTarget.OnResize()

          at System.Windows.Interop.HwndTarget.HandleMessage(Int32 msg, IntPtr wparam, IntPtr lparam)

          at System.Windows.Interop.HwndSource.HwndTargetFilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)

          at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)

          at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)

          at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Boolean isSingleParameter)

          at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Boolean isSingleParameter, Delegate catchHandler)

          at System.Windows.Threading.Dispatcher.WrappedInvoke(Delegate callback, Object args, Boolean isSingleParameter, Delegate catchHandler)

          at System.Windows.Threading.Dispatcher.InvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Boolean isSingleParameter)

          at System.Windows.Threading.Dispatcher.Invoke(DispatcherPriority priority, Delegate method, Object arg)

          at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)

          at MS.Win32.UnsafeNativeMethods.CallWindowProc(IntPtr wndProc, IntPtr hWnd, Int32 msg, IntPtr wParam, IntPtr lParam)

          at MS.Win32.HwndSubclass.DefWndProcWrapper(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)

          at MS.Win32.UnsafeNativeMethods.CallWindowProc(IntPtr wndProc, IntPtr hWnd, Int32 msg, IntPtr wParam, IntPtr lParam)

          at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)

          at MS.Win32.UnsafeNativeMethods.SetWindowPos(HandleRef hWnd, HandleRef hWndInsertAfter, Int32 x, Int32 y, Int32 cx, Int32 cy, Int32 flags)

          at System.Windows.Interop.HwndSource.Resize(Size newSize)

          at System.Windows.Interop.HwndSource.OnLayoutUpdated(Object obj, EventArgs args)

          at System.Windows.ContextLayoutManager.fireLayoutUpdateEvent()

          at System.Windows.ContextLayoutManager.UpdateLayout()

          at System.Windows.UIElement.UpdateLayout()

          at System.Windows.Interop.HwndSource.SetLayoutSize()

          at System.Windows.Interop.HwndSource.set_RootVisualInternal(Visual value)

          at System.Windows.Interop.HwndSource.set_RootVisual(Visual value)

          at System.AddIn.Pipeline.FrameworkElementAdapters.ViewToContractAdapter(FrameworkElement root)

          at AddInSideAdapters.AddIn_ViewToContractAddInSideAdapter.GetAddInUI(IPersonContract personContract)

          at System.Runtime.Remoting.Messaging.StackBuilderSink._PrivateProcessMessage(IntPtr md, Object[] args, Object server, Int32 methodPtr, Boolean fExecuteInContext, Object[]& outArgs)

          at System.Runtime.Remoting.Messaging.StackBuilderSink.PrivateProcessMessage(RuntimeMethodHandle md, Object[] args, Object server, Int32 methodPtr, Boolean fExecuteInContext, Object[]& outArgs)

          at System.Runtime.Remoting.Messaging.StackBuilderSink.SyncProcessMessage(IMessage msg, Int32 methodPtr, Boolean fExecuteInContext)

     InnerException: