Cómo: Crear un complemento que sea una interfaz de usuario

En este ejemplo se muestra cómo crear un complemento que es una aplicación Windows Presentation Foundation (WPF) hospedada por una aplicación WPF independiente.

El complemento es una interfaz de usuario que es un control de usuario de WPF. El contenido del control de usuario es un botón único que muestra un cuadro de mensaje cuando se hace clic en él. La aplicación WPF independiente hospeda la interfaz de usuario de complemento como contenido de la ventana de la aplicación principal.

Requisitos previos

En este ejemplo se resaltan las extensiones de WPF del modelo de complementos de .NET Framework que habilitan este escenario, y se presupone lo siguiente:

Ejemplo

Para crear un complemento que es una interfaz de usuario de WPF, se necesita un código concreto para cada segmento de la canalización, el complemento y la aplicación host.

Implementar el segmento de canalización del contrato

Cuando un complemento es una interfaz de usuario, el contrato para el complemento debe implementar INativeHandleContract. En el ejemplo, IWPFAddInContract implementa INativeHandleContract, como se muestra en el código siguiente.

using System.AddIn.Contract;
using System.AddIn.Pipeline;

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 {}
}

Imports System.AddIn.Contract
Imports System.AddIn.Pipeline

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
        Inherits INativeHandleContract
        Inherits IContract
    End Interface
End Namespace

Implementar el segmento de canalización de la vista de complemento

Dado que el complemento se implementa como una subclase del tipo FrameworkElement, la vista de complemento también debe crear subclases de FrameworkElement. En el código siguiente se muestra la vista de complemento del contrato, implementada como la clase WPFAddInView.

using System.AddIn.Pipeline;
using System.Windows.Controls;

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

Imports System.AddIn.Pipeline
Imports System.Windows.Controls

Namespace AddInViews
    ''' <summary>
    ''' Defines the add-in's view of the contract.
    ''' </summary>
    <AddInBase>
    Public Class WPFAddInView
        Inherits UserControl
    End Class
End Namespace

Aquí, la vista de complemento se deriva de UserControl. Por consiguiente, la interfaz de usuario del complemento también debe derivar de UserControl.

Implementar el segmento de canalización del adaptador de conversión

Aunque el contrato es INativeHandleContract, el complemento es un FrameworkElement (como se especifica en el segmento de canalización de la vista de complemento). Por lo tanto, el objeto FrameworkElement se debe convertir en una interfaz INativeHandleContract antes de cruzar el límite de aislamiento. Este trabajo lo realiza el adaptador de conversión mediante una llamada a ViewToContractAdapter, como se muestra en el código siguiente.

using System;
using System.AddIn.Contract;
using System.AddIn.Pipeline;
using System.Security.Permissions;

using AddInViews;
using Contracts;

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 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, that is, 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>
        public IntPtr GetHandle()
        {
            return FrameworkElementAdapters.ViewToContractAdapter(this.wpfAddInView).GetHandle();
        }
    }
}

Imports System
Imports System.AddIn.Contract
Imports System.AddIn.Pipeline
Imports System.Security.Permissions

Imports AddInViews
Imports Contracts

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 WPFAddInView

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

        ''' <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 Overrides Function QueryContract(ByVal contractIdentifier As String) As IContract
            If contractIdentifier.Equals(GetType(INativeHandleContract).AssemblyQualifiedName) Then
                Return FrameworkElementAdapters.ViewToContractAdapter(Me.wpfAddInView)
            End If

            Return MyBase.QueryContract(contractIdentifier)
        End Function

        ''' <summary>
        ''' GetHandle is called by the WPF add-in model from the host application's 
        ''' application domain 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, that is, overriding ContractBase.QueryContract, 
        ''' as shown above.
        ''' </summary>
        Public Function GetHandle() As IntPtr Implements INativeHandleContract.GetHandle
            Return FrameworkElementAdapters.ViewToContractAdapter(Me.wpfAddInView).GetHandle()
        End Function

    End Class
End Namespace

En el modelo de complemento, donde un complemento devuelve una interfaz de usuario (vea Crear un complemento que devuelva una interfaz de usuario), el adaptador de complemento convierte el elemento FrameworkElement en una interfaz INativeHandleContract mediante una llamada al método ViewToContractAdapter. En este modelo también es necesario llamar al método ViewToContractAdapter, aunque se debe implementar un método desde el que escribir el código para llamarlo. Para ello, invalide QueryContract e implemente el código que llama a ViewToContractAdapter si el código que está llamando a QueryContract espera una interfaz INativeHandleContract. En este caso, el autor de la llamada será el adaptador del host, que se aborda en una subsección siguiente.

Nota:

También necesita invalidar QueryContract en este modelo para permitir la tabulación entre la interfaz de usuario de la aplicación host y la interfaz de usuario del complemento. Para obtener más información, vea "Limitaciones de los complementos de WPF" en Información general sobre los complementos de WPF.

Dado que el adaptador de conversión implementa una interfaz que deriva de INativeHandleContract, también debe implementar GetHandle, aunque esto se omite cuando se invalida QueryContract.

Implementar el segmento de canalización de la vista host

En este modelo, la aplicación host suele esperar que la vista host sea una subclase de FrameworkElement. El adaptador del host debe convertir la interfaz INativeHandleContract en un objeto FrameworkElement después de que la interfaz INativeHandleContract cruce el límite de aislamiento. Como la aplicación host no llama a un método para obtener el objeto FrameworkElement, la vista host debe contener el objeto FrameworkElement para poder "devolverlo". Por consiguiente, la vista host debe derivar de una subclase de FrameworkElement que pueda contener otras interfaces de usuario, como UserControl. En el código siguiente se muestra la vista host del contrato, implementada como la clase WPFAddInHostView.

using System.Windows.Controls;

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

Imports System.Windows.Controls

Namespace HostViews
    ''' <summary>
    ''' Defines the host's view of the add-in
    ''' </summary>
    Public Class WPFAddInHostView
        Inherits UserControl
    End Class
End Namespace

Implementar el segmento de canalización del adaptador del host

Aunque el contrato es una interfaz INativeHandleContract, la aplicación host espera un objeto UserControl (según lo especificado por la vista host). Por consiguiente, la interfaz INativeHandleContract se debe convertir en un objeto FrameworkElement después de cruzar el límite de aislamiento, antes de que se establezca como contenido de la vista host (que deriva de UserControl).

Este trabajo lo realiza el adaptador del host, como se muestra en el código siguiente.

using System.AddIn.Contract;
using System.AddIn.Pipeline;
using System.Windows;

using Contracts;
using HostViews;

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;
        }
    }
}

Imports System.AddIn.Contract
Imports System.AddIn.Pipeline
Imports System.Windows

Imports Contracts
Imports HostViews

Namespace HostSideAdapters
    ''' <summary>
    ''' Adapts the add-in contract to the host's view of the add-in
    ''' </summary>
    <HostAdapter>
    Public Class WPFAddIn_ContractToViewHostSideAdapter
        Inherits WPFAddInHostView
        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 (WPFAddInHostView)
            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)

            ' Convert the INativeHandleContract for the add-in UI that was passed 
            ' from the add-in side of the isolation boundary to a FrameworkElement
            Dim aqn As String = GetType(INativeHandleContract).AssemblyQualifiedName
            Dim inhc As INativeHandleContract = CType(wpfAddInContract.QueryContract(aqn), INativeHandleContract)
            Dim fe As FrameworkElement = CType(FrameworkElementAdapters.ContractToViewAdapter(inhc), FrameworkElement)

            ' Add FrameworkElement (which displays the UI provided by the add-in) as
            ' content of the view (a UserControl)
            Me.Content = fe
        End Sub
    End Class
End Namespace

Como puede ver, el adaptador del host adquiere la interfaz INativeHandleContract llamando al método QueryContract del adaptador de conversión (este es el punto donde INativeHandleContract cruza el límite de aislamiento).

Después, el adaptador del host convierte la interfaz INativeHandleContract en un objeto FrameworkElement llamando a ContractToViewAdapter. Finalmente, se establece el objeto FrameworkElement como el contenido de la vista host.

Implementar el complemento

Con el adaptador y la vista de conversión en su lugar, se puede implementar el complemento derivando de la vista de complemento, como se muestra en el código siguiente.

using System.AddIn;
using System.Windows;

using AddInViews;

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");
        }
    }
}

Imports System.AddIn
Imports System.Windows

Imports AddInViews

Namespace WPFAddIn1
    ''' <summary>
    ''' Implements the add-in by deriving from WPFAddInView
    ''' </summary>
    <AddIn("WPF Add-In 1")>
    Partial Public Class AddInUI
        Inherits WPFAddInView
        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

En este ejemplo, se puede ver una ventaja interesante de este modelo: los desarrolladores de complementos solamente necesitan implementar el complemento (dado que también es la interfaz de usuario), en lugar de una clase de complemento y una interfaz de usuario del complemento.

Implementación de la aplicación host

Con el adaptador y la vista host creados, la aplicación host puede usar el modelo de complementos de .NET Framework para abrir la canalización y adquirir una vista host del complemento. Estos pasos se muestran en el código siguiente.

// 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);
' 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(WPFAddInHostView), appPath)
Dim wpfAddInToken As AddInToken = addInTokens(0)
Me.wpfAddInHostView = wpfAddInToken.Activate(Of WPFAddInHostView)(AddInSecurityLevel.Internet)

' Display add-in UI
Me.addInUIHostGrid.Children.Add(Me.wpfAddInHostView)

La aplicación host usa el código típico del modelo de complementos de .NET Framework para activar el complemento, lo que implícitamente devuelve la vista host a la aplicación host. Después, la aplicación host muestra la vista host (que es un control UserControl) de un control Grid.

El código para procesar las interacciones con la interfaz de usuario del complemento se ejecuta en el dominio de aplicación del complemento. Estas interacciones incluyen lo siguiente:

Esta actividad está completamente aislada de la aplicación host.

Vea también