Neden Uzak Kullanıcı Arabirimi
VisualStudio.Genişletilebilirlik modelinin ana hedeflerinden biri, uzantıların Visual Studio işleminin dışında çalışmasına izin vermektir. Bu, çoğu kullanıcı arabirimi çerçevesi devam ettiğinden uzantılara kullanıcı arabirimi desteği eklemeye engel oluşturur.
Uzak kullanıcı arabirimi, işlem dışı bir uzantıda WPF denetimleri tanımlamanıza ve bunları Visual Studio kullanıcı arabiriminin bir parçası olarak göstermenize olanak sağlayan bir sınıf kümesidir.
Uzak kullanıcı arabirimi, XAML ve veri bağlama, komutlar (olaylar yerine) ve tetikleyicileri (arka planda bulunan mantıksal ağaçla etkileşime geçmek yerine) kullanan Model-View-ViewModel tasarım desenine büyük ölçüde dayanır.
Uzak kullanıcı arabirimi, işlem dışı uzantıları destekleyecek şekilde geliştirilmiş olsa da, gibi ToolWindow
Uzak kullanıcı arabirimini kullanan VisualStudio.Genişletilebilirlik API'leri de işlem içi uzantılar için Uzak Kullanıcı Arabirimi'ni kullanır.
Uzak kullanıcı arabirimi ile normal WPF geliştirme arasındaki temel farklar şunlardır:
- Veri bağlamı ve komut yürütme bağlama da dahil olmak üzere çoğu Uzak UI işlemi zaman uyumsuz olur.
- Uzak UI veri bağlamlarında kullanılacak veri türlerini tanımlarken, ve
DataMember
öznitelikleriyleDataContract
ve türleri Uzak Kullanıcı Arabirimi tarafından serileştirilebilir olmalıdır (ayrıntılar için buraya bakın). - Uzak kullanıcı arabirimi kendi özel denetimlerinize başvurmaya izin vermez.
- Uzak kullanıcı denetimi, tek bir (ancak karmaşık ve iç içe geçmiş) veri bağlam nesnesine başvuran tek bir XAML dosyasında tam olarak tanımlanır.
- Uzak kullanıcı arabirimi arka planda kodu veya olay işleyicilerini desteklemez (geçici çözümler gelişmiş Uzak KULLANıCı arabirimi kavramları belgesinde açıklanmıştır).
- Uzak kullanıcı denetimi, uzantıyı barındıran işlemde değil Visual Studio işleminde örneği oluşturulur: XAML uzantıdan türlere ve derlemelere başvuramaz, ancak Visual Studio işleminden türlere ve derlemelere başvurabilir.
Uzak kullanıcı arabirimi Merhaba Dünya uzantısı oluşturma
En temel Uzak Kullanıcı Arabirimi uzantısını oluşturarak başlayın. İlk işlem dışı Visual Studio uzantınızı oluşturma başlığındaki yönergeleri izleyin.
Artık tek bir komutla çalışan bir uzantıya sahip olmanız gerekir. Sonraki adım ve ToolWindow
RemoteUserControl
eklemektir. RemoteUserControl
, WPF kullanıcı denetiminin Uzak kullanıcı arabirimi eşdeğeridir.
Dört dosyayla sonuçlanır:
- araç penceresini açan komut için bir
.cs
dosya, - için Visual Studio'ya sağlayan
RemoteUserControl
bir.cs
dosyaToolWindow
, - için XAML tanımına
RemoteUserControl
başvuran bir.cs
dosya, - için
RemoteUserControl
bir.xaml
dosya.
Daha sonra, MVVM desenindeki ViewModel'i temsil eden için RemoteUserControl
bir veri bağlamı eklersiniz.
Komutu güncelleştirme
komutunun ShowToolWindowAsync
kodunu kullanarak araç penceresini gösterecek şekilde güncelleştirin:
public override Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken)
{
return Extensibility.Shell().ShowToolWindowAsync<MyToolWindow>(activate: true, cancellationToken);
}
Ayrıca, daha uygun bir görüntüleme iletisi ve yerleşimi için ve string-resources.json
değişikliklerini CommandConfiguration
de göz önünde bulundurabilirsiniz:
public override CommandConfiguration CommandConfiguration => new("%MyToolWindowCommand.DisplayName%")
{
Placements = new[] { CommandPlacement.KnownPlacements.ViewOtherWindowsMenu },
};
{
"MyToolWindowCommand.DisplayName": "My Tool Window"
}
Araç penceresini oluşturma
Yeni MyToolWindow.cs
bir dosya oluşturun ve genişleten ToolWindow
bir MyToolWindow
sınıf tanımlayın.
yönteminin GetContentAsync
bir sonraki adımda tanımlayabileceğiniz bir IRemoteUserControl
döndürmesi gerekir. Uzak kullanıcı denetimi atılabilir olduğundan, yöntemini geçersiz kılarak kullanımdan kaldırmaya Dispose(bool)
dikkat edin.
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);
}
}
Uzak kullanıcı denetimini oluşturma
Bu eylemi üç dosyada gerçekleştirin:
Uzak kullanıcı denetimi sınıfı
adlı MyToolWindowContent
uzak kullanıcı denetimi sınıfı basittir:
namespace MyToolWindowExtension;
using Microsoft.VisualStudio.Extensibility.UI;
internal class MyToolWindowContent : RemoteUserControl
{
public MyToolWindowContent()
: base(dataContext: null)
{
}
}
Henüz bir veri bağlamı gerekmez, bu nedenle şimdilik olarak ayarlayabilirsiniz null
.
Genişleten RemoteUserControl
bir sınıf otomatik olarak aynı ada sahip XAML eklenmiş kaynağını kullanır. Bu davranışı değiştirmek istiyorsanız yöntemini geçersiz kılın GetXamlAsync
.
XAML tanımı
Ardından adlı MyToolWindowContent.xaml
bir dosya oluşturun:
<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>
Uzak kullanıcı denetiminin XAML tanımı, bir açıklayan normal WPF XAML'dir DataTemplate
. Bu XAML Visual Studio'ya gönderilir ve araç penceresi içeriğini doldurmak için kullanılır. Uzak UI XAML için özel bir ad alanı (xmlns
öznitelik) kullanırız: http://schemas.microsoft.com/visualstudio/extensibility/2022/xaml
.
XAML'yi ekli kaynak olarak ayarlama
Son olarak, dosyayı açın .csproj
ve XAML dosyasının katıştırılmış kaynak olarak işlendiğinden emin olun:
<ItemGroup>
<EmbeddedResource Include="MyToolWindowContent.xaml" />
<Page Remove="MyToolWindowContent.xaml" />
</ItemGroup>
Daha önce açıklandığı gibi, XAML dosyasının uzak kullanıcı denetimi sınıfıyla aynı ada sahip olması gerekir. Kesin olarak, genişleten RemoteUserControl
sınıfın tam adı eklenmiş kaynağın adıyla eşleşmelidir. Örneğin, uzak kullanıcı denetim sınıfının tam adı iseMyToolWindowExtension.MyToolWindowContent
, eklenmiş kaynak adı olmalıdırMyToolWindowExtension.MyToolWindowContent.xaml
. Varsayılan olarak, katıştırılmış kaynaklara projenin kök ad alanı tarafından oluşturulan bir ad, altında olabilecekleri alt klasör yolları ve dosya adları atanır. Bu, uzak kullanıcı denetim sınıfınız projenin kök ad alanından farklı bir ad alanı kullanıyorsa veya xaml dosyası projenin kök klasöründe değilse sorun oluşturabilir. Gerekirse, etiketini kullanarak LogicalName
eklenmiş kaynak için bir ad zorlayabilirsiniz:
<ItemGroup>
<EmbeddedResource Include="MyToolWindowContent.xaml" LogicalName="MyToolWindowExtension.MyToolWindowContent.xaml" />
<Page Remove="MyToolWindowContent.xaml" />
</ItemGroup>
Uzantıyı test etme
Artık uzantıda hata ayıklamak için basabilmeniz F5
gerekir.
Temalar için destek ekleme
Visual Studio'nun tema oluşturabileceğini ve farklı renklerin kullanılmasına neden olabileceğini göz önünde bulundurarak kullanıcı arabirimini yazmak iyi bir fikirdir.
Visual Studio'da kullanılan stilleri ve renkleri kullanmak için XAML'yi güncelleştirin:
<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>
Etiket artık Visual Studio kullanıcı arabiriminin geri kalanıyla aynı temayı kullanır ve kullanıcı koyu moda geçtiğinde renk otomatik olarak değişir:
Burada özniteliği, xmlns
uzantı bağımlılıklarından biri olmayan Microsoft.VisualStudio.Shell.15.0 derlemesine başvurur. Bu XAML, uzantının kendisi tarafından değil Shell.15'e bağımlılığı olan Visual Studio işlemi tarafından kullanıldığından bu sorun yoktur.
Daha iyi bir XAML düzenleme deneyimi elde etmek için uzantı projesine Microsoft.VisualStudio.Shell.15.0
geçici olarak bir PackageReference
ekleyebilirsiniz. İşlem dışı bir VisualStudio.Genişletilebilirlik uzantısı bu pakete başvurmaması gerektiğinden daha sonra kaldırmayı unutmayın!
Veri bağlamı ekleme
Uzak kullanıcı denetimi için veri bağlamı sınıfı ekleyin:
using System.Runtime.Serialization;
namespace MyToolWindowExtension;
[DataContract]
internal class MyToolWindowData
{
[DataMember]
public string? LabelText { get; init; }
}
ve güncelleştirin MyToolWindowContent.cs
ve MyToolWindowContent.xaml
kullanın:
internal class MyToolWindowContent : RemoteUserControl
{
public MyToolWindowContent()
: base(dataContext: new MyToolWindowData { LabelText = "Hello Binding!"})
{
}
<Label Content="{Binding LabelText}" />
Etiketin içeriği artık veri bağlama aracılığıyla ayarlanır:
Buradaki veri bağlam türü ve DataMember
öznitelikleriyle DataContract
işaretlenir. Bunun nedeni MyToolWindowData
, örneğin uzantı ana bilgisayar işleminde mevcut olması ve içinden MyToolWindowContent.xaml
oluşturulan WPF denetiminin Visual Studio işleminde mevcut olmasıdır. Veri bağlamanın çalışmasını sağlamak için, Uzak UI altyapısı Visual Studio işleminde nesnenin MyToolWindowData
bir proxy'sini oluşturur. DataContract
ve DataMember
öznitelikleri, hangi tür ve özelliklerin veri bağlamaya uygun olduğunu ve ara sunucuya çoğaltılması gerektiğini belirtir.
Uzak kullanıcı denetiminin veri bağlamı sınıfının oluşturucu parametresi RemoteUserControl
olarak geçirilir: RemoteUserControl.DataContext
özelliği salt okunurdur. Bu, tüm veri bağlamının sabit olduğu anlamına gelmez, ancak uzak kullanıcı denetiminin kök veri bağlamı nesnesi değiştirilemez. Sonraki bölümde, karartılabilir ve gözlemlenebilir hale getireceğiz MyToolWindowData
.
Serileştirilebilir türler ve Uzak UI veri bağlamı
Uzak UI veri bağlamı yalnızca serileştirilebilir türler içerebilir veya daha kesin olmak gerekirse yalnızca DataMember
serileştirilebilir bir türün özellikleri veriye bağlanabilir.
Uzak kullanıcı arabirimi tarafından yalnızca aşağıdaki türler serileştirilebilir:
- ilkel veriler (çoğu .NET sayısal türü, sabit listesi,
bool
,string
,DateTime
) - ve
DataMember
öznitelikleriyleDataContract
işaretlenmiş genişletici tanımlı türler (ve tüm veri üyeleri de serileştirilebilir) - IAsyncCommand uygulayan nesneler
- XamlFragment ve SolidColorBrush nesneleri ve Color değerleri
Nullable<>
serileştirilebilir bir tür için değerler- gözlemlenebilir koleksiyonlar da dahil olmak üzere serileştirilebilir tür koleksiyonları.
Uzak Kullanıcı Denetiminin Yaşam Döngüsü
Denetim bir WPF kapsayıcısına ControlLoadedAsync
ilk kez yüklendiğinde bildirim almak için yöntemini geçersiz kılabilirsiniz. Uygulamanızda veri bağlamının durumu UI olaylarından bağımsız olarak değişebilirse, ControlLoadedAsync
yöntem veri bağlamının içeriğini başlatmak ve buna değişiklik uygulamaya başlamak için doğru yerdir.
Ayrıca, denetim yok edildiğinde ve artık kullanılmadığında bildirim almak için yöntemini geçersiz kılabilirsiniz Dispose
.
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);
}
}
Komutlar, gözlemlenebilirlik ve iki yönlü veri bağlama
Şimdi veri bağlamını gözlemlenebilir hale getirelim ve araç kutusuna bir düğme ekleyelim.
INotifyPropertyChanged uygulanarak veri bağlamı gözlemlenebilir hale getirilebilir. Alternatif olarak, Uzak kullanıcı arabirimi ortak kodu azaltmak için genişletebileceğimiz kullanışlı bir soyut sınıf NotifyPropertyChangedObject
sağlar.
Veri bağlamı genellikle salt okunur özelliklerin ve gözlemlenebilir özelliklerin bir karışımına sahiptir. Veri bağlamı, ve DataMember
öznitelikleriyle DataContract
işaretlendikleri ve gerektiğinde INotifyPropertyChanged uygulandığı sürece nesnelerin karmaşık bir grafiği olabilir. Gözlemlenebilir koleksiyonlar veya uzak kullanıcı arabirimi tarafından sağlanan genişletilmiş bir ObservableCollection<T olan ve daha iyi performans sağlayan aralık işlemlerini desteklemek için observableList<T>> de olabilir.
Ayrıca veri bağlamı için bir komut eklememiz gerekir. Uzak kullanıcı arabiriminde komutlar uygulanır IAsyncCommand
, ancak sınıfın bir örneğini AsyncCommand
oluşturmak genellikle daha kolaydır.
IAsyncCommand
ICommand
iki şekilde farklıdır:
Execute
Uzak kullanıcı arabirimindeki her şey zaman uyumsuz olduğundan yöntemi ileExecuteAsync
değiştirilir!CanExecute(object)
yöntemi birCanExecute
özellik ile değiştirilir.AsyncCommand
Sınıf gözlemlenebilir hale getirmekleCanExecute
ilgilenir.
Uzak kullanıcı arabiriminin olay işleyicilerini desteklemediğini, bu nedenle kullanıcı arabiriminden uzantıya tüm bildirimlerin veri bağlama ve komutlar aracılığıyla uygulanması gerektiğini unutmayın.
Bu, için MyToolWindowData
sonuçta elde edilen koddur:
[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; }
}
Oluşturucuyu düzeltin MyToolWindowContent
:
public MyToolWindowContent()
: base(dataContext: new MyToolWindowData())
{
}
Veri bağlamındaki yeni özellikleri kullanacak şekilde güncelleştirin MyToolWindowContent.xaml
. Bunların tümü normal WPF XAML'dir. Nesneye IAsyncCommand
bile Visual Studio işleminde çağrılan ICommand
bir ara sunucu üzerinden erişilir, böylece her zamanki gibi verilere bağlanabilir.
<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>
Uzak kullanıcı arabiriminde zaman uyumsuzluğu anlama
Bu araç penceresi için uzak kullanıcı arabirimi iletişiminin tamamı şu adımları izler:
Veri bağlamı, özgün içeriğiyle Visual Studio işleminin içindeki bir ara sunucu aracılığıyla erişilir.
'den
MyToolWindowContent.xaml
oluşturulan denetim, veri bağlamı ara sunucusuna bağlı olan verilerdir.Kullanıcı, veri bağlama aracılığıyla veri bağlamı ara sunucusunun özelliğine
Name
atanan metin kutusuna bazı metinler yazın. yeni değeriName
nesnesineMyToolWindowData
yayılır.Kullanıcı düğmeye tıklar ve art arda efektlere neden olur:
HelloCommand
veri bağlamı ara sunucusu yürütülür- genişletici
AsyncCommand
kodunun zaman uyumsuz yürütülmesi başlatılır - için
HelloCommand
zaman uyumsuz geri arama, gözlemlenebilir özelliğin değerini güncelleştirirText
- yeni değeri
Text
veri bağlamı ara sunucusuna yayılır - araç penceresindeki metin bloğu, veri bağlama aracılığıyla yeni değerine
Text
güncelleştirilir
Yarış koşullarından kaçınmak için komut parametrelerini kullanma
Visual Studio ile uzantı arasındaki iletişimi içeren tüm işlemler (diyagramdaki mavi oklar) zaman uyumsuz olur. Uzantının genel tasarımında bu yönü göz önünde bulundurmak önemlidir.
Bu nedenle tutarlılık önemliyse, bir komutun yürütülmesi sırasında veri bağlamı durumunu almak için iki yönlü bağlama yerine komut parametrelerini kullanmak daha iyidir.
Düğmelerini CommandParameter
Name
ile bağlayarak bu değişikliği yapın:
<Button Content="Say Hello" Command="{Binding HelloCommand}" CommandParameter="{Binding Name}" Grid.Column="2" />
Ardından komutunun geri çağırmasını parametresini kullanacak şekilde değiştirin:
HelloCommand = new AsyncCommand((parameter, cancellationToken) =>
{
Text = $"Hello {(string)parameter!}!";
return Task.CompletedTask;
});
Bu yaklaşımla, özelliğin Name
değeri düğme tıklandığında veri bağlamı ara sunucusundan zaman uyumlu olarak alınır ve uzantıya gönderilir. Bu, özellikle gelecekte geri çağırmanın verim (ifadelere sahipawait
) olarak değiştirilmesi durumunda HelloCommand
herhangi bir yarış koşulunu önler.
Zaman uyumsuz komutlar birden çok özellikten veri tüketir
Komutun kullanıcı tarafından ayarlanan birden çok özelliği tüketmesi gerekiyorsa komut parametresi kullanmak bir seçenek değildir. Örneğin, kullanıcı arabiriminde iki metin kutusu varsa: "Ad" ve "Soyadı".
Bu durumda çözüm, zaman uyumsuz komut geri çağırmasında veri bağlamındaki tüm özelliklerin değerini döndürmeden önce almaktır.
Aşağıda, komut çağırma sırasındaki değerin FirstName
kullanıldığından emin olmak için vermeden önce ve LastName
özellik değerlerinin alındığı bir örnek görebilirsiniz:
HelloCommand = new(async (parameter, cancellationToken) =>
{
string firstName = FirstName;
string lastName = LastName;
await Task.Delay(TimeSpan.FromSeconds(1));
Text = $"Hello {firstName} {lastName}!";
});
Uzantının, kullanıcı tarafından da güncelleştirilebilecek özelliklerin değerini zaman uyumsuz olarak güncelleştirmesini önlemek de önemlidir. Başka bir deyişle, TwoWay veri bağlamasından kaçının.
İlgili içerik
Buradaki bilgiler basit Uzak Kullanıcı Arabirimi bileşenleri oluşturmak için yeterli olmalıdır. Daha gelişmiş senaryolar için bkz . Gelişmiş Uzak Kullanıcı Arabirimi kavramları.