Gewusst wie: Erstellen eines Add-Ins, das eine Benutzeroberfläche ist
Dieses Beispiel zeigt, wie Sie ein Add-In erstellen, bei dem es sich um eine Windows Presentation Foundation (WPF)-user interface (UI) handelt, die von einer eigenständigen WPF-Anwendung gehostet wird.
Das Add-In ist eine UI, bei der es sich um ein WPF-Benutzersteuerelement handelt. Der Inhalt des Benutzersteuerelements ist eine einzelne Schaltfläche, bei der ein Meldungsfeld angezeigt wird, wenn Benutzer darauf klicken. Die eigenständige WPF-Anwendung hostet die Add-In-UI als Inhalt des Hauptfensters der Anwendung.
Vorbereitungsmaßnahmen
Dieses Beispiel zeigt die WPF-Erweiterungen des .NET Framework-Add-In-Modells, die dieses Szenario ermöglichen. Dabei wird Folgendes vorausgesetzt:
Kenntnis des .NET Framework-Add-In-Modells, einschließlich Pipeline-, Add-In- und Hostentwicklung. Wenn Sie mit diesen Begriffen nicht vertraut sind, finden Sie weitere Informationen unter Add-Ins und Erweiterbarkeit. Ein Lernprogramm, das die Implementierung einer Pipeline, eines Add-Ins und einer Hostanwendung veranschaulicht, finden Sie unter Exemplarische Vorgehensweise: Erstellen von erweiterbaren Anwendungen.
Kenntnis der WPF-Erweiterungen des .NET Framework-Add-In-Modells. Informationen finden Sie unter: Übersicht über WPF-Add-Ins.
Beispiel
Zum Erstellen eines Add-Ins, bei dem es sich um eine WPF-UI handelt, ist spezieller Code für die einzelnen Pipelinesegmente, das Add-In und die Hostanwendung erforderlich.
Implementieren des Vertragspipelinesegments
Handelt es sich bei einem Add-In um eine UI, muss der Vertrag für das Add-In INativeHandleContract implementieren. In diesem Beispiel wird durch IWPFAddInContract INativeHandleContract implementiert, wie in folgendem Code gezeigt.
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 {}
}
Implementieren des Add-In-Ansichtspipelinesegments
Da das Add-In als Unterklasse des FrameworkElement-Typs implementiert wird, muss auch die Add-In-Ansicht FrameworkElement als Unterklasse verwenden. Im folgenden Code wird die als WPFAddInView-Klasse implementierte Add-In-Ansicht des Vertrags gezeigt.
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 { }
}
Hier wird die Add-In-Ansicht von UserControl abgeleitet. Daher sollte die UI des Add-Ins auch von UserControl abgeleitet werden.
Implementieren des Add-In-seitigen Adapterpipelinesegments
Bei dem Vertrag handelt es sich um INativeHandleContract, das Add-In entspricht jedoch FrameworkElement (wie durch das Add-In-Ansichtspipelinesegment festgelegt). Daher muss FrameworkElement vor dem Überschreiten der Isolationsgrenze in INativeHandleContract umgewandelt werden. Dieser Vorgang wird vom Add-In-seitigen Adapter durch Aufrufen von ViewToContractAdapter ausgeführt, wie im folgenden Code gezeigt.
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();
}
}
}
Im Add-In-Modell, in dem ein Add-In eine UI zurückgibt (siehe Gewusst wie: Erstellen eines Add-Ins, das eine Benutzeroberfläche zurückgibt), hat der Add-In-Adapter das FrameworkElement in einen INativeHandleContract umgewandelt, indem die Methode ViewToContractAdapter aufgerufen wurde. ViewToContractAdapter muss in diesem Modell ebenfalls aufgerufen werden, obwohl Sie eine Methode implementieren müssen, in der Sie den Code für den Aufruf schreiben müssen. Dazu überschreiben Sie QueryContract und implementieren den Code, der ViewToContractAdapter aufruft, wenn der Code, der QueryContract aufruft, INativeHandleContract erwartet. In diesem Fall ist der Aufrufer der hostseitige Adapter, der in einem nachfolgenden Unterabschnitt behandelt wird.
Hinweis |
---|
Außerdem müssen Sie in diesem Modell QueryContract überschreiben, damit das Wechseln zwischen der UI der Hostanwendung und der UI des Add-Ins mit der TAB-TASTE unterstützt wird.Weitere Informationen finden Sie im Abschnitt "Einschränkungen des WPF-Add-Ins" unter Übersicht über WPF-Add-Ins. |
Da der Add-In-seitige Adapter eine Schnittstelle implementiert, die sich von INativeHandleContract ableitet, müssen Sie auch GetHandle implementieren, obwohl dies beim Überschreiben von QueryContract ignoriert wird.
Implementieren des Host-Ansichtspipelinesegments
In diesem Modell wird normalerweise von der Hostanwendung erwartet, dass die Hostansicht eine FrameworkElement-Unterklasse ist. Der hostseitige Adapter muss INativeHandleContract in FrameworkElement umwandeln, nachdem die Isolationsgrenze von INativeHandleContract überschritten wurde. Da von der Hostanwendung keine Methode aufgerufen wird, um FrameworkElement abzurufen, muss FrameworkElement von der Hostansicht "zurückgegeben" werden, indem es darin enthalten ist. Die Hostansicht muss also von einer Unterklasse von FrameworkElement abgeleitet sein, das andere UIs enthalten kann, z. B. UserControl. Im folgenden Code wird die als WPFAddInHostView-Klasse implementierte Hostansicht des Vertrags gezeigt.
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 { }
}
Implementieren des hostseitigen Adapterpipelinesegments
Der Vertrag entspricht zwar INativeHandleContract, die Hostanwendung erwartet jedoch UserControl (wie durch die Hostansicht festgelegt). Daher muss INativeHandleContract nach dem Überschreiten der Isolationsgrenze in FrameworkElement umgewandelt werden, bevor er als Inhalt der Hostansicht festgelegt wird (die abgeleitet wird von UserControl).
Dieser Vorgang wird vom hostseitigen Adapter ausgeführt, wie im folgenden Code gezeigt.
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;
}
}
}
Wie Sie sehen, ruft der hostseitige Adapter INativeHandleContract ab, indem er die QueryContract-Methode des Add-In-seitigen Adapters aufruft (an diesem Punkt wird die Isolationsgrenze von INativeHandleContract überschritten).
Anschließend wird INativeHandleContract vom hostseitigen Adapter in FrameworkElement umgewandelt. Dies geschieht durch Aufrufen von ContractToViewAdapter. Schließlich wird FrameworkElement als Inhalt der Hostansicht festgelegt.
Implementieren des Add-Ins
Sind der Add-In-seitige Adapter und die Add-In-Ansicht eingerichtet, kann das Add-In durch Ableiten von der Add-In-Ansicht implementiert werden, wie in folgendem Code gezeigt.
<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");
}
}
}
Dieses Beispiel veranschaulicht einen interessanten Vorteil dieses Modells: Add-In-Entwickler müssen nur das Add-In implementieren (da es auch die UI ist) und nicht sowohl eine Add-In-Klasse als auch eine Add-In-UI.
Implementieren der Hostanwendung
Nachdem der hostseitige Adapter und die Hostansicht erstellt wurden, kann die Hostanwendung das Add-In-Modell von .NET Framework verwenden, um die Pipeline zu öffnen und eine Hostansicht des Add-Ins abzurufen. Diese Schritte werden im folgenden Code gezeigt.
' 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);
Die Hostanwendung verwendet typischen Code für das Add-In-Modell von .NET Framework, um das Add-In zu aktivieren, das die Hostansicht implizit an die Hostanwendung zurückgibt. Daraufhin zeigt die Hostanwendung die Hostansicht (bei der es sich um UserControl handelt) aus einem Grid an.
Der Code zum Verarbeiten von Interaktionen mit der UI des Add-Ins wird in der Anwendungsdomäne des Add-Ins ausgeführt. Diese Interaktionen umfassen Folgendes:
Anzeigen von MessageBox
Diese Aktivität ist von der Hostanwendung vollständig isoliert.