Поделиться через


Как создать Add-In, который является пользовательским интерфейсом

В этом примере показано, как создать надстройку, которая представляет собой Windows Presentation Foundation (WPF), размещенную автономным приложением WPF.

Надстройка представляет собой интерфейс пользователя в виде элемента управления WPF. Содержимое пользовательского элемента управления — это одна кнопка, которая при нажатии отображает окно сообщения. Автономное приложение WPF размещает пользовательский интерфейс надстройки в качестве содержимого основного окна приложения.

Предварительные условия

В этом примере выделены расширения WPF для модели надстроек .NET Framework, которая включает этот сценарий, и предполагается следующее:

Пример

Для создания надстройки, которая является пользовательским интерфейсом WPF, требуется определенный код для каждого сегмента конвейера, надстройки и ведущего приложения.

Реализация сегмента конвейера контракта

Если надстройка является пользовательским интерфейсом, контракт надстройки должен реализовать INativeHandleContract. В примере IWPFAddInContract реализует INativeHandleContract, как показано в следующем коде.

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

Внедрение сегмента конвейера представления Add-In

Так как надстройка реализуется как подкласс типа FrameworkElement, представление надстройки также должно быть подклассом FrameworkElement. В следующем коде представлено представление дополнения контракта, реализованное как класс 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

Здесь представление надстройки выводится как производное от UserControl. Следовательно, пользовательский интерфейс надстройки также должен быть производным от UserControl.

Реализация сегмента конвейера адаптера Add-In-Side

Хотя контракт является INativeHandleContract, надстройка представляет собой FrameworkElement (указано в сегменте канала представления надстройки). Поэтому FrameworkElement необходимо преобразовать в INativeHandleContract перед пересечением границы изоляции. Эта работа выполняется адаптером на стороне надстройки путем вызова ViewToContractAdapter, как показано в следующем коде.

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

В модели надстройки, в которой надстройка возвращает пользовательский интерфейс (см. раздел Создание Add-In, возвращающегопользовательского интерфейса), адаптер надстройки преобразовал FrameworkElement в INativeHandleContract путем вызова ViewToContractAdapter. ViewToContractAdapter также должен вызываться в этой модели, хотя необходимо реализовать метод, из которого необходимо написать код для вызова. Для этого необходимо переопределить QueryContract и реализовать код, который вызывает ViewToContractAdapter, если код, вызывающий QueryContract, ожидает INativeHandleContract. В этом случае вызывающая сторона будет адаптером на стороне хоста, который рассматривается в следующем подразделе.

Заметка

Кроме того, необходимо переопределить QueryContract в этой модели, чтобы включить переключение между пользовательским интерфейсом ведущего приложения и пользовательским интерфейсом надстройки. Для получения дополнительной информации см. в разделе «Ограничения Add-In WPF» в обзоре Add-InsWPF .

Поскольку адаптер надстройки реализует интерфейс, производный от INativeHandleContract, необходимо также реализовать GetHandle, хотя это требование можно не учитывать при переопределении QueryContract.

Реализация сегмента конвейера представления хоста

В этой модели основное приложение обычно ожидает, что представление хоста будет подклассом FrameworkElement. Адаптер на стороне узла должен преобразовать INativeHandleContract в FrameworkElement после того, как INativeHandleContract пересекает границу изоляции. Так как метод не вызывается ведущим приложением для получения FrameworkElement, представление узла должно возвращать FrameworkElement, содержая его. Следовательно, представление хоста должно быть производным от подкласса FrameworkElement, который может содержать другие UI, например, UserControl. В следующем коде показано хост-вью контракта, реализованное как класс 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

Реализация сегмента конвейера адаптера Host-Side

Хотя контракт является INativeHandleContract, основное приложение ожидает UserControl (как указано в хост-просмотре). Следовательно, INativeHandleContract необходимо преобразовать в FrameworkElement после пересечения границы изоляции, прежде чем задавать в качестве содержимого представления узла (который является производным от UserControl).

Эта работа выполняется адаптером на стороне узла, как показано в следующем коде.

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

Как видно, адаптер на стороне хоста вызывает метод QueryContract адаптера на стороне надстройки для получения INativeHandleContract (это точка, в которой INativeHandleContract пересекает границу изоляции).

Затем адаптер на стороне узла преобразует INativeHandleContract в FrameworkElement путем вызова ContractToViewAdapter. Наконец, FrameworkElement задается в качестве содержимого хост-представления.

Реализация Add-In

С помощью адаптера на стороне надстройки и представления надстроек надстройка может быть реализована путем извлечения из представления надстройки, как показано в следующем коде.

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

В этом примере можно увидеть одно интересное преимущество этой модели: разработчикам надстроек необходимо реализовать надстройку (так как это пользовательский интерфейс, а не класс надстройки и пользовательский интерфейс надстройки).

Реализация ведущего приложения

При создании адаптера на стороне хоста и представления хоста хост-приложение может использовать модель надстройки .NET Framework для открытия конвейера и получения представления хоста надстройки. Эти шаги показаны в следующем коде.

// 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)

Хост-приложение использует типичный код модели надстроек .NET Framework для активации надстройки, которая неявно возвращает представление хоста в хост-приложение. Хост-приложение впоследствии отображает хост-вид (который является UserControl) из Grid.

Код для обработки взаимодействий с пользовательским интерфейсом надстройки выполняется в домене приложения надстройки. Эти взаимодействия включают в себя следующее:

Это действие полностью изолировано от ведущего приложения.

См. также