Compartir a través de


Cómo crear un Add-In que sea una interfaz de usuario

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

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

Requisitos previos

En este ejemplo se resaltan las extensiones de WPF en el modelo de complemento de .NET Framework que habilita este escenario y se supone lo siguiente:

Ejemplo

Para crear un complemento que sea una interfaz de usuario de WPF, se requiere código específico para cada segmento de canalización, el complemento y la aplicación host.

Implementación del segmento de canalización de contrato

Cuando un complemento es una interfaz de usuario, el contrato del 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

Implementación del segmento de canalización de vista Add-In

Dado que el complemento se implementa como una subclase del tipo FrameworkElement, la vista del complemento también debe subclasear FrameworkElement. El código siguiente muestra la vista de complemento del contrato, implementada como 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 del complemento se deriva de UserControl. Por lo tanto, la interfaz de usuario del complemento también debe derivar de UserControl.

Implementación del segmento de canalización Add-In-Side Adapter

Aunque el contrato es un INativeHandleContract, el complemento es un FrameworkElement (según lo especificado por el segmento de canalización de vista de complemento). Por lo tanto, FrameworkElement debe convertirse en un INativeHandleContract antes de cruzar el límite de aislamiento. Este trabajo lo realiza el adaptador del complemento llamando 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 en el que un complemento devuelve una interfaz de usuario (vea Crear un Add-In que devuelve una interfaz de usuario), el adaptador del complemento ha convertido FrameworkElement en un INativeHandleContract mediante la llamada a ViewToContractAdapter. ViewToContractAdapter también debe llamarse en este modelo, aunque necesita implementar un método para escribir el código que lo llame. Para ello, sobrescriba QueryContract e implemente el código que llama a ViewToContractAdapter si el código que llama a QueryContract espera un INativeHandleContract. En este caso, el autor de la llamada será el adaptador del lado host, que se trata en una subsección posterior.

Nota:

También debe sobrescribir QueryContract en este modelo para habilitar la navegación con el tabulador 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 Add-In de WPF" en información general sobre wpF Add-Ins.

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

Implementación del segmento de canalización de visualización del host

En este modelo, la aplicación anfitriona normalmente anticipa que la vista anfitriona sea una FrameworkElement subclase. El adaptador del lado del host debe convertir el INativeHandleContract en un FrameworkElement después de que el INativeHandleContract cruce el límite de aislamiento. Dado que la aplicación host no llama a un método para obtener el FrameworkElement, la vista host debe contener el FrameworkElement. Por lo tanto, la vista host debe derivar de una subclase de FrameworkElement que puede contener otras interfaces de usuario, como UserControl. El código siguiente muestra la vista host del contrato, implementada como la WPFAddInHostView clase .

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

Implementación del segmento de canalización del adaptador de Host-Side

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

Este trabajo lo realiza el adaptador del lado 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 usted puede ver, el adaptador del lado host adquiere el INativeHandleContract llamando al método del adaptador del lado complemento QueryContract (este es el punto donde el INativeHandleContract cruza el límite de aislamiento).

El adaptador del lado del host convierte INativeHandleContract en FrameworkElement mediante la llamada a ContractToViewAdapter. Por último, FrameworkElement se establece como el contenido de la vista host.

Implementación del Add-In

Con el adaptador del lado del complemento y la vista del complemento en su lugar, el complemento se puede implementar derivando a partir de la vista del complemento, como se muestra en el siguiente fragmento de código.

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, puede ver una ventaja interesante de este modelo: los desarrolladores de complementos solo necesitan implementar el complemento (ya que también es la interfaz de usuario), en lugar de una clase de complemento y una interfaz de usuario de complemento.

Implementación de la aplicación host

Con el adaptador del lado host y la vista host creadas, la aplicación host puede usar el modelo de complemento 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 complemento de .NET Framework para activar el complemento, que devuelve implícitamente la vista host a la aplicación host. La aplicación host muestra posteriormente la vista host (que es un UserControl) desde un Grid.

El código para procesar 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.

Consulte también