How to: Create an Add-In That Returns a UI

 

This example shows how to create an add-in that returns a Windows Presentation Foundation (WPF) user interface (UI) to a host WPF standalone application.

The add-in returns a UI that is a WPF user control. The content of the user control is a single button that, when clicked, displays a message box. The WPF standalone application hosts the add-in and displays the user control (returned by the add-in) as the content of the main application window.

Prerequisites

This example highlights the WPF extensions to the .NET Framework add-in model that enable this scenario, and assumes the following:

Example

To create an add-in that returns a WPF UI requires specific code for each pipeline segment, the add-in, and the host application.

A method must be defined by the contract for returning a UI, and its return value must be of type INativeHandleContract. This is demonstrated by the GetAddInUI method of the IWPFAddInContract contract in the following code.

Imports System.AddIn.Contract ' IContract, INativeHandleContract
Imports System.AddIn.Pipeline ' AddInContractAttribute

Namespace Contracts
	''' <summary>
	''' Defines the services that an add-in will provide to a host application
	''' </summary>
	<AddInContract>
	Public Interface IWPFAddInContract
		Inherits IContract
		' Return a UI to the host application
		Function GetAddInUI() As INativeHandleContract
	End Interface
End Namespace

Because the add-in implements the UIs it provides as subclasses of FrameworkElement, the method on the add-in view that correlates to IWPFAddInView.GetAddInUI must return a value of type FrameworkElement. The following code shows the add-in view of the contract, implemented as an interface.

Imports System.AddIn.Pipeline ' AddInBaseAttribute
Imports System.Windows ' FrameworkElement

Namespace AddInViews
	''' <summary>
	''' Defines the add-in's view of the contract
	''' </summary>
	<AddInBase>
	Public Interface IWPFAddInView
		' The add-in's implementation of this method will return
		' a UI type that directly or indirectly derives from 
		' FrameworkElement.
		Function GetAddInUI() As FrameworkElement
	End Interface
End Namespace

The contract method returns an INativeHandleContract, but the add-in returns a FrameworkElement (as specified by the add-in view). Consequently, the FrameworkElement must be converted to an INativeHandleContract before crossing the isolation boundary. This work is performed by the add-in-side adapter by calling ViewToContractAdapter, as shown in the following code.

Imports System.AddIn.Contract ' INativeHandleContract
Imports System.AddIn.Pipeline ' AddInAdapterAttribute, FrameworkElementAdapters, ContractBase
Imports System.Windows ' FrameworkElement

Imports AddInViews ' IWPFAddInView
Imports Contracts ' IWPFAddInContract

Namespace AddInSideAdapters
	''' <summary>
	''' Adapts the add-in's view of the contract to the add-in contract
	''' </summary>
	<AddInAdapter>
	Public Class WPFAddIn_ViewToContractAddInSideAdapter
		Inherits ContractBase
		Implements IWPFAddInContract
		Private wpfAddInView As IWPFAddInView

		Public Sub New(ByVal wpfAddInView As IWPFAddInView)
			' Adapt the add-in view of the contract (IWPFAddInView) 
			' to the contract (IWPFAddInContract)
			Me.wpfAddInView = wpfAddInView
		End Sub

        Public Function GetAddInUI() As INativeHandleContract Implements IWPFAddInContract.GetAddInUI
            ' Convert the FrameworkElement from the add-in to an INativeHandleContract 
            ' that will be passed across the isolation boundary to the host application.
            Dim fe As FrameworkElement = Me.wpfAddInView.GetAddInUI()
            Dim inhc As INativeHandleContract = FrameworkElementAdapters.ViewToContractAdapter(fe)
            Return inhc
        End Function
	End Class
End Namespace

Because the host application will display a FrameworkElement, the method on the host view that correlates to IWPFAddInHostView.GetAddInUI must return a value of type FrameworkElement. The following code shows the host view of the contract, implemented as an interface.

Imports System.Windows ' FrameworkElement

Namespace HostViews
	''' <summary>
	''' Defines the host's view of the add-in
	''' </summary>
	Public Interface IWPFAddInHostView
		' The view returns as a class that directly or indirectly derives from 
		' FrameworkElement and can subsequently be displayed by the host 
		' application by embedding it as content or sub-content of a UI that is 
		' implemented by the host application.
		Function GetAddInUI() As FrameworkElement
	End Interface
End Namespace

The contract method returns an INativeHandleContract, but the host application expects a FrameworkElement (as specified by the host view). Consequently, the INativeHandleContract must be converted to a FrameworkElement after crossing the isolation boundary. This work is performed by the host-side adapter by calling ContractToViewAdapter, as shown in the following code.

Imports System.AddIn.Contract ' INativeHandleContract
Imports System.AddIn.Pipeline ' HostAdapterAttribute, FrameworkElementAdapters, ContractHandle
Imports System.Windows ' FrameworkElement

Imports Contracts ' IWPFAddInContract
Imports HostViews ' IWPFAddInHostView

Namespace HostSideAdapters
	''' <summary>
	''' Adapts the add-in contract to the host's view of the add-in
	''' </summary>
	<HostAdapter>
	Public Class WPFAddIn_ContractToViewHostSideAdapter
        Implements IWPFAddInHostView
		Private wpfAddInContract As IWPFAddInContract
		Private wpfAddInContractHandle As ContractHandle

		Public Sub New(ByVal wpfAddInContract As IWPFAddInContract)
			' Adapt the contract (IWPFAddInContract) to the host application's
			' view of the contract (IWPFAddInHostView)
			Me.wpfAddInContract = wpfAddInContract

			' Prevent the reference to the contract from being released while the
			' host application uses the add-in
			Me.wpfAddInContractHandle = New ContractHandle(wpfAddInContract)
		End Sub

        Public Function GetAddInUI() As FrameworkElement Implements IWPFAddInHostView.GetAddInUI
            ' Convert the INativeHandleContract that was passed from the add-in side
            ' of the isolation boundary to a FrameworkElement
            Dim inhc As INativeHandleContract = Me.wpfAddInContract.GetAddInUI()
            Dim fe As FrameworkElement = FrameworkElementAdapters.ContractToViewAdapter(inhc)
            Return fe
        End Function
	End Class
End Namespace

With the add-in-side adapter and add-in view created, the add-in (WPFAddIn1.AddIn) must implement the IWPFAddInView.GetAddInUI method to return a FrameworkElement object (a UserControl in this example). The implementation of the UserControl, AddInUI, is shown by the following code.

<UserControl
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="WPFAddIn1.AddInUI">

    <StackPanel>
		<Button Click="clickMeButton_Click" Content="Click Me!" />
    </StackPanel>

</UserControl>
Imports System.Windows ' MessageBox, RoutedEventArgs
Imports System.Windows.Controls ' UserControl

Namespace WPFAddIn1
	Partial Public Class AddInUI
		Inherits UserControl
		Public Sub New()
			InitializeComponent()
		End Sub

		Private Sub clickMeButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
			MessageBox.Show("Hello from WPFAddIn1")
		End Sub
	End Class
End Namespace

The implementation of the IWPFAddInView.GetAddInUI by the add-in simply needs to return a new instance of AddInUI, as shown by the following code.

Imports System.AddIn ' AddInAttribute
Imports System.Windows ' FrameworkElement

Imports AddInViews ' IWPFAddInView

Namespace WPFAddIn1
	''' <summary>
	''' Add-In implementation
	''' </summary>
	<AddIn("WPF Add-In 1")>
	Public Class WPFAddIn
        Implements IWPFAddInView
        Public Function GetAddInUI() As FrameworkElement Implements IWPFAddInView.GetAddInUI
            ' Return add-in UI
            Return New AddInUI()
        End Function
	End Class
End Namespace

With the host-side adapter and host view created, the host application can use the .NET Framework add-in model to open the pipeline, acquire a host view of the add-in, and call the IWPFAddInHostView.GetAddInUI method. These steps are shown in the following code.

' Get add-in pipeline folder (the folder in which this application was launched from)
Dim appPath As String = Environment.CurrentDirectory

' Rebuild visual add-in pipeline
Dim warnings() As String = AddInStore.Rebuild(appPath)
If warnings.Length > 0 Then
    Dim msg As String = "Could not rebuild pipeline:"
    For Each warning As String In warnings
        msg &= vbLf & warning
    Next warning
    MessageBox.Show(msg)
    Return
End If

' Activate add-in with Internet zone security isolation
Dim addInTokens As Collection(Of AddInToken) = AddInStore.FindAddIns(GetType(IWPFAddInHostView), appPath)
Dim wpfAddInToken As AddInToken = addInTokens(0)
Me.wpfAddInHostView = wpfAddInToken.Activate(Of IWPFAddInHostView)(AddInSecurityLevel.Internet)

' Get and display add-in UI
Dim addInUI As FrameworkElement = Me.wpfAddInHostView.GetAddInUI()
Me.addInUIHostGrid.Children.Add(addInUI)
Show: