如何:创建作为 UI 的外接程序

本示例演示如何创建一个作为由 WPF 独立应用程序承载的 Windows Presentation Foundation (WPF) user interface (UI) 的外接程序。

该外接程序是一个作为 WPF 用户控件的 UI。 该用户控件的内容为单个按钮,单击该按钮时将显示一个消息框。 WPF 独立应用程序将该外接程序 UI 作为主应用程序窗口的内容加以承载。

先决条件

本示例重点演示对 .NET Framework 外接程序模型进行的支持此方案的 WPF 扩展,并假设您具备以下条件:

  • 了解 .NET Framework 外接程序模型的相关知识,包括管线、外接程序和宿主开发。 如果您对这些概念不熟悉,请参见外接程序和扩展性。 有关演示如何实现管线、外接程序和宿主应用程序的教程,请参见演练:创建可扩展的应用程序

  • 了解 .NET Framework 外接程序模型的 WPF 扩展的相关知识,这些知识可在以下位置找到:WPF 外接程序概述

示例

要创建作为 WPF UI 的外接程序,需要为每个管线段、外接程序和宿主应用程序编写特定代码。

实现协定管线段

当外接程序为 UI 时,外接程序的协定必须实现 INativeHandleContract。 在本示例中,IWPFAddInContract 实现 INativeHandleContract,如下面的代码所示。


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

实现外接程序视图管线段

由于外接程序实现为 FrameworkElement 类型的子类,因此外接程序视图也必须创建 FrameworkElement 的子类。 下面的代码演示实现为 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 { }
}

此示例中,外接程序视图从 UserControl 派生。 因此,外接程序 UI 也应从 UserControl 派生。

实现外接程序端适配器管线段

协定是一个 INativeHandleContract,而外接程序是一个 FrameworkElement(如外接程序视图管线段所指定的那样)。 因此,在越过隔离边界之前,必须将 FrameworkElement 转换为 INativeHandleContract。 此任务由外接程序端适配器通过调用 ViewToContractAdapter 完成,如下面的代码所示。


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

在外接程序返回 UI 的外接程序模型中(请参见如何:创建返回 UI 的外接程序),外接程序适配器通过调用 ViewToContractAdapterFrameworkElement 转换为 INativeHandleContract。 在此模型中还必须调用 ViewToContractAdapter,不过您需要实现一个方法,并在此方法中编写调用它的代码。 为此,需要重写 QueryContract 并实现调用 ViewToContractAdapter 的代码(如果调用 QueryContract 的代码需要获得 INativeHandleContract)。 在这种情况下,调用方将是宿主端适配器。在下面的小节中将介绍宿主端适配器。

注意注意

在此模型中,还需重写 QueryContract 以在宿主应用程序 UI 和外接程序 UI 之间实现 Tab 键切换功能。有关更多信息,请参见 WPF 外接程序概述中的“WPF 外接程序限制”。

由于外接程序端适配器实现了派生自 INativeHandleContract 的接口,因此您还需要实现 GetHandle,尽管在重写 QueryContract 时会忽略此方法。

实现宿主视图管线段

在此模型中,宿主应用程序通常认为宿主视图是 FrameworkElement 子类。 在 INativeHandleContract 越过隔离边界之后,宿主端适配器必须将 INativeHandleContract 转换为 FrameworkElement。 由于宿主应用程序没有通过调用方法来获取 FrameworkElement,因此宿主视图必须通过包含 FrameworkElement 的方式将其“返回”。 这样,宿主视图必须从可包含其他 UIs(例如 UserControl)的 FrameworkElement 的子类派生。 下面的代码演示实现为 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 { }
}

实现宿主端适配器管线段

协定是一个 INativeHandleContract,而宿主应用程序需要 UserControl(如宿主视图指定的那样)。 因此,在 INativeHandleContract 越过隔离边界之后,被设置为宿主视图(派生自 UserControl)的内容之前,必须将其转换为 FrameworkElement

此任务由宿主端适配器完成,如下面的代码所示。


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

如您所看到的那样,宿主端适配器通过调用外接程序端适配器的 QueryContract 方法(这是 INativeHandleContract 越过隔离边界的点)获取 INativeHandleContract

然后宿主端适配器通过调用 ContractToViewAdapterINativeHandleContract 转换为 FrameworkElement。 最后,将 FrameworkElement 设置为宿主视图的内容。

实现外接程序

具备外接程序端适配器和外接程序视图后,就可以通过从外接程序视图派生来实现外接程序,如下面的代码所示。

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

从此示例中可以看到,该模型的一个突出的优点是,外接程序开发人员只需实现外接程序(因为它同时也是 UI),而无需同时实现外接程序类和外接程序 UI。

实现宿主应用程序

创建宿主端适配器和宿主视图后,宿主应用程序就可以使用 .NET Framework 外接程序模型来打开管线,并获得外接程序的宿主视图。 下面的代码演示这些步骤。

' 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);

宿主应用程序使用典型的 .NET Framework 外接程序模型代码来激活外接程序,外接程序隐式向宿主应用程序返回宿主视图。 然后宿主应用程序显示从 Grid 获得的宿主视图(宿主视图为一个 UserControl)。

用于处理与外接程序 UI 交互的代码在外接程序的应用程序域运行。 这些交互包括以下内容:

此活动完全独立于宿主应用程序。

请参见

概念

外接程序和扩展性

WPF 外接程序概述