it-swarm-tr.com

WPF bağlayıcılarını RelativeSource ile nasıl kullanırım?

RelativeSource WPF bağlamaları ile nasıl kullanırım ve farklı kullanım durumları nelerdir?

550
David Schmitt

Nesne üzerindeki başka bir özelliğe bağlanmak istiyorsanız:

{Binding Path=PathToProperty, RelativeSource={RelativeSource Self}}

Bir atadan mülk elde etmek istiyorsanız:

{Binding Path=PathToProperty,
    RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}}

Şablonlu ebeveyn üzerinde bir özellik edinmek istiyorsanız (yani bir ControlTemplate'te 2 yönlü ciltleme yapabilirsiniz)

{Binding Path=PathToProperty, RelativeSource={RelativeSource TemplatedParent}}

veya daha kısa (bu yalnızca OneWay ciltlemeleri için çalışır):

{TemplateBinding Path=PathToProperty}
735
Abe Heidebrecht
Binding RelativeSource={
    RelativeSource Mode=FindAncestor, AncestorType={x:Type ItemType}
}
...

RelativeSource öğesinin varsayılan niteliği Mode özelliğidir. Tam bir geçerli değerler kümesi burada verilmiştir ( MSDN'den ):

  • Önceki Veri Görüntülenen veri öğeleri listesinde önceki veri öğesini (veri öğesini içeren bu kontrol değil) ciltlemenizi sağlar.

  • TemplatedParent Şablonun (içinde bulunan veri bağlı öğenin bulunduğu) uygulandığı öğeyi ifade eder. Bu, TemplateBindingExtension ayarına benzer ve yalnızca Ciltleme bir şablonun içindeyse uygulanabilir.

  • Self Bağlamayı ayarladığınız öğeyi ifade eder ve bu öğenin bir özelliğini aynı öğedeki başka bir özelliğe bağlamanızı sağlar.

  • FindAncestor Veriye bağlı öğenin ana zincirindeki atayı ifade eder. Bunu, belirli bir türün atalarına veya alt sınıflarına bağlamak için kullanabilirsiniz. AncestorType ve/veya AncestorLevel belirtmek istiyorsanız kullandığınız mod budur.

126
Drew Noakes

MVVM mimarisi bağlamında daha görsel bir açıklama:

enter image description here

120
Jeffrey Knight

Diyelim ki bu durumu, yüksekliğinin daima genişliğine eşit olmasını istediğimiz bir dikdörtgen, bir kare diyelim. Bunu element ismini kullanarak yapabiliriz.

<Rectangle Fill="Red" Name="rectangle" 
                    Height="100" Stroke="Black" 
                    Canvas.Top="100" Canvas.Left="100"
                    Width="{Binding ElementName=rectangle,
                    Path=Height}"/>

Fakat bu yukarıdaki durumda, bağlayıcı nesnenin 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, ciltleme nesnesinin adını söylemek zorunda değiliz ve Genişlik her değiştiğinde Yükseklik her zaman Yüksekliğe eşit olacaktır.

Genişliği yüksekliğin yarısı olacak şekilde değiştirmek istiyorsanız, bunu Ciltleme işaretleme uzantısına bir dönüştürücü ekleyerek yapabilirsiniz. Şimdi başka bir vaka 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, Ebeveyn denilen bir özelliğe sahiptir. Bu, bizi FindAncestor olan başka bir göreceli kaynak moduna yönlendirir.

40

Bechir Bejaoui, WPF’deki RelativeSources’un kullanım durumlarını burada makalesi :

RelativeSource, belirli bir nesnenin özelliğini, nesnenin bir özelliğini, göreceli ebeveynlerinden birine bağlamaya çalıştığımızda, belirli bir nesnenin özelliğini nesnenin kendisinin başka bir özelliğine bağlamaya çalıştığımızda, belirli bağlayıcı durumlarda kullanılır. Özel kontrol geliştirme durumunda ve son olarak bir dizi bağlı veri serisinin kullanılması durumunda bir bağımlılık özellik değerini bir XAML parçasına bağlarken. Bu durumların tümü göreceli kaynak modları olarak ifade edilir. Tüm bu davaları birer birer göstereceğim.

  1. Mod kendini:

Diyelim ki bu durumu, yüksekliğinin daima genişliğine eşit olmasını istediğimiz bir dikdörtgen, bir kare diyelim. Bunu element ismini kullanarak yapabiliriz.

<Rectangle Fill="Red" Name="rectangle" 
                Height="100" Stroke="Black" 
                Canvas.Top="100" Canvas.Left="100"
                Width="{Binding ElementName=rectangle,
                Path=Height}"/>

Fakat bu yukarıdaki durumda, bağlayıcı nesnenin 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, ciltleme nesnesinin adını söylemek zorunda değiliz ve Genişlik her değiştiğinde Yükseklik her zaman Yüksekliğe eşit olacaktır.

Genişliği yüksekliğin yarısı olacak şekilde değiştirmek istiyorsanız, bunu Ciltleme işaretleme uzantısına bir dönüştürücü ekleyerek yapabilirsiniz. Şimdi başka bir vaka 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, Ebeveyn denilen bir özelliğe sahiptir. Bu, bizi FindAncestor olan başka bir göreceli kaynak moduna yönlendirir.

  1. Mod FindAncestor

Bu durumda, belirli bir öğenin mülkü, ebeveynlerinden biri olan Corse ile ilişkilendirilecektir. Yukarıdaki durumla ilgili temel fark, mülkü bağlamak için ata türünü ve ata sırasını belirlemek sizin sorumluluğunuzda olduğudur. Bu arada, bu XAML parçası ile 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 sınır içine yerleştirilmiş iki TextBlock elemanı ve hiyerarşik ebeveynlerini temsil eden kanvas elemanlardır. İkinci TextBlock, verilen ebeveyne adını göreceli kaynak seviyesinde gösterecektir.

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 = Kenarlık ve AncestorType = Kanvas 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 türüne uygun değilse ne olur? Bu iyi bir soru, sormak üzere olduğunu biliyorum. Yanıt, istisnalar atılmayacak ve TextBlock düzeyinde hiçbir şey gösterilmeyecek.

  1. TemplatedParent

Bu mod, belirli bir ControlTemplate özelliğini, ControlTemplate'in uygulandığı denetimin bir özelliğine bağlamayı sağlar. Burada konuyu iyi anlamak için aşağıdaki bir ö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 kontrolün özelliklerini kontrol şablonuna uygulamak istersem TemplatedParent modunu kullanabilirim. Aynı zamanda ilk işaretleme işleminin bir nevi kısa kolu olan TemplateBinding olan bu işaretleme uzantısına da benzer bir şey var, ancak TemplateBinding derleme zamanında, ilk çalışma zamanından hemen sonra değerlendirilen TemplatedParent'ın aksine değerlendirilir. Aşağıdaki şekilde söyleyebileceğiniz gibi, arka plan ve içerik butonun içinden kontrol şablonuna uygulanır.

34
Cornel Marian

WPF RelativeSource ciltlemesinde, ayarlanacak üç properties öğesi gösterilir:

1. Mode: Bu, dört değere sahip olabilecek bir enum'dır:

a. Önceki Veriler (value=0): property öğesinin önceki değerine bağlı olana atar

b. TemplatedParent (value=1): Bu, herhangi bir kontrolün templates tanımlanırken ve control öğesinin bir değerine/Özellikine bağlanmak istediğinizde kullanılır.

Örneğin,ControlTemplate tanımlayın:

  <ControlTemplate>
        <CheckBox IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
 </ControlTemplate>

c. Self (value=2): Kendini self veya property öğesinden bağlamak istediğimizde.

Örneğin:checkbox üzerinde CommandParameter ayarlanırken Command işaretli durumunu CheckBox olarak gönder

<CheckBox ...... CommandParameter="{Binding RelativeSource={RelativeSource Self},Path=IsChecked}" />

d. FindAncestor (value=3): Visual Tree öğesinde control üst öğesinden bağlanmak istediğinizde.

Örneğin:checkbox __ __ içindeki bir records bağlayın, grid ise headercheckbox işaretliyse

<CheckBox IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid}}, Path=DataContext.IsHeaderChecked, Mode=TwoWay}" />

2. AncestorType: mod FindAncestor olduğunda ne tür bir atası tanımladığınızı

RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid}}

3.. AncestorLevel: mod ne zaman FindAncestor, ne tür bir atası varsa (visual tree'da iki tür ebeveyn varsa)

RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid, AncestorLevel=1}}

Yukarıda, RelativeSource binding için kullanım durumları belirtilmiştir.

İşte bir referans bağlantısı .

23
Kylo Ren

TemplatedParent'ı unutma:

<Binding RelativeSource="{RelativeSource TemplatedParent}"/>

veya

{Binding RelativeSource={RelativeSource TemplatedParent}}
18
Bob King

WPF'nin bağlayıcı sözdizimini basitleştirmek için RelativeSource'u kullanmayı kolaylaştıran bir kütüphane yarattım. İş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

'BEFORE' örneğinde, bu kodun bağlanması için kullandığımı ve bu kodun zaten son olarak kontrol ettiğim RelayCommand kullanarak WPF'nin yerel bir parçası olmadığına dikkat edin. Bu olmadan 'ÖNCE' örneği daha uzun olurdu.

13
Luis Perez

Bu Silverlight düşüncesini tökezleyenler için:

Silverlight bu komutların yalnızca azaltılmış bir alt kümesini sunar

13
Matthew Black

Bazı yararlı bit ve parçalar:

İşte çoğunlukla kodda yapmak için:

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 Behind kodundaki Bağıl Kaynak Bağlama 'dan kopyaladım.

Ayrıca, MSDN sayfası örneklere göre oldukça iyidir: RelativeSource Class

12
Nathan Cooper

Silverlight'ta benim için çalışan bir ana öğenin DataContext'ine erişmek için başka bir çözüm yayınladım. Binding ElementName öğesini kullanır.

10
Juve

Her cevabı okumadım ama bir düğmenin göreceli kaynak komutu ile bağlanması durumunda bu bilgiyi eklemek istiyorum.

Mode=FindAncestor ile göreceli bir kaynak kullandığınızda, bağlamanın aşağıdaki gibi olması gerekir:

Command="{Binding Path=DataContext.CommandProperty, RelativeSource={...}}"

DataContext'i yolunuza eklemezseniz, yürütme zamanında bu özellik geri alınamaz.

9
Kevin VDF

Bu benim için boş datagrids üzerinde ç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>
8
Edd

Bir öğe görsel ağacın bir parçası değilse, RelativeSource asla çalışmayacak.

Bu durumda, Thomas Levesque öncülüğünde farklı bir teknik denemeniz gerekir.

Blogunda [WPF] DataContext miras alınmadığında verilere nasıl bağlanır? altında çözümü var. Ve kesinlikle zekice çalışıyor!

Beklenmedik bir şekilde blogunun kapalı olması durumunda, Ek A, makalesi 'in aynalı bir kopyasını içerir.

Lütfen burada yorum yapmayın, lütfen doğrudan blog gönderisine yorum yapın .

Ek A: Blog yazısı yansıması

WPF'deki DataContext özelliği son derece kullanışlıdır, çünkü atadığınız öğenin tüm çocukları tarafından otomatik olarak miras alı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 bir parçası olmayan öğeler için geçerlidir. Bu unsurlara bir özellik bağlamak o zaman zor olabilir…

Basit bir örnekle açıklayalım: Bir DataGrid'de bir ürün listesi görüntülemek istiyoruz. Kılavuzda, ViewModel tarafından gösterilen ShowPrice özelliğinin değerine bağlı olarak Fiyat sütununu göstermek veya gizlemek istiyoruz. Açık bir 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 durumda… neden? Visual Studio'daki Output 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 eleman 'DataGridTextColumn' (HashCode = 32685253); hedef özelliği ‘Görünürlük’ (‘Görünürlük’ yazın)

Mesaj oldukça şifrelidir, ancak anlamı aslında oldukça basittir: WPF, DataContext'i almak için hangi FrameworkElement öğesinin kullanılacağını bilmez, çünkü sütun DataGrid'in görsel veya mantıksal ağacına ait değildir.

İstenen sonucu elde etmek için bağlayıcıyı ince ayarlamayı deneyebiliriz, örneğin RelativeSource'u DataGrid'in kendisine ayarlayarak:

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                Visibility="{Binding DataContext.ShowPrice,
                Converter={StaticResource visibilityConverter},
                RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}}"/>

Ya da ShowPrice'a bağlı bir CheckBox ekleyebilir ve öğe adını belirterek sütun görünürlüğünü IsChecked özelliğine bağlamayı deneyebiliriz:

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                Visibility="{Binding IsChecked,
                Converter={StaticResource visibilityConverter},
                ElementName=chkShowPrice}"/>

Ancak bu geçici çözümlerin hiçbiri çalışmıyor gibi görünüyor, her zaman aynı sonucu elde ediyoruz…

Bu noktada, tek geçerli yaklaşımın, MVVM modelini kullanırken genellikle kaçınmayı tercih ettiğimiz kodların arkasındaki sütun görünürlüğünü değiştirmek olduğu anlaşılıyor… Ama çok yakında pes etmeyeceğim. dikkate alınması gereken diğer seçenekler varken ????

Sorunumuzun çözümü aslında oldukça basittir ve Dondurulabilir sınıftan yararlanır. Bu sınıfın temel amacı, değiştirilebilir ve salt okunur bir duruma sahip nesneleri tanımlamaktır, ancak bizim durumumuzdaki ilginç özellik, Freezable nesnelerin görsel veya mantıksal ağaçta olmasalar bile DataContext'i devralabilecekleridir. Bu davranışı mümkün kılan mekanizmayı tam olarak bilmiyorum, ancak bağlayıcı çalışmamızı sağlamak için bundan yararlanacağız…

Buradaki fikir, Dondurulabilir bir miras alan ve Veri bağımlılığı özelliğini ilan eden bir sınıf oluşturmaktır (Ben çok yakında belli olması gereken nedenlerden dolayı BindingProxy olarak adlandırdım):

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 bu sınıfın bir örneğini DataGrid'in kaynaklarında ilan edebilir ve Data özelliğini mevcut DataContext öğesine 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 Kaynağı olarak belirtmektir:

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                Visibility="{Binding Data.ShowPrice,
                Converter={StaticResource visibilityConverter},
                Source={StaticResource proxy}}"/>

Yol şimdi BindingProxy nesnesine göre olduğundan, bağlayıcı yolun “Veri” ile önceden eklendiğini unutmayın.

Bağlama artık düzgün çalışıyor ve sütun ShowPrice özelliğine göre düzgün bir şekilde gösteriliyor veya gizleniyor.

4
Contango