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.
This example highlights the WPF extensions to the .NET Framework add-in model that enable this scenario, and assumes the following:
Knowledge of the .NET Framework add-in model, including pipeline, add-in, and host development. If you are unfamiliar with these concepts, see Add-ins and Extensibility. For a tutorial that demonstrates the implementation of a pipeline, an add-in, and a host application, see Walkthrough: Creating an Extensible Application.
Knowledge of the WPF extensions to the .NET Framework add-in model, which can be found here: WPF Add-Ins Overview.
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)