Öğretici: Gelişmiş uzak kullanıcı arabirimi
Bu öğreticide, rastgele renklerin listesini gösteren bir araç penceresini artımlı olarak değiştirerek gelişmiş Uzak kullanıcı arabirimi kavramları hakkında bilgi ediniyorsunuz:
Şu konularda bilgi edineceksiniz:
- Birden çok zaman uyumsuz komut yürütmenin paralel olarak nasıl çalışabileceği ve bir komut çalışırken kullanıcı arabirimi öğelerini devre dışı bırakma.
- Birden çok düğmeyi aynı zaman uyumsuz komuta bağlama.
- Başvuru türlerinin Uzak UI veri bağlamında ve proxy'sinde nasıl işleneceğini.
- Zaman uyumsuz bir komutu olay işleyicisi olarak kullanma.
- Birden çok düğme aynı komuta bağlıysa, zaman uyumsuz komutunun geri çağırması yürütülürken tek bir düğmeyi devre dışı bırakma.
- Uzak kullanıcı arabirimi denetiminden XAML kaynak sözlüklerini kullanma.
- Uzak UI veri bağlamında karmaşık fırçalar gibi WPF türlerini kullanma.
- Uzak kullanıcı arabiriminin iş parçacığını işleme şekli.
Bu öğretici, giriş niteliğindeki Uzak Kullanıcı Arabirimi makalesini temel alır ve aşağıdakiler dahil olmak üzere çalışan bir VisualStudio.Genişletilebilirlik uzantısına sahip olduğunuzu bekler:
- araç penceresini açan komut için bir
.cs
dosya, - sınıfı için
ToolWindow
birMyToolWindow.cs
dosya, - sınıfı için
RemoteUserControl
birMyToolWindowContent.cs
dosya, - xaml tanımı için
RemoteUserControl
eklenmiş birMyToolWindowContent.xaml
kaynak dosyası, - veri bağlamı
RemoteUserControl
için birMyToolWindowData.cs
dosya.
Başlamak için liste görünümünü ve düğmeyi gösterecek şekilde güncelleştirin 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"
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 x:Name="RootGrid">
<Grid.Resources>
<Style TargetType="ListView" BasedOn="{StaticResource {x:Static styles:VsResourceKeys.ThemedDialogListViewStyleKey}}" />
<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.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListView ItemsSource="{Binding Colors}" HorizontalContentAlignment="Stretch">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding ColorText}" />
<Rectangle Fill="{Binding Color}" Width="50px" Grid.Column="1" />
<Button Content="Remove" Grid.Column="2" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Content="Add color" Command="{Binding AddColorCommand}" Grid.Row="1" />
</Grid>
</DataTemplate>
Ardından veri bağlamı sınıfını MyToolWindowData.cs
güncelleştirin:
using Microsoft.VisualStudio.Extensibility.UI;
using System.Collections.ObjectModel;
using System.Runtime.Serialization;
using System.Text;
using System.Windows.Media;
namespace MyToolWindowExtension;
[DataContract]
internal class MyToolWindowData
{
private Random random = new();
public MyToolWindowData()
{
AddColorCommand = new AsyncCommand(async (parameter, cancellationToken) =>
{
await Task.Delay(TimeSpan.FromSeconds(2));
var color = new byte[3];
random.NextBytes(color);
Colors.Add(new MyColor(color[0], color[1], color[2]));
});
}
[DataMember]
public ObservableList<MyColor> Colors { get; } = new();
[DataMember]
public AsyncCommand AddColorCommand { get; }
[DataContract]
public class MyColor
{
public MyColor(byte r, byte g, byte b)
{
ColorText = Color = $"#{r:X2}{g:X2}{b:X2}";
}
[DataMember]
public string ColorText { get; }
[DataMember]
public string Color { get; }
}
}
Bu kodda yalnızca birkaç önemli şey vardır:
MyColor.Color
string
, ancak XAML'de verilere bağlı olduğunda olarakBrush
kullanılır, bu WPF tarafından sağlanan bir özelliktir.- Zaman
AddColorCommand
uyumsuz geri çağırma, uzun süre çalışan bir işlemin benzetimini yapmak için 2 saniyelik bir gecikme içerir. - Uzak kullanıcı arabirimi tarafından sağlanan genişletilmiş bir ObservableCollection T olan ObservableList<T'yi>>, daha iyi performans sağlayan aralık işlemlerini de desteklemek için kullanırız.<
MyToolWindowData
veMyColor
şu anda tüm özellikler salt okunur olduğundan INotifyPropertyChanged uygulamayın.
Uzun süre çalışan zaman uyumsuz komutları işleme
Uzak kullanıcı arabirimi ile normal WPF arasındaki en önemli farklardan biri, kullanıcı arabirimi ile uzantı arasındaki iletişimi içeren tüm işlemlerin zaman uyumsuz olmasıdır.
Gibi AddColorCommand
zaman uyumsuz komutlar, zaman uyumsuz bir geri çağırma sağlayarak bunu açık hale getirir.
Renk ekle düğmesine kısa bir süre içinde birden çok kez tıklarsanız bunun etkisini görebilirsiniz: Her komut yürütmesi 2 saniye sürdüğünden, birden çok yürütme paralel olarak gerçekleşir ve 2 saniyelik gecikme sona erdiğinde birden çok renk birlikte listede görünür. Bu, kullanıcıya Renk ekle düğmesinin çalışmadığını gösterebilir.
Bu sorunu gidermek için, zaman uyumsuz komut yürütülürken düğmeyi devre dışı bırakın. Bunu yapmak için en basit yol, komutu false olarak ayarlamaktır CanExecute
:
AddColorCommand = new AsyncCommand(async (parameter, ancellationToken) =>
{
AddColorCommand!.CanExecute = false;
try
{
await Task.Delay(TimeSpan.FromSeconds(2));
var color = new byte[3];
random.NextBytes(color);
Colors.Add(new MyColor(color[0], color[1], color[2]));
}
finally
{
AddColorCommand.CanExecute = true;
}
});
Kullanıcı düğmeye tıkladığında, geri çağırma komutu uzantıda zaman uyumsuz olarak yürütülür, geri arama olarak ayarlanır CanExecute
false
ve ardından Visual Studio işlemindeki ara sunucu veri bağlamı için zaman uyumsuz olarak yayılır ve düğme devre dışı bırakılır. Kullanıcı, düğme devre dışı bırakılmadan önce düğmeye iki kez tıklayabilir.
Daha iyi bir çözüm, zaman uyumsuz komutların RunningCommandsCount
özelliğini kullanmaktır:
<Button Content="Add color" Command="{Binding AddColorCommand}" IsEnabled="{Binding AddColorCommand.RunningCommandsCount.IsZero}" Grid.Row="1" />
RunningCommandsCount
, komutun şu anda kaç eşzamanlı zaman uyumsuz yürütmesinin devam etmekte olduğunu gösteren bir sayaçtır. Bu sayaç, düğmesine tıklandığında kullanıcı arabirimi iş parçacığında artırılır ve bu da öğesine bağlanarak IsEnabled
düğmeyi zaman uyumlu bir şekilde devre dışı bırakmanızı RunningCommandsCount.IsZero
sağlar.
Tüm Uzak UI komutları zaman uyumsuz olarak yürütülürken en iyi yöntem, komutun hızla tamamlanması beklense bile uygun olduğunda denetimleri devre dışı bırakmak için kullanmaktır RunningCommandsCount.IsZero
.
Zaman uyumsuz komutlar ve veri şablonları
Bu bölümde, kullanıcının listeden bir girişi silmesini sağlayan Kaldır düğmesini uygulayacaksınız. Her MyColor
nesne için tek bir zaman uyumsuz komut oluşturabilir veya içinde MyToolWindowData
tek bir zaman uyumsuz komutumuz olabilir ve hangi rengin kaldırılması gerektiğini belirlemek için bir parametre kullanabiliriz. İkinci seçenek daha temiz bir tasarımdır, bu nedenle bunu uygulayalım.
- Veri şablonunda düğme XAML'sini güncelleştirin:
<Button Content="Remove" Grid.Column="2"
Command="{Binding DataContext.RemoveColorCommand,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}"
CommandParameter="{Binding}"
IsEnabled="{Binding DataContext.RemoveColorCommand.RunningCommandsCount.IsZero,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}" />
- buna karşılık gelen
AsyncCommand
öğesiniMyToolWindowData
ekleyin:
[DataMember]
public AsyncCommand RemoveColorCommand { get; }
- öğesinin oluşturucusunda komutun zaman uyumsuz geri çağırmasını
MyToolWindowData
ayarlayın:
RemoveColorCommand = new AsyncCommand(async (parameter, ancellationToken) =>
{
await Task.Delay(TimeSpan.FromSeconds(2));
Colors.Remove((MyColor)parameter!);
});
Bu kod, uzun süre çalışan bir zaman uyumsuz komut yürütmenin benzetimini yapmak için kullanırTask.Delay
.
Veri bağlamında başvuru türleri
Önceki kodda, bir MyColor
nesne zaman uyumsuz komutun parametresi olarak alınır ve kaldırılacak öğeyi tanımlamak için başvuru eşitliğini (MyColor
geçersiz kılmayan Equals
bir başvuru türü olduğundan) kullanan bir List<T>.Remove
çağrının parametresi olarak kullanılır. Bu mümkündür, çünkü parametre kullanıcı arabiriminden alınsa bile, bunun tam örneği MyColor
şu anda veri bağlamının bir parçası olarak alınır, kopya alınmaz.
İşlemleri:
- uzak kullanıcı denetiminin veri bağlamını ara sunucu olarak kullanma;
- uzantıdan Visual Studio'ya güncelleştirme gönderme
INotifyPropertyChanged
veya bunun tersi; - uzantıdan Visual Studio'ya gözlemlenebilir koleksiyon güncelleştirmeleri gönderme veya tam tersi;
- zaman uyumsuz komut parametreleri gönderme
tümü başvuru türü nesnelerinin kimliğine saygı gösterir. Dizeler dışında, uzantıya geri aktarıldığında başvuru türü nesneleri hiçbir zaman çoğaltılır.
Resimde, veri bağlamındaki her başvuru türü nesnesine (komutlar, koleksiyon, her MyColor
biri ve hatta tüm veri bağlamı) Uzak UI altyapısı tarafından benzersiz bir tanımlayıcı atandığı görebilirsiniz. Kullanıcı ara sunucu renk nesnesi #5 için Kaldır düğmesine tıkladığında, nesnenin değeri değil benzersiz tanımlayıcı (#5) uzantıya geri gönderilir. Uzak UI altyapısı, karşılık gelen MyColor
nesneyi alıp zaman uyumsuz komutun geri çağırmasına parametre olarak geçirme işlemini üstlenir.
Birden çok bağlama ve olay işleme ile RunningCommandsCount
Uzantıyı bu noktada test ederseniz Kaldır düğmelerinden birine tıklandığında tüm Kaldır düğmelerinin devre dışı bırakıldığını görebilirsiniz:
İstenen davranış bu olabilir. Ancak, yalnızca geçerli düğmenin devre dışı bırakılmasını istediğinizi ve kullanıcının kaldırma için birden çok rengi kuyruğa almasına izin verebileceğinizi varsayalım: Tüm düğmeler arasında paylaşılan tek bir komutumuz olduğundan zaman uyumsuz komutunRunningCommandsCount
özelliğini kullanamıyoruz.
Her düğmeye bir RunningCommandsCount
özellik ekleyerek hedefimize ulaşabiliriz, böylece her renk için ayrı bir sayacımız olur. Bu özellikler, XAML'den http://schemas.microsoft.com/visualstudio/extensibility/2022/xaml
Uzak UI türlerini kullanmanıza olanak tanıyan ad alanı tarafından sağlanır:
Kaldır düğmesini aşağıdaki şekilde değiştiririz:
<Button Content="Remove" Grid.Column="2"
IsEnabled="{Binding Path=(vs:ExtensibilityUICommands.RunningCommandsCount).IsZero, RelativeSource={RelativeSource Self}}">
<vs:ExtensibilityUICommands.EventHandlers>
<vs:EventHandlerCollection>
<vs:EventHandler Event="Click"
Command="{Binding DataContext.RemoveColorCommand, ElementName=RootGrid}"
CommandParameter="{Binding}"
CounterTarget="{Binding RelativeSource={RelativeSource Self}}" />
</vs:EventHandlerCollection>
</vs:ExtensibilityUICommands.EventHandlers>
</Button>
vs:ExtensibilityUICommands.EventHandlers
Ekli özellik, herhangi bir olaya (örneğin, MouseRightButtonUp
) zaman uyumsuz komutlar atamaya olanak tanır ve daha gelişmiş senaryolarda yararlı olabilir.
vs:EventHandler
ayrıca bir CounterTarget
özelliğin eklenmesi gereken bir de olabilir: UIElement
vs:ExtensibilityUICommands.RunningCommandsCount
bu belirli olayla ilgili etkin yürütmeleri sayarak. Ekli bir özelliğe bağlarken parantez (örneğin Path=(vs:ExtensibilityUICommands.RunningCommandsCount).IsZero
) kullandığınızdan emin olun.
Bu durumda, her düğmeye ayrı bir etkin komut yürütme sayacı eklemek için kullanırız vs:EventHandler
. Ekli özelliğe bağlanıldığında IsEnabled
, ilgili renk kaldırıldığında yalnızca belirli bir düğme devre dışı bırakılır:
Kullanıcı XAML kaynak sözlükleri
Visual Studio 17.10'dan başlayarak, Uzak Kullanıcı Arabirimi XAML kaynak sözlüklerini destekler. Bu, birden çok Uzak Kullanıcı Arabirimi denetiminin stilleri, şablonları ve diğer kaynakları paylaşmasına olanak tanır. Ayrıca, farklı diller için farklı kaynaklar (örn. dizeler) tanımlamanızı sağlar.
Uzak kullanıcı arabirimi denetimi XAML'sine benzer şekilde, kaynak dosyalarının ekli kaynaklar olarak yapılandırılması gerekir:
<ItemGroup>
<EmbeddedResource Include="MyResources.xaml" />
<Page Remove="MyResources.xaml" />
</ItemGroup>
Uzak kullanıcı arabirimi, kaynak sözlüklerine WPF'den farklı bir şekilde başvurur: Bunlar denetimin birleştirilmiş sözlüklerine eklenmez (birleştirilmiş sözlükler Uzak KULLANıCı Arabirimi tarafından hiç desteklenmez) ancak denetimin .cs dosyasındaki adla başvurulur:
internal class MyToolWindowContent : RemoteUserControl
{
public MyToolWindowContent()
: base(dataContext: new MyToolWindowData())
{
this.ResourceDictionaries.AddEmbeddedResource(
"MyToolWindowExtension.MyResources.xaml");
}
...
AddEmbeddedResource
, varsayılan olarak projenin kök ad alanından, altında olabileceği alt klasör yolundan ve dosya adından oluşan ekli kaynağın tam adını alır. Proje dosyasında için bir LogicalName
EmbeddedResource
ayarlayarak bu adı geçersiz kılmak mümkündür.
Kaynak dosyasının kendisi normal bir WPF kaynak sözlüğüdür:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<system:String x:Key="removeButtonText">Remove</system:String>
<system:String x:Key="addButtonText">Add color</system:String>
</ResourceDictionary>
kullanarak DynamicResource
Uzak kullanıcı arabirimi denetimindeki kaynak sözlüğünden bir kaynağa başvurabilirsiniz:
<Button Content="{DynamicResource removeButtonText}" ...
XAML kaynak sözlüklerini yerelleştirme
Uzak UI kaynak sözlükleri, ekli kaynakları yerelleştirdiğiniz gibi yerelleştirilebilir: Aynı ada ve dil sonekine sahip diğer XAML dosyalarını oluşturursunuz, örneğin MyResources.it.xaml
İtalyanca kaynaklar için:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<system:String x:Key="removeButtonText">Rimuovi</system:String>
<system:String x:Key="addButtonText">Aggiungi colore</system:String>
</ResourceDictionary>
Proje dosyasında joker karakterler kullanarak tüm yerelleştirilmiş XAML sözlüklerini ekli kaynaklar olarak ekleyebilirsiniz:
<ItemGroup>
<EmbeddedResource Include="MyResources.*xaml" />
<Page Remove="MyResources.*xaml" />
</ItemGroup>
Veri bağlamında WPF türlerini kullanma
Şimdiye kadar, uzak kullanıcı denetimimizin veri bağlamı ilkel öğelerden (sayılar, dizeler vb.), gözlemlenebilir koleksiyonlardan ve ile DataContract
işaretlenmiş kendi sınıflarımızdan oluşuyordu. bazen karmaşık fırçalar gibi veri bağlamında basit WPF türleri eklemek yararlı olabilir.
VisualStudio.Genişletilebilirlik uzantısı Visual Studio işleminde bile çalışmayabileceği için WPF nesnelerini kullanıcı arabirimiyle doğrudan paylaşamaz. Uzantının WPF türlerine erişimi bile olmayabilir çünkü hedefleyebilir netstandard2.0
veya net6.0
(değişkene -windows
erişemez).
Uzak kullanıcı arabirimi, uzak kullanıcı denetiminin XamlFragment
veri bağlamında WPF nesnesinin XAML tanımının dahil edilmesini sağlayan türü sağlar:
[DataContract]
public class MyColor
{
public MyColor(byte r, byte g, byte b)
{
ColorText = $"#{r:X2}{g:X2}{b:X2}";
Color = new(@$"<LinearGradientBrush xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
StartPoint=""0,0"" EndPoint=""1,1"">
<GradientStop Color=""Black"" Offset=""0.0"" />
<GradientStop Color=""{ColorText}"" Offset=""0.7"" />
</LinearGradientBrush>");
}
[DataMember]
public string ColorText { get; }
[DataMember]
public XamlFragment Color { get; }
}
Yukarıdaki kodla, Color
özellik değeri veri bağlamı ara sunucusundaki bir LinearGradientBrush
nesneye dönüştürülür:
Uzak kullanıcı arabirimi ve iş parçacıkları
Zaman uyumsuz komut geri çağırmaları (ve INotifyPropertyChanged
kullanıcı arabirimi tarafından veri teklifi yoluyla güncelleştirilen değerler için geri çağırmalar) rastgele iş parçacığı havuzu iş parçacıklarında oluşturulur. Geri çağırmalar birer birer oluşturulur ve kod denetimi (ifade await
kullanarak) elde edene kadar çakışmaz.
Bu davranış oluşturucuya NonConcurrentSynchronizationContext RemoteUserControl
geçirilerek değiştirilebilir. Bu durumda, bu denetimle ilgili tüm zaman uyumsuz komut ve INotifyPropertyChanged
geri çağırmalar için sağlanan eşitleme bağlamını kullanabilirsiniz.
İlgili içerik
- VisualStudio.Genişletilebilirlik uzantısının bileşenleri.