다음을 통해 공유


방법: 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입니다. 따라서 격리 경계를 통과하기 전에 FrameworkElementINativeHandleContract로 변환해야 합니다. 다음 코드와 같이 이 작업은 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를 호출하여 FrameworkElementINativeHandleContract로 변환했습니다. 이 모델에서는 ViewToContractAdapter도 호출해야 하지만 이를 호출하기 위한 코드를 작성하는 메서드를 구현해야 합니다. QueryContract를 호출하는 코드에 INativeHandleContract가 필요한 경우 QueryContract를 재정의하고 ViewToContractAdapter를 호출하는 코드를 구현하여 이 작업을 수행합니다. 이 경우 호출자가 호스트측 어댑터가 됩니다. 이에 대해서는 이후의 하위 단원에서 설명합니다.

참고참고

또한 이 모델에서는 QueryContract를 재정의하여 호스트 응용 프로그램 UI와 추가 기능 UI 간의 탭 기능을 활성화해야 합니다.자세한 내용은 WPF 추가 기능 개요의 "WPF 추가 기능 제한"을 참조하십시오.

추가 기능측 어댑터가 INativeHandleContract에서 파생되는 인터페이스를 구현하므로 GetHandle도 구현해야 하지만 QueryContract를 재정의할 때는 이 메서드가 무시됩니다.

호스트 뷰 파이프라인 세그먼트 구현

이 모델에서 호스트 응용 프로그램은 대개 호스트 뷰가 FrameworkElement 서브클래스라고 가정합니다. 호스트측 어댑터는 INativeHandleContract가 격리 경계를 통과한 후 INativeHandleContractFrameworkElement로 변환해야 합니다. 호스트 응용 프로그램에서는 FrameworkElement 개체를 가져오기 위해 메서드를 호출하지 않으므로 호스트 뷰가 해당 개체를 포함하여 FrameworkElement를 "반환"해야 합니다. 결과적으로 호스트 뷰가 UserControl과 같이 다른 UIs를 포함할 수 있는 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이 필요합니다. 따라서 격리 경계를 통과한 후 호스트 뷰의 콘텐츠(UserControl에서 파생됨)로 설정하기 전에 INativeHandleContractFrameworkElement로 변환해야 합니다.

다음 코드와 같이 이 작업은 호스트측 어댑터로 수행됩니다.


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를 호출하여 INativeHandleContractFrameworkElement로 변환합니다. 마지막으로 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 추가 기능 개요