如何:创建作为 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 的外接程序),外接程序适配器通过调用 ViewToContractAdapter 将 FrameworkElement 转换为 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。
然后宿主端适配器通过调用 ContractToViewAdapter 将 INativeHandleContract 转换为 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 交互的代码在外接程序的应用程序域运行。 这些交互包括以下内容:
显示 MessageBox。
此活动完全独立于宿主应用程序。