Cómo: Crear un complemento que sea una interfaz de usuario
En este ejemplo se muestra cómo crear un complemento que es una user interface (UI) de Windows Presentation Foundation (WPF) hospedada por una aplicación WPF independiente.
El complemento es una UI 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 UI 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 supone lo siguiente:
Conocimientos del modelo de complementos de .NET Framework, lo que incluye la programación de canalizaciones, complementos y host. Si no está familiarizado con estos conceptos, vea Complementos y extensibilidad. Para obtener un tutorial que muestra la implementación de una canalización, un complemento y una aplicación host, vea Tutorial: Crear una aplicación extensible.
Conocimientos de las extensiones de WPF del modelo de complementos de .NET Framework, que encontrará en Información general sobre los complementos de WPF.
Ejemplo
Para crear un complemento que es una UI 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 UI, el contrato para el complemento debe implementar INativeHandleContract. En el ejemplo, IWPFAddInContract implementa INativeHandleContract, como se muestra en el código siguiente.
Imports System.AddIn.Contract ' INativeHandleContract
Imports 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
Inherits INativeHandleContract
Inherits IContract
End Interface
End Namespace
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 {}
}
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.
Imports System.AddIn.Pipeline ' AddInBaseAttribute
Imports System.Windows.Controls ' UserControl
Namespace AddInViews
''' <summary>
''' Defines the add-in's view of the contract.
''' </summary>
<AddInBase>
Public Class WPFAddInView
Inherits UserControl
End Class
End Namespace
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 { }
}
Aquí, la vista de complemento se deriva de UserControl. Por consiguiente, la UI 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 elemento FrameworkElement (como se especifica en el segmento de canalización de la vista de complemento). Por consiguiente, 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.
Imports System ' IntPtr
Imports System.AddIn.Contract ' INativeHandleContract
Imports System.AddIn.Pipeline ' AddInAdapterAttribute, FrameworkElementAdapters, ContractBase
Imports System.Security.Permissions
Imports AddInViews ' WPFAddInView
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 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 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 Function GetHandle() As IntPtr Implements INativeHandleContract.GetHandle
Return FrameworkElementAdapters.ViewToContractAdapter(Me.wpfAddInView).GetHandle()
End Function
End Class
End Namespace
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();
}
}
}
En el modelo de complemento, donde un complemento devuelve una UI (vea Cómo: 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 llamador 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 UI de la aplicación host y la UI 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 de host
En este modelo, la aplicación host suele esperar que la vista de 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. Puesto que la aplicación host no llama a un método para obtener el objeto FrameworkElement, la vista de host debe contener el objeto FrameworkElement para poder "devolverlo". Por consiguiente, la vista de host debe derivar de una subclase de FrameworkElement que pueda contener otras UIs, como UserControl. En el código siguiente se muestra la vista de host del contrato, implementada como la clase WPFAddInHostView.
Imports System.Windows.Controls ' UserControl
Namespace HostViews
''' <summary>
''' Defines the host's view of the add-in
''' </summary>
Public Class WPFAddInHostView
Inherits UserControl
End Class
End Namespace
using System.Windows.Controls; // UserControl
namespace HostViews
{
/// <summary>
/// Defines the host's view of the add-in
/// </summary>
public class WPFAddInHostView : UserControl { }
}
Implementar el segmento de canalización del adaptador del host
Si bien el contrato es una interfaz INativeHandleContract, la aplicación host espera un objeto UserControl (según lo especificado por la vista de 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 de host (que deriva de UserControl).
Este trabajo lo realiza el adaptador del host, como se muestra en el código siguiente.
Imports System.AddIn.Contract ' INativeHandleContract
Imports System.AddIn.Pipeline ' HostAdapterAttribute, FrameworkElementAdapters, ContractHandle
Imports System.Windows ' FrameworkElement
Imports Contracts ' IWPFAddInContract
Imports 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
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
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;
}
}
}
Como puede ver, el adaptador del host adquiere la interfaz INativeHandleContract llamando al método QueryContract del adaptador de conversión (éste es el punto donde INativeHandleContract cruza el límite de aislamiento).
A continuación, 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 de 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.
<addInViews:WPFAddInView
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://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>
Imports System.AddIn ' AddInAttribute
Imports System.Windows ' MessageBox, RoutedEventArgs
Imports AddInViews ' WPFAddInView
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
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");
}
}
}
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 UI ), en lugar de una clase de complemento y una UI del complemento.
Implementar la aplicación host
Con el adaptador y la vista de host creados, la aplicación host puede utilizar el modelo de complementos de .NET Framework para abrir la canalización y adquirir una vista de 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)
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)
// 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);
La aplicación host utiliza el código típico del modelo de complementos de .NET Framework para activar el complemento, lo que implícitamente devuelve la vista de host a la aplicación host. A continuación, la aplicación host muestra la vista de host (que es un control UserControl) de un control Grid.
El código para procesar las interacciones con la UI del complemento se ejecuta en el dominio de aplicación del complemento. Estas interacciones incluyen lo siguiente:
Mostrar el objeto MessageBox.
Esta actividad está completamente aislada de la aplicación host.