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

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

The add-in is 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 UI 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:

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

Implementing the Contract Pipeline Segment

When an add-in is a UI, the contract for the add-in must implement INativeHandleContract. In the example, IWPFAddInContract implements INativeHandleContract, as shown in the following code.


using System.AddIn.Contract; // INativeHandleContract
using System.AddIn.Pipeline; // AddInContractAttribute

namespace Contracts
{
    /// <summary>
    /// Defines the services that an add-in will provide to a host application.
    /// In this case, the add-in is a UI.
    /// </summary>
    [AddInContract]
    public interface IWPFAddInContract : INativeHandleContract {}
}


Implementing the Add-In View Pipeline Segment

Because the add-in is implemented as a subclass of the FrameworkElement type, the add-in view must also subclass FrameworkElement. The following code shows the add-in view of the contract, implemented as the WPFAddInView class.


using System.AddIn.Pipeline; // AddInBaseAttribute
using System.Windows.Controls; // UserControl

namespace AddInViews
{
    /// <summary>
    /// Defines the add-in's view of the contract.
    /// </summary>
    [AddInBase]
    public class WPFAddInView : UserControl { }
}


Here, the add-in view is derived from UserControl. Consequently, the add-in UI should also derive from UserControl.

Implementing the Add-In-Side Adapter Pipeline Segment

While the contract is an INativeHandleContract, the add-in is a FrameworkElement (as specified by the add-in view pipeline segment). Therefore, 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.


using System; // IntPtr
using System.AddIn.Contract; // INativeHandleContract
using System.AddIn.Pipeline; // AddInAdapterAttribute, FrameworkElementAdapters, ContractBase
using System.Security.Permissions;

using AddInViews; // WPFAddInView
using 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 : ContractBase, IWPFAddInContract
    {
        WPFAddInView wpfAddInView;

        public WPFAddIn_ViewToContractAddInSideAdapter(WPFAddInView wpfAddInView)
        {
            // Adapt the add-in view of the contract (WPFAddInView) 
            // to the contract (IWPFAddInContract)
            this.wpfAddInView = wpfAddInView;
        }

        /// <summary>
        /// ContractBase.QueryContract must be overridden to:
        /// * Safely return a window handle for an add-in UI to the host 
        ///   application's application.
        /// * Enable tabbing between host application UI and add-in UI, in the
        ///   "add-in is a UI" scenario.
        /// </summary>
        public override IContract QueryContract(string contractIdentifier)
        {
            if (contractIdentifier.Equals(typeof(INativeHandleContract).AssemblyQualifiedName))
            {
                return FrameworkElementAdapters.ViewToContractAdapter(this.wpfAddInView);
            }

            return base.QueryContract(contractIdentifier);
        }

        /// <summary>
        /// GetHandle is called by the WPF add-in model from the host application's 
        /// application domain to to get the window handle for an add-in UI from the 
        /// add-in's application domain. GetHandle is called if a window handle isn't 
        /// returned by other means ie overriding ContractBase.QueryContract, 
        /// as shown above.
        /// NOTE: This method requires UnmanagedCodePermission to be called 
        ///       (full-trust by default), to prevent illegal window handle
        ///       access in partially trusted scenarios. If the add-in could
        ///       run in a partially trusted application domain 
        ///       (eg AddInSecurityLevel.Internet), you can safely return a window
        ///       handle by overriding ContractBase.QueryContract, as shown above.
        /// </summary>
        [SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        public IntPtr GetHandle()
        {
            return FrameworkElementAdapters.ViewToContractAdapter(this.wpfAddInView).GetHandle();
        }
    }
}


In the add-in model where an add-in returns a UI (see How to: Create an Add-In That Returns a UI), the add-in adapter converted the FrameworkElement to an INativeHandleContract by calling ViewToContractAdapter. ViewToContractAdapter must also be called in this model, although you need to implement a method from which to write the code to call it. You do this by overriding QueryContract and implementing the code that calls ViewToContractAdapter if the code that is calling QueryContract is expecting an INativeHandleContract. In this case, the caller will be the host-side adapter, which is covered in a subsequent subsection.

NoteNote

You also need to override QueryContract in this model to enable tabbing between host application UI and add-in UI. For more information, see "WPF Add-In Limitations" in WPF Add-Ins Overview.

Because the add-in-side adapter implements an interface that derives from INativeHandleContract, you also need to implement GetHandle, although this is ignored when QueryContract is overridden.

Implementing the Host View Pipeline Segment

In this model, the host application typically expects the host view to be a FrameworkElement subclass. The host-side adapter must convert the INativeHandleContract to a FrameworkElement after the INativeHandleContract crosses the isolation boundary. Because a method isn't being called by the host application to get the FrameworkElement, the host view must "return" the FrameworkElement by containing it. Consequently, the host view must derive from a subclass of FrameworkElement that can contain other UIs, such as UserControl. The following code shows the host view of the contract, implemented as the WPFAddInHostView class.


using System.Windows.Controls; // UserControl

namespace HostViews
{
    /// <summary>
    /// Defines the host's view of the add-in
    /// </summary>
    public class WPFAddInHostView : UserControl { }
}


Implementing the Host-Side Adapter Pipeline Segment

While the contract is an INativeHandleContract, the host application expects a UserControl (as specified by the host view). Consequently, the INativeHandleContract must be converted to a FrameworkElement after crossing the isolation boundary, before being set as content of the host view (which derives from UserControl).

This work is performed by the host-side adapter, as shown in the following code.


using System.AddIn.Contract; // INativeHandleContract
using System.AddIn.Pipeline; // HostAdapterAttribute, FrameworkElementAdapters, ContractHandle
using System.Windows; // FrameworkElement

using Contracts; // IWPFAddInContract
using HostViews; // WPFAddInHostView

namespace HostSideAdapters
{
    /// <summary>
    /// Adapts the add-in contract to the host's view of the add-in
    /// </summary>
    [HostAdapter]
    public class WPFAddIn_ContractToViewHostSideAdapter : WPFAddInHostView
    {
        IWPFAddInContract wpfAddInContract;
        ContractHandle wpfAddInContractHandle;

        public WPFAddIn_ContractToViewHostSideAdapter(IWPFAddInContract wpfAddInContract)
        {
            // Adapt the contract (IWPFAddInContract) to the host application's
            // view of the contract (WPFAddInHostView)
            this.wpfAddInContract = wpfAddInContract;

            // Prevent the reference to the contract from being released while the
            // host application uses the add-in
            this.wpfAddInContractHandle = new ContractHandle(wpfAddInContract);

            // Convert the INativeHandleContract for the add-in UI that was passed 
            // from the add-in side of the isolation boundary to a FrameworkElement
            string aqn = typeof(INativeHandleContract).AssemblyQualifiedName;
            INativeHandleContract inhc = (INativeHandleContract)wpfAddInContract.QueryContract(aqn);
            FrameworkElement fe = (FrameworkElement)FrameworkElementAdapters.ContractToViewAdapter(inhc);

            // Add FrameworkElement (which displays the UI provided by the add-in) as
            // content of the view (a UserControl)
            this.Content = fe;
        }
    }
}


As you can see, the host-side adapter acquires the INativeHandleContract by calling the add-in-side adapter's QueryContract method (this is the point where the INativeHandleContract crosses the isolation boundary).

The host-side adapter then converts the INativeHandleContract to a FrameworkElement by calling ContractToViewAdapter. Finally, the FrameworkElement is set as the content of the host view.

Implementing the Add-In

With the add-in-side adapter and add-in view in place, the add-in can be implemented by deriving from the add-in view, as shown in the following code.


    <addInViews:WPFAddInView
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:addInViews="clr-namespace:AddInViews;assembly=AddInViews"
    x:Class="WPFAddIn1.AddInUI">

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

</addInViews:WPFAddInView>



using System.AddIn; // AddInAttribute
using System.Windows; // MessageBox, RoutedEventArgs

using AddInViews; // WPFAddInView

namespace WPFAddIn1
{
    /// <summary>
    /// Implements the add-in by deriving from WPFAddInView
    /// </summary>
    [AddIn("WPF Add-In 1")]
    public partial class AddInUI : WPFAddInView
    {
        public AddInUI()
        {
            InitializeComponent();
        }

        void clickMeButton_Click(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("Hello from WPFAddIn1");
        }
    }
}


From this example, you can see one interesting benefit of this model: add-in developers only need to implement the add-in (since it is the UI as well), rather than both an add-in class and an add-in UI.

Implementing the Host Application

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


// Get add-in pipeline folder (the folder in which this application was launched from)
string appPath = Environment.CurrentDirectory;

// Rebuild visual add-in pipeline
string[] warnings = AddInStore.Rebuild(appPath);
if (warnings.Length > 0)
{
    string msg = "Could not rebuild pipeline:";
    foreach (string warning in warnings) msg += "\n" + warning;
    MessageBox.Show(msg);
    return;
}

// Activate add-in with Internet zone security isolation
Collection<AddInToken> addInTokens = AddInStore.FindAddIns(typeof(WPFAddInHostView), appPath);
AddInToken wpfAddInToken = addInTokens[0];
this.wpfAddInHostView = wpfAddInToken.Activate<WPFAddInHostView>(AddInSecurityLevel.Internet);

// Display add-in UI
this.addInUIHostGrid.Children.Add(this.wpfAddInHostView);


The host application uses typical .NET Framework add-in model code to activate the add-in, which implicitly returns the host view to the host application. The host application subsequently displays the host view (which is a UserControl) from a Grid.

The code for processing interactions with the add-in UI runs in the add-in's application domain. These interactions include the following:

This activity is completely isolated from the host application.

Was this page helpful?
(1500 characters remaining)
Thank you for your feedback
Show:
© 2014 Microsoft