RelativeSource
WPF bağlarıyla nasıl kullanırım ve farklı kullanım durumları nelerdir?
RelativeSource
WPF bağlarıyla nasıl kullanırım ve farklı kullanım durumları nelerdir?
Yanıtlar:
Nesnedeki başka bir özelliğe bağlamak istiyorsanız:
{Binding Path=PathToProperty, RelativeSource={RelativeSource Self}}
Bir atada mülk edinmek istiyorsanız:
{Binding Path=PathToProperty,
RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}}
Şablonlu üst öğede bir özellik almak istiyorsanız (böylece bir ControlTemplate'te 2 yönlü bağlamalar yapabilirsiniz)
{Binding Path=PathToProperty, RelativeSource={RelativeSource TemplatedParent}}
veya daha kısa (bu yalnızca OneWay bağlantıları için geçerlidir):
{TemplateBinding Path=PathToProperty}
AncestorType
.
FindAncestor
, önce AncestorType
, aşağıdaki hatayı alıyorum: "RelativeSource FindAncestor modunda değil". (VS2013'te, Topluluk sürümü)
{Binding Path=DataContext.SomeProperty, RelativeSource=...
. Bir DataTemplate içinde bir ebeveynin DataContext bağlamak için çalışırken bir acemi olarak bu benim için biraz beklenmedik oldu.
Binding RelativeSource={
RelativeSource Mode=FindAncestor, AncestorType={x:Type ItemType}
}
...
Varsayılan özellik RelativeSource
olan Mode
özellik. Tam bir geçerli değerler kümesi burada verilmiştir ( MSDN'den ):
PreviousData Görüntülenen veri öğeleri listesinde önceki veri öğesini (veri öğesini içeren denetimi değil) bağlamanızı sağlar.
TemplatedParent Şablonun (veriye bağlı öğenin bulunduğu) uygulandığı öğeyi ifade eder. Bu, bir TemplateBindingExtension ayarına benzer ve yalnızca Ciltleme bir şablon içindeyse uygulanabilir.
Kendini Bağlamayı ayarladığınız öğeyi ifade eder ve o öğenin bir özelliğini aynı öğedeki başka bir özelliğe bağlamanızı sağlar.
FindAncestor Veriye bağlı öğenin üst zincirindeki atası ifade eder. Belirli bir türde veya alt sınıflarında bir ataya bağlanmak için bunu kullanabilirsiniz. Bu, AncestorType ve / veya AncestorLevel belirtmek istiyorsanız kullandığınız moddur.
MVVM mimarisi bağlamında daha görsel bir açıklama:
{Binding Message}
(biraz daha basit ...)
Path=DataContext.Message
Bağlanmanın işe yaramasını sağlamak için açıkça ayarlamam gerekiyordu. Genişlik / yükseklik / vb. İçin göreli bağlamalar yapabileceğiniz göz önüne alındığında bu mantıklıdır. bir kontrol.
Bechir Bejaoui, WPF'deki RelativeSources kullanım örneklerini buradaki makalesinde ortaya koyuyor :
RelativeSource, belirli bir nesnenin bir özelliğini nesnenin kendisinin başka bir özelliğine bağlamaya çalıştığımızda, bir nesnenin bir özelliğini göreceli üst öğelerinden başka birine bağlamaya çalıştığımızda, özellikle bağlayıcı durumlarda kullanılan bir biçimlendirme uzantısıdır, özel kontrol geliştirme durumunda ve son olarak bir bağlı veri serisinin diferansiyelinin kullanılması durumunda bir bağımlılık özelliği değerini bir XAML parçasına bağlarken. Tüm bu durumlar göreceli kaynak modları olarak ifade edilir. Tüm bu davaları tek tek ortaya koyacağım.
- Mod Kendini:
Bir kare düşünelim, yüksekliğinin her zaman genişliğine eşit olmasını istediğimiz bir dikdörtgen düşünün. Bunu element adını kullanarak yapabiliriz
<Rectangle Fill="Red" Name="rectangle" Height="100" Stroke="Black" Canvas.Top="100" Canvas.Left="100" Width="{Binding ElementName=rectangle, Path=Height}"/>
Ancak bu durumda, bağlama nesnesinin adını, yani dikdörtgeni belirtmek zorundayız. RelativeSource'u kullanarak aynı amaca farklı şekilde ulaşabiliriz
<Rectangle Fill="Red" Height="100" Stroke="Black" Width="{Binding RelativeSource={RelativeSource Self}, Path=Height}"/>
Bu durumda, bağlayıcı nesnenin adından bahsetmek zorunda değiliz ve yükseklik her değiştirildiğinde Genişlik her zaman Yüksekliğe eşit olacaktır.
Genişliği yüksekliğin yarısı olacak şekilde parametreleştirmek istiyorsanız, Ciltleme işaretleme uzantısına bir dönüştürücü ekleyerek bunu yapabilirsiniz. Şimdi başka bir vakayı düşünelim:
<TextBlock Width="{Binding RelativeSource={RelativeSource Self}, Path=Parent.ActualWidth}"/>
Yukarıdaki durum, belirli bir öğenin belirli bir özelliğini doğrudan üst öğelerinden birine bağlamak için kullanılır, çünkü bu öğe Üst adlı bir özelliği barındırır. Bu bizi FindAncestor modu olan başka bir göreceli kaynak moduna götürür.
- Mode FindAncestor
Bu durumda, belirli bir öğenin bir mülkü ebeveynlerinden biri olan Corse'ye bağlanacaktır. Yukarıdaki durumla ilgili temel fark, özelliği bağlamak için hiyerarşideki ata türünü ve ata sırasını belirlemenin size kalmasıdır. Bu arada bu XAML parçasıyla oynamaya çalışın
<Canvas Name="Parent0"> <Border Name="Parent1" Width="{Binding RelativeSource={RelativeSource Self}, Path=Parent.ActualWidth}" Height="{Binding RelativeSource={RelativeSource Self}, Path=Parent.ActualHeight}"> <Canvas Name="Parent2"> <Border Name="Parent3" Width="{Binding RelativeSource={RelativeSource Self}, Path=Parent.ActualWidth}" Height="{Binding RelativeSource={RelativeSource Self}, Path=Parent.ActualHeight}"> <Canvas Name="Parent4"> <TextBlock FontSize="16" Margin="5" Text="Display the name of the ancestor"/> <TextBlock FontSize="16" Margin="50" Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Border}, AncestorLevel=2},Path=Name}" Width="200"/> </Canvas> </Border> </Canvas> </Border> </Canvas>
Yukarıdaki durum, bir dizi kenarlığa gömülü iki TextBlock öğesinden ve hiyerarşik ebeveynlerini temsil eden kanvas öğelerinden oluşur. İkinci TextBlock, göreli kaynak düzeyinde belirli bir üst öğenin adını görüntüler.
Bu yüzden AncestorLevel = 2'yi AncestorLevel = 1 olarak değiştirmeye çalışın ve ne olduğunu görün. Ardından, ata türünü AncestorType = Border yerine AncestorType = Canvas olarak değiştirmeye çalışın ve ne olduğunu görün.
Görüntülenen metin Ata türüne ve seviyesine göre değişecektir. Öyleyse, ata seviyesi ata tipine uygun değilse ne olur? Bu iyi bir soru, bunu sormak üzere olduğunu biliyorum. Yanıt istisna değildir ve TextBlock düzeyinde hiçbir şey görüntülenmez.
- TemplatedParent
Bu mod, belirli bir ControlTemplate özelliğini ControlTemplate'in uygulandığı denetim özelliğine bağlamayı sağlar. Buradaki sorunu iyi anlamak için aşağıdaki örnek
<Window.Resources> <ControlTemplate x:Key="template"> <Canvas> <Canvas.RenderTransform> <RotateTransform Angle="20"/> </Canvas.RenderTransform> <Ellipse Height="100" Width="150" Fill="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Background}"> </Ellipse> <ContentPresenter Margin="35" Content="{Binding RelativeSource={RelativeSource TemplatedParent},Path=Content}"/> </Canvas> </ControlTemplate> </Window.Resources> <Canvas Name="Parent0"> <Button Margin="50" Template="{StaticResource template}" Height="0" Canvas.Left="0" Canvas.Top="0" Width="0"> <TextBlock FontSize="22">Click me</TextBlock> </Button> </Canvas>
Belirli bir denetimin özelliklerini kendi denetim şablonuna uygulamak istersem TemplatedParent modunu kullanabilirim. Bu işaretleme uzantısına benzer bir tane de vardır, bu da birincisinin kısa bir eli olan TemplateBinding'dır, ancak TemplateBinding ilk çalıştırma süresinden hemen sonra değerlendirilen TemplatedParent'in kontrastında derleme zamanında değerlendirilir. Aşağıdaki resimde de belirtebileceğiniz gibi, arka plan ve içerik düğmeden kontrol şablonuna uygulanır.
ListView
. Ebeveynin altında 2 ListView
seviye daha vardır . Bu bana her birinin sonraki her vm veri geçirerek önlemek yardımcı ListView
'sDataTemplate
WPF'de RelativeSource
ciltleme üç properties
sete maruz kalır:
1. Mod: Bu enum
dört değere sahip olabilen bir değerdir:
a. PreviousData (
value=0
): Bu önceki değer atarproperty
bağlı birineb. TemplatedParent (
value=1
):templates
Herhangi bir denetim tanımlanırken kullanılır ve öğesinin/ Değerine bir değer / Özellik bağlamak istercontrol
.Örneğin,
ControlTemplate
şunları tanımlayın :
<ControlTemplate>
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</ControlTemplate>
c. Benlik (
value=2
): Benliktenself
veyaproperty
benliktenbağlanmak istediğimizde.Örneğin: Gönder durumunu kontrol
checkbox
olarakCommandParameter
ayarlanırkenCommand
üzerindeCheckBox
<CheckBox ...... CommandParameter="{Binding RelativeSource={RelativeSource Self},Path=IsChecked}" />
d. FindAncestor (
value=3
):control
içindekibir üst öğeden bağlanmak istediğinizdeVisual Tree
.Örneğin: Bind bir
checkbox
derecords
bir eğergrid
, eğerheader
checkbox
kontrol edilir
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid}}, Path=DataContext.IsHeaderChecked, Mode=TwoWay}" />
2. AncestorType: mod FindAncestor
ne zaman ne tür bir atası tanımlayacağınızı tanımlar
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid}}
3. AncestorLevel: modFindAncestor
nezamano atasının seviyesidir (eğer aynı tipte iki ebeveyn varsavisual tree
)
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid, AncestorLevel=1}}
Yukarıda tüm kullanım durumları vardır
RelativeSource binding
.
Bu Silverlight düşüncesinde tökezleyenler için:
Silverlight, bu komutların yalnızca alt kümelerini azaltır
RelativeSource kullanımını kolaylaştırmak da dahil olmak üzere WPF'nin bağlayıcı sözdizimini basitleştirmek için bir kütüphane oluşturdum. İşte bazı örnekler. Önce:
{Binding Path=PathToProperty, RelativeSource={RelativeSource Self}}
{Binding Path=PathToProperty, RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}}
{Binding Path=PathToProperty, RelativeSource={RelativeSource TemplatedParent}}
{Binding Path=Text, ElementName=MyTextBox}
Sonra:
{BindTo PathToProperty}
{BindTo Ancestor.typeOfAncestor.PathToProperty}
{BindTo Template.PathToProperty}
{BindTo #MyTextBox.Text}
İşte yöntem bağlamanın nasıl basitleştirildiğine bir örnek. Önce:
// C# code
private ICommand _saveCommand;
public ICommand SaveCommand {
get {
if (_saveCommand == null) {
_saveCommand = new RelayCommand(x => this.SaveObject());
}
return _saveCommand;
}
}
private void SaveObject() {
// do something
}
// XAML
{Binding Path=SaveCommand}
Sonra:
// C# code
private void SaveObject() {
// do something
}
// XAML
{BindTo SaveObject()}
Kütüphaneyi burada bulabilirsiniz: http://www.simplygoodcode.com/2012/08/simpler-wpf-binding.html
Yöntemin bağlanması için kullandığım 'ÖNCE' örneğinde, kodun RelayCommand
WPF'nin yerel bir parçası olmadığı son kullanılan denetleme kullanılarak önceden optimize edildiğine dikkat edin . Bu olmadan 'ÖNCE' örneği daha da uzun olurdu.
Bazı yararlı bitler ve parçalar:
Çoğunlukla kodda nasıl yapılacağı aşağıda açıklanmıştır:
Binding b = new Binding();
b.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, this.GetType(), 1);
b.Path = new PropertyPath("MyElementThatNeedsBinding");
MyLabel.SetBinding(ContentProperty, b);
Bunu büyük ölçüde Binding Bağıl Kaynak Binding Behind kodunda.
Ayrıca, MSDN sayfası örneklere göre oldukça iyidir: RelativeSource Class
Yeni bir çözüm daha gönderdimSilverlight'ta benim için çalışan bir üst öğenin DataContext'e erişmek için . Kullanır Binding ElementName
.
Her cevabı okumadım, ancak sadece bir düğmenin göreli kaynak komutu bağlanması durumunda bu bilgileri eklemek istiyorum.
İle göreli bir kaynak kullandığınızda Mode=FindAncestor
, bağlanma şöyle olmalıdır:
Command="{Binding Path=DataContext.CommandProperty, RelativeSource={...}}"
Yolunuza DataContext eklemezseniz, yürütme sırasında özelliği alamaz.
Bu, benim için boş veri ızgaralarında çalışan bu modelin kullanımına bir örnektir.
<Style.Triggers>
<DataTrigger Binding="{Binding Items.Count, RelativeSource={RelativeSource Self}}" Value="0">
<Setter Property="Background">
<Setter.Value>
<VisualBrush Stretch="None">
<VisualBrush.Visual>
<TextBlock Text="We did't find any matching records for your search..." FontSize="16" FontWeight="SemiBold" Foreground="LightCoral"/>
</VisualBrush.Visual>
</VisualBrush>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
Bir öğe görsel ağacın bir parçası değilse, RelativeSource asla çalışmaz.
Bu durumda, Thomas Levesque öncülüğünde farklı bir teknik denemeniz gerekir.
Blogunda [WPF] altında DataContext devralınmamış olduğunda verilere nasıl bağlanacağı konusunda çözümü var . Ve kesinlikle mükemmel çalışıyor!
Blogunun kapanması gibi beklenmedik bir durumda, Ek A makalesinin ayna bir kopyasını içerir .
Lütfen buraya yorum yapmayın, lütfen doğrudan blog yayınına yorum yapın .
WPF'deki DataContext özelliği son derece kullanışlıdır, çünkü atadığınız öğenin tüm alt öğeleri tarafından otomatik olarak devralınır; bu nedenle bağlamak istediğiniz her öğeye tekrar ayarlamanıza gerek yoktur. Ancak, bazı durumlarda DataContext'e erişilemez: görsel veya mantıksal ağacın parçası olmayan öğeler için olur. Bu unsurlar üzerine bir mülk bağlamak çok zor olabilir…
Basit bir örnekle açıklayalım: DataGrid'de bir ürün listesi görüntülemek istiyoruz. Izgarada, ViewModel tarafından maruz bırakılan bir ShowPrice özelliğinin değerine bağlı olarak Fiyat sütununu gösterebilir veya gizlemek istiyoruz. Açık yaklaşım, sütunun Görünürlüğünü ShowPrice özelliğine bağlamaktır:
<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
Visibility="{Binding ShowPrice,
Converter={StaticResource visibilityConverter}}"/>
Ne yazık ki, ShowPrice değerini değiştirmenin bir etkisi yoktur ve sütun her zaman görünür… neden? Visual Studio'daki Çıktı penceresine bakarsak, aşağıdaki satırı fark ederiz:
System.Windows.Data Hatası: 2: Hedef öğe için geçerli FrameworkElement veya FrameworkContentElement bulunamıyor. BindingExpression: Yol ShowPrice =; Dataıtem = null; hedef öğe 'DataGridTextColumn' (HashCode = 32685253); target özelliği 'Görünürlük'tür (' Görünürlük 'yazın)
İleti oldukça şifreli, ancak anlamı aslında oldukça basit: WPF, DataContext'i almak için hangi FrameworkElement öğesinin kullanılacağını bilmiyor, çünkü sütun DataGrid'in görsel veya mantıksal ağacına ait değil.
İstediğiniz sonucu elde etmek için, örneğin RelativeSource'u DataGrid'in kendisine ayarlayarak bağlamayı değiştirmeyi deneyebiliriz:
<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
Visibility="{Binding DataContext.ShowPrice,
Converter={StaticResource visibilityConverter},
RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}}"/>
Veya ShowPrice'a bağlı bir CheckBox ekleyebilir ve öğe adını belirterek sütun görünürlüğünü IsChecked özelliğine bağlamaya çalışabiliriz:
<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
Visibility="{Binding IsChecked,
Converter={StaticResource visibilityConverter},
ElementName=chkShowPrice}"/>
Ancak bu geçici çözümlerin hiçbiri işe yaramıyor gibi görünüyor, hep aynı sonucu alıyoruz…
Bu noktada, tek geçerli yaklaşım, MVVM kalıbını kullanırken genellikle kaçınmayı tercih ettiğimiz arka plandaki sütun görünürlüğünü değiştirmek olacak gibi görünüyor ... Ama çok yakında pes etmeyeceğim, en azından dikkate alınacak başka seçenekler varken 😉
Sorunumuzun çözümü aslında oldukça basittir ve Freezable sınıfından yararlanır. Bu sınıfın temel amacı değiştirilebilir ve salt okunur duruma sahip nesneleri tanımlamaktır, ancak bizim durumumuzdaki ilginç özellik, Dondurulabilir nesnelerin görsel veya mantıksal ağaçta olmasalar bile DataContext'i devralabilmesidir. Bu davranışı mümkün kılan tam mekanizmayı bilmiyorum, ama bağlayıcı çalışmamızı sağlamak için bundan yararlanacağız…
Fikir, Freezable'ı devralan ve bir Veri bağımlılığı özelliği bildiren bir sınıf (çok yakında belirgin hale gelmesi için BindingProxy olarak adlandırdım) oluşturmaktır:
public class BindingProxy : Freezable
{
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
// Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
Daha sonra DataGrid'in kaynaklarında bu sınıfın bir örneğini bildirebilir ve Data özelliğini geçerli DataContext'e bağlayabiliriz:
<DataGrid.Resources>
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
</DataGrid.Resources>
Son adım, bu BindingProxy nesnesini (StaticResource ile kolayca erişilebilir) ciltleme için Kaynak olarak belirtmektir:
<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
Visibility="{Binding Data.ShowPrice,
Converter={StaticResource visibilityConverter},
Source={StaticResource proxy}}"/>
Bağlama yolunun "Data" ile önek eklendiğini unutmayın, çünkü yol şimdi BindingProxy nesnesine görecelidir.
Bağlama şimdi düzgün çalışıyor ve sütun ShowPrice özelliğine göre düzgün bir şekilde gösteriliyor veya gizleniyor.