Freigeben über


Gründe für eine Remotebenutzeroberfläche

Eines der Standardziele des VisualStudio.Extensibility-Modells besteht darin, Erweiterungen außerhalb des Visual Studio-Prozesses auszuführen. Dies führt zu einem Hindernis für das Hinzufügen von Benutzeroberflächenunterstützung zu Erweiterungen, da die meisten Benutzeroberflächenframeworks in Bearbeitung sind.

Eine Remote-Benutzeroberfläche ist eine Reihe von Klassen, mit denen Sie WPF-Steuerelemente in einer Out-of-Process-Erweiterung definieren und als Teil der Visual Studio-Benutzeroberfläche anzeigen können.

Die Remote-Benutzeroberfläche lehnt sich stark an dem Entwurfsmuster Model-View-ViewModel an, das sich auf XAML und Datenbindung, Befehle (anstelle von Ereignissen) und Trigger stützt (anstatt mit der logischen Struktur aus CodeBehind zu interagieren).

Während Remote-Benutzeroberflächen entwickelt wurden, um Out-of-Process-Erweiterungen zu unterstützen, verwenden VisualStudio.Extensibility-APIs, die auf Remote-Benutzeroberflächen basieren, wie z. B. ToolWindow, Remote-Benutzeroberflächen für In-Process-Erweiterungen.

Die Standardunterschiede zwischen einer Remote-Benutzeroberfläche und normaler WPF-Entwicklung sind:

  • Die meisten Operationen der Remote-Benutzeroberfläche, einschließlich der Bindung an den Datenkontext und die Ausführung von Befehlen, sind asynchron.
  • Beim Definieren von Datentypen, die in Datenkontexten von Remote-Benutzeroberflächen verwendet werden sollen, müssen sie mit den DataContract- und DataMember-Attributen versehen werden, und ihr Typ muss von der Remote-Benutzeroberfläche serialisierbar sein (weitere Informationen finden Sie hier).
  • Die Remote-Benutzeroberfläche lässt keinen Verweis auf ihre eigenen benutzerdefinierten Steuerelemente zu.
  • Ein Remote-Benutzersteuerelement ist in einer einzelnen XAML-Datei vollständig definiert, die auf ein einzelnes (aber potenziell komplexes und geschachteltes) Datenkontextobjekt verweist.
  • Die Remotebenutzeroberfläche unterstützt codeBehind oder Ereignishandler nicht (Problemumgehungen werden im Dokument für erweiterte Konzepte für Remote-Benutzeroberflächen beschrieben).
  • Ein Remote-Benutzersteuerelement wird im Visual Studio-Prozess instanziiert, nicht der Prozess, der die Erweiterung hostet: Der XAML-Code kann nicht auf Typen und Assemblys aus der Erweiterung verweisen, sondern auf Typen und Assemblys aus dem Visual Studio-Prozess.

Erstellen einer Hallo Welt-Erweiterung fü eine Remote-Benutzeroberfläche

Erstellen Sie zunächst die einfachste Erweiterung für eine Remote-Benutzeroberfläche. Befolgen Sie die Anweisungen zum Erstellen Ihrer ersten Out-Of-Process-Visual Studio-Erweiterung.

Sie sollten nun über eine funktionierende Erweiterung mit einem einzigen Befehl verfügen. Der nächste Schritt besteht darin, ein ToolWindow und ein RemoteUserControlhinzuzufügen. RemoteUserControl ist die Entsprechung der Remote-Benutzeroberfläche eines WPF-Benutzersteuerelements.

Am Ende erhalten Sie vier Dateien:

  1. eine .cs-Datei für den Befehl, der das Toolfenster öffnet,
  2. eine .cs-Datei für ToolWindow, was RemoteUserControl für Visual Studio bereitstellt,
  3. eine .cs-Datei für RemoteUserControl, was auf die XAML-Definition verweist,
  4. eine .xaml-Datei für RemoteUserControl.

Später fügen Sie einen Datenkontext für RemoteUserControl hinzu, der das ViewModel im MVVM-Muster darstellt.

Aktualisieren Sie den Befehl

Aktualisieren Sie den Code des Befehls, um das Toolfenster mithilfe von ShowToolWindowAsync anzuzeigen:

public override Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken)
{
    return Extensibility.Shell().ShowToolWindowAsync<MyToolWindow>(activate: true, cancellationToken);
}

Sie können auch eine Änderung von CommandConfiguration und string-resources.json für eine geeignetere Anzeigemeldung und Platzierung in Betracht ziehen:

public override CommandConfiguration CommandConfiguration => new("%MyToolWindowCommand.DisplayName%")
{
    Placements = new[] { CommandPlacement.KnownPlacements.ViewOtherWindowsMenu },
};
{
  "MyToolWindowCommand.DisplayName": "My Tool Window"
}

Erstellen des Toolfensters

Erstellen Sie eine neue MyToolWindow.cs-Datei, und definieren Sie eine MyToolWindow-Klasse mit Erweiterung von ToolWindow.

Die GetContentAsync-Methode soll IRemoteUserControl zurückgeben, was Sie im nächsten Schritt definieren werden. Da das Remote-Benutzersteuerelement entbehrlich ist, sollten Sie sie entfernen, indem Sie die Methode Dispose(bool) überschreiben.

namespace MyToolWindowExtension;

using Microsoft.VisualStudio.Extensibility;
using Microsoft.VisualStudio.Extensibility.ToolWindows;
using Microsoft.VisualStudio.RpcContracts.RemoteUI;

[VisualStudioContribution]
internal class MyToolWindow : ToolWindow
{
    private readonly MyToolWindowContent content = new();

    public MyToolWindow(VisualStudioExtensibility extensibility)
        : base(extensibility)
    {
        Title = "My Tool Window";
    }

    public override ToolWindowConfiguration ToolWindowConfiguration => new()
    {
        Placement = ToolWindowPlacement.DocumentWell,
    };

    public override async Task<IRemoteUserControl> GetContentAsync(CancellationToken cancellationToken)
        => content;

    public override Task InitializeAsync(CancellationToken cancellationToken)
        => Task.CompletedTask;

    protected override void Dispose(bool disposing)
    {
        if (disposing)
            content.Dispose();

        base.Dispose(disposing);
    }
}

Erstellen der Remote-Benutzersteuerung

Führen Sie diese Aktion in drei Dateien aus:

Remote-Benutzersteuerungsklasse

Die Remote-Benutzersteuerungsklasse namens MyToolWindowContent ist sehr einfach:

namespace MyToolWindowExtension;

using Microsoft.VisualStudio.Extensibility.UI;

internal class MyToolWindowContent : RemoteUserControl
{
    public MyToolWindowContent()
        : base(dataContext: null)
    {
    }
}

Sie benötigen noch keinen Datenkontext, damit Sie sie für jetzt auf null festlegen können.

Eine Klasse, die RemoteUserControl erweitert, verwendet automatisch die eingebettete XAML-Ressource mit demselben Namen. Wenn Sie dieses Verhalten ändern möchten, setzen Sie die GetXamlAsync-Methode außer Kraft.

XAML-Definition

Erstellen Sie als Nächstes eine Datei mit dem Namen MyToolWindowContent.xaml:

<DataTemplate xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:vs="http://schemas.microsoft.com/visualstudio/extensibility/2022/xaml">
    <Label>Hello World</Label>
</DataTemplate>

Die XAML-Definition der Remote-Benutzersteuerung ist ein normales WPF-XAML, was DataTemplate beschreibt. Dieser XAML-Code wird an Visual Studio gesendet und zum Ausfüllen des Toolfensterinhalts verwendet. Wir verwenden einen speziellen Namespace (xmlns-Attribut) für XAML für Remote-Benutzeroberfläche: http://schemas.microsoft.com/visualstudio/extensibility/2022/xaml.

Festlegen des XAML-Codes als eingebettete Ressource

Öffnen Sie schließlich die .csproj-Datei, und stellen Sie sicher, dass die XAML-Datei als eingebettete Ressource behandelt wird:

<ItemGroup>
  <EmbeddedResource Include="MyToolWindowContent.xaml" />
  <Page Remove="MyToolWindowContent.xaml" />
</ItemGroup>

Wie zuvor beschrieben, muss die XAML-Datei denselben Namen wie die Remote-Benutzersteuerungsklasse haben. Konkret muss der vollständige Name der Klasse, die RemoteUserControl erweitert, mit dem Namen der eingebetteten Ressource übereinstimmen. Wenn beispielsweise der vollständige Name der Remote-BenutzersteuerungsklasseMyToolWindowExtension.MyToolWindowContent lautet, sollte der Name der eingebetteten Ressource MyToolWindowExtension.MyToolWindowContent.xaml sein. Standardmäßig werden eingebettete Ressourcen einem Namen zugewiesen, der aus dem Stammnamespace für das Projekt, allen Unterordnerpfaden, unter denen sie stehen können, und deren Dateinamen besteht. Dies kann Probleme verursachen, wenn Ihre Remote-Benutzersteuerungsklasse einen Namespace verwendet, der sich vom Stammnamespace des Projekts unterscheidet oder wenn sich die XAML-Datei nicht im Stammordner des Projekts befindet. Bei Bedarf können Sie mithilfe des LogicalName-Tags einen Namen für die eingebettete Ressource erzwingen:

<ItemGroup>
  <EmbeddedResource Include="MyToolWindowContent.xaml" LogicalName="MyToolWindowExtension.MyToolWindowContent.xaml" />
  <Page Remove="MyToolWindowContent.xaml" />
</ItemGroup>

Testen der Erweiterung

Sie sollten jetzt in der Lage sein, F5 zu drücken, um die Erweiterung zu debuggen.

Screenshot des Menüs und Toolfensters.

Unterstützung von Designs hinzufügen

Es empfiehlt sich, beim Schreiben der Benutzeroberfläche zu berücksichtigen, dass Visual Studio so designiert werden kann, sodass unterschiedliche Farben verwendet werden.

Aktualisieren Sie den XAML-Code, um die Formatvorlagen und Farben zu verwenden, die in Visual Studio verwendet werden:

<DataTemplate xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:vs="http://schemas.microsoft.com/visualstudio/extensibility/2022/xaml"
              xmlns:styles="clr-namespace:Microsoft.VisualStudio.Shell;assembly=Microsoft.VisualStudio.Shell.15.0"
              xmlns:colors="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.15.0">
    <Grid>
        <Grid.Resources>
            <Style TargetType="Label" BasedOn="{StaticResource {x:Static styles:VsResourceKeys.ThemedDialogLabelStyleKey}}" />
        </Grid.Resources>
        <Label>Hello World</Label>
    </Grid>
</DataTemplate>

Die Beschriftung verwendet jetzt das gleiche Design wie die restliche Visual Studio-Benutzeroberfläche und ändert die Farbe automatisch, wenn der Benutzer in den dunklen Modus wechselt:

Screenshot des Designtoolfensters.

Hier verweist das xmlns-Attribut auf die Microsoft.VisualStudio.Shell.15.0-Assembly, die keine der Erweiterungsabhängigkeiten ist. Dies ist in Ordnung, da dieser XAML-Code vom Visual Studio-Prozess verwendet wird, der eine Abhängigkeit von Shell.15 hat, nicht von der Erweiterung selbst.

Um eine bessere XAML-Bearbeitung zu erzielen, können Sie dem Erweiterungsprojekt vorübergehend ein PackageReference zu Microsoft.VisualStudio.Shell.15.0 hinzufügen. Vergessen Sie nicht, es später zu entfernen, da eine Out-Of-Process VisualStudio.Extensibility-Erweiterung nicht auf dieses Paket verweisen sollte!

Hinzufügen eines Datenkontexts

Fügen Sie eine Datenkontextklasse für die Remote-Benutzersteuerung hinzu:

using System.Runtime.Serialization;

namespace MyToolWindowExtension;

[DataContract]
internal class MyToolWindowData
{
    [DataMember]
    public string? LabelText { get; init; }
}

und aktualisieren MyToolWindowContent.cs und MyToolWindowContent.xaml, um sie zu verwenden:

internal class MyToolWindowContent : RemoteUserControl
{
    public MyToolWindowContent()
        : base(dataContext: new MyToolWindowData { LabelText = "Hello Binding!"})
    {
    }
<Label Content="{Binding LabelText}" />

Der Inhalt der Bezeichnung wird jetzt über die Datenbindung festgelegt:

Screenshot des Toolfensters mit Datenbindung.

Der hier aufgeführte Datentyp ist mit DataContract- und DataMember-Attributen gekennzeichnet. Dies liegt daran, dass die MyToolWindowData-Instanz im Erweiterungshostprozess vorhanden ist, während das WPF-Steuerelement, das aus MyToolWindowContent.xaml erstellt wurde, im Visual Studio-Prozess vorhanden ist. Damit die Datenbindung funktioniert, generiert die Infrastruktur der Remote-Benutzeroberfläche einen Proxy des MyToolWindowData-Objekts im Visual Studio-Prozess. Die DataContract- und DataMember-Attribute geben an, welche Typen und Eigenschaften für die Datenbindung relevant sind und im Proxy repliziert werden sollen.

Der Datenkontext der Remote-Benutzersteuerung wird als Konstruktorparameter der RemoteUserControl-Klasse übergeben: Die RemoteUserControl.DataContext-Eigenschaft ist schreibgeschützt. Dies bedeutet nicht, dass der gesamte Datenkontext unveränderlich ist, aber das Stammdatenkontextobjekt einer Remote-Benutzersteuerung kann nicht ersetzt werden. Im nächsten Abschnitt werden wir MyToolWindowData änderbar und beobachtbar machen.

Serialisierbare Typen und Datenkontext der Remote-Benutzeroberfläche

Ein Datenkontext einer Remote-Benutzeroberfläche kann nur serialisierbare Typen enthalten, oder, um präziser zu sein, können nur DataMember-Eigenschaften eines serialisierbaren Typs datengebunden sein.

Nur die folgenden Typen können von der Remote-Benutzeroberfläche serialisiert werden:

  • Grundtypdaten (die meisten numerischen .NET-Typen, Enumerationen, bool, string, DateTime)
  • erweiterungsdefinierte Typen, die mit DataContract- und DataMember-Attributen gekennzeichnet sind (und alle ihre Datenelemente sind ebenfalls serialisierbar)
  • Objekte, die IAsyncCommand implementieren
  • XamlFragment- und SolidColorBrush-Objekte und Farben-Werte
  • Nullable<>-Werte für einen serialisierbaren Typ
  • Auflistungen serialisierbarer Typen, einschließlich beobachtbarer Auflistungen.

Lebenszyklus einer Remote-Benutzersteuerung

Sie können die ControlLoadedAsync-Methode außer Kraft setzen, die benachrichtigt werden soll, wenn das Steuerelement zum ersten Mal in einem WPF-Container geladen wird. Wenn sich der Status des Datenkontexts in Ihrer Implementierung unabhängig von Benutzeroberflächenereignissen ändern kann, ist die ControlLoadedAsync-Methode der richtige Ort, um den Inhalt des Datenkontexts zu initialisieren und Änderungen darauf anzuwenden.

Sie können die Dispose-Methode auch überschreiben, die benachrichtigt werden soll, wenn das Steuerelement zerstört wird und nicht mehr verwendet wird.

internal class MyToolWindowContent : RemoteUserControl
{
    public MyToolWindowContent()
        : base(dataContext: new MyToolWindowData())
    {
    }

    public override async Task ControlLoadedAsync(CancellationToken cancellationToken)
    {
        await base.ControlLoadedAsync(cancellationToken);
        // Your code here
    }

    protected override void Dispose(bool disposing)
    {
        // Your code here
        base.Dispose(disposing);
    }
}

Befehle, Beobachtbarkeit und bidirektionale Datenbindung

Als Nächstes machen wir den Datenkontext beobachtbar und fügen der Toolbox eine Taste hinzu.

Der Datenkontext kann durch die Implementierung von INotifyPropertyChanged beobachtet werden. Alternativ bieten Remote-Benutzeroberflächen eine praktische abstrakte Klasse, NotifyPropertyChangedObject, die wir erweitern können, um den Boilerplate-Code zu reduzieren.

Ein Datenkontext hat in der Regel eine Mischung aus schreibgeschützten Eigenschaften und beobachtbaren Eigenschaften. Der Datenkontext kann ein komplexes Diagramm von Objekten sein, solange sie mit den DataContract- und-Attributen DataMember gekennzeichnet sind und INotifyPropertyChanged bei Bedarf implementieren. Es ist auch möglich, beobachtbare Sammlungen oder ein ObservableList<T> zu haben, bei denen es sich um ein erweitertes ObservableCollection<T> handelt, das von einer Remote-Benutzeroberfläche bereitgestellt wird, um auch Bereichsoperationen zu unterstützen, was eine bessere Leistung zu ermöglicht.

Außerdem müssen wir dem Datenkontext einen Befehl hinzufügen. In einer Remote-Benutzeroberfläche implementieren Befehle IAsyncCommand, aber häufig ist es einfacher, eine Instanz der AsyncCommand-Klasse zu erstellen.

IAsyncCommand unterscheidet sich von ICommand auf zwei Arten:

  • Die Execute-Methode wird mit ExecuteAsync ersetzt, da alles in der Remote-Benutzeroberfläche asynchron ist!
  • Die CanExecute(object)-Methode wird durch eine CanExecute-Eigenschaft ersetzt. Die AsyncCommand-Klasse kümmert sich darum, CanExecute beobachtbar zu machen.

Es ist wichtig zu beachten, dass die Remote-Benutzeroberfläche keine Ereignishandler unterstützt. Daher müssen alle Benachrichtigungen von der Benutzeroberfläche an die Erweiterung über Datenbindung und Befehle implementiert werden.

Dies ist der resultierende Code für MyToolWindowData:

[DataContract]
internal class MyToolWindowData : NotifyPropertyChangedObject
{
    public MyToolWindowData()
    {
        HelloCommand = new((parameter, cancellationToken) =>
        {
            Text = $"Hello {Name}!";
            return Task.CompletedTask;
        });
    }

    private string _name = string.Empty;
    [DataMember]
    public string Name
    {
        get => _name;
        set => SetProperty(ref this._name, value);
    }

    private string _text = string.Empty;
    [DataMember]
    public string Text
    {
        get => _text;
        set => SetProperty(ref this._text, value);
    }

    [DataMember]
    public AsyncCommand HelloCommand { get; }
}

Beheben des MyToolWindowContent-Konstruktors:

public MyToolWindowContent()
    : base(dataContext: new MyToolWindowData())
{
}

Aktualisieren Sie MyToolWindowContent.xaml, um die neuen Eigenschaften im Datenkontext zu verwenden. Dies ist alles normaler WPF-XAML-Code. Auch auf das IAsyncCommand-Objekt wird über einen Proxy namens ICommand zugegriffen, der im Visual Studio-Prozess aufgerufen wird, sodass es wie gewohnt datengebunden werden kann.

<DataTemplate xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:vs="http://schemas.microsoft.com/visualstudio/extensibility/2022/xaml"
              xmlns:styles="clr-namespace:Microsoft.VisualStudio.Shell;assembly=Microsoft.VisualStudio.Shell.15.0"
              xmlns:colors="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.15.0">
    <Grid>
        <Grid.Resources>
            <Style TargetType="Label" BasedOn="{StaticResource {x:Static styles:VsResourceKeys.ThemedDialogLabelStyleKey}}" />
            <Style TargetType="TextBox" BasedOn="{StaticResource {x:Static styles:VsResourceKeys.TextBoxStyleKey}}" />
            <Style TargetType="Button" BasedOn="{StaticResource {x:Static styles:VsResourceKeys.ButtonStyleKey}}" />
            <Style TargetType="TextBlock">
                <Setter Property="Foreground" Value="{DynamicResource {x:Static styles:VsBrushes.WindowTextKey}}" />
            </Style>
        </Grid.Resources>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Label Content="Name:" />
        <TextBox Text="{Binding Name}" Grid.Column="1" />
        <Button Content="Say Hello" Command="{Binding HelloCommand}" Grid.Column="2" />
        <TextBlock Text="{Binding Text}" Grid.ColumnSpan="2" Grid.Row="1" />
    </Grid>
</DataTemplate>

Diagramm des Toolfensters mit bidirektionale Bindung und einem Befehl.

Grundlegendes zur Asynchronität in der Remote-Benutzeroberfläche

Die gesamte Kommunikation der Remote-Benutzeroberfläche für dieses Toolfenster befolgt die folgenden Schritte:

  1. Auf den Datenkontext wird über einen Proxy innerhalb des Visual Studio-Prozesses mit seinem ursprünglichen Inhalt zugegriffen,

  2. Das aus MyToolWindowContent.xaml erstellte Steuerelement ist an den Datenkontextproxy datengebunden,

  3. Der Benutzer gibt Text in das Textfeld ein, der der Name-Eigenschaft des Datenkontextproxys über die Datenbindung zugewiesen ist. Der neue Wert von Name wird an das MyToolWindowData-Objekt verteilt.

  4. Der Benutzer klickt auf die Schaltfläche und löst damit eine Kaskade von Effekten aus:

    • der HelloCommand im Datenkontextproxy wird ausgeführt
    • Die asynchrone Ausführung des Codes AsyncCommand der Erweiterung wird gestartet
    • der asynchrone Rückruf für HelloCommand-Aktualisierungen des Werts der beobachtbaren Eigenschaft Text
    • der neue Wert von Text, der an den Datenkontext-Proxy weitergegeben wird
    • Der Textblock im Toolfenster wird auf den neuen Wert von Text per Datenbindung aktualisiert

Diagramm der bidirektionale Bindung und Kommunikation von Befehlen im Toolfenster.

Verwenden von Befehlsparametern, um Racebedingungen zu vermeiden

Alle Operationen, die die Kommunikation zwischen Visual Studio und der Erweiterung (blaue Pfeile im Diagramm) umfassen, sind asynchron. Es ist wichtig, diesen Aspekt im Gesamtentwurf der Erweiterung zu berücksichtigen.

Aus diesem Grund ist es besser, , wenn die Konsistenz wichtig ist, Befehlsparameter anstelle von bidirektionaler Bindung zu verwenden, um den Datenkontextstatus zum Zeitpunkt der Ausführung eines Befehls abzurufen.

Nehmen Sie diese Änderung vor, indem Sie die Schaltfläche CommandParameter an Namebinden:

<Button Content="Say Hello" Command="{Binding HelloCommand}" CommandParameter="{Binding Name}" Grid.Column="2" />

Ändern Sie dann den Rückruf des Befehls so, dass er den Parameter verwendet:

HelloCommand = new AsyncCommand((parameter, cancellationToken) =>
{
    Text = $"Hello {(string)parameter!}!";
    return Task.CompletedTask;
});

Bei diesem Ansatz wird der Wert der Name-Eigenschaft synchron vom Datenkontextproxy zum Zeitpunkt des Klickens auf die Schaltfläche abgerufen und an die Erweiterung gesendet. Dies vermeidet Racebedingungen, insbesondere, wenn der HelloCommand-Rückruf in Zukunft für (await-Ausdrücke) geändert wird.

Asynchrone Befehle nutzen Daten aus mehreren Eigenschaften

Die Verwendung eines Befehlsparameters ist keine Option, wenn der Befehl mehrere Eigenschaften benötigt, die vom Benutzer eingestellt werden können. Wenn beispielsweise auf der Benutzeroberfläche zwei Textfelder vorhanden sind: „Vorname“ und „Nachname“.

Die Lösung in diesem Fall besteht darin, im asynchronen Befehlsrückruf den Wert aller Eigenschaften aus dem Datenkontext vor der Rückgabe abzurufen.

Nachfolgend sehen Sie ein Beispiel, bei dem die FirstName- und LastName-Eigenschaftswerte und vor dem Zurückgeben abgerufen werden, um sicherzustellen, dass der Wert zum Zeitpunkt des Befehlsaufrufs verwendet wird:

HelloCommand = new(async (parameter, cancellationToken) =>
{
    string firstName = FirstName;
    string lastName = LastName;
    await Task.Delay(TimeSpan.FromSeconds(1));
    Text = $"Hello {firstName} {lastName}!";
});

Es ist auch wichtig zu vermeiden, dass die Erweiterung asynchron den Wert von Eigenschaften aktualisiert, die auch vom Benutzer aktualisiert werden können. Mit anderen Worten: Vermeiden Sie eine TwoWay-Datenbindung.

Die hier aufgeführten Informationen sollten ausreichen, um einfache Komponenten der Remote-Benutzeroberfläche zu erstellen. Weitere erweiterte Szenarien finden Sie unter Erweiterten Konzepte einer Remote-Benutzeroberfläche.