it-swarm-id.com

Bagaimana cara menggunakan binding WPF dengan RelativeSource?

Bagaimana cara menggunakan RelativeSource dengan binding WPF dan apa perbedaan kasus penggunaan?

550
David Schmitt

Jika Anda ingin mengikat ke properti lain di objek:

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

Jika Anda ingin mendapatkan properti pada leluhur:

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

Jika Anda ingin mendapatkan properti di induk templated (sehingga Anda dapat melakukan binding 2 arah di ControlTemplate)

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

atau, lebih pendek (ini hanya berfungsi untuk ikatan OneWay):

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

Atribut default RelativeSource adalah properti Mode. Satu set lengkap nilai valid diberikan di sini ( dari MSDN ):

  • PreviousData Memungkinkan Anda untuk mengikat item data sebelumnya (bukan kontrol yang berisi item data) dalam daftar item data yang ditampilkan.

  • TemplatedParent Mengacu pada elemen yang menjadi templat (di mana elemen terikat data) diterapkan. Ini mirip dengan pengaturan TemplateBindingExtension dan hanya berlaku jika Binding ada di dalam templat.

  • Cukup Mengacu pada elemen yang Anda atur pengikatannya dan memungkinkan Anda untuk mengikat satu properti elemen tersebut ke properti lain pada elemen yang sama.

  • FindAncestor Mengacu pada leluhur dalam rantai induk elemen terikat data. Anda dapat menggunakan ini untuk mengikat leluhur dari jenis tertentu atau subkelasnya. Ini adalah mode yang Anda gunakan jika Anda ingin menentukan AncestorType dan/atau AncestorLevel.

126
Drew Noakes

Berikut penjelasan yang lebih visual dalam konteks arsitektur MVVM:

enter image description here

120
Jeffrey Knight

Bayangkan kasus ini, persegi panjang yang kita inginkan tingginya selalu sama dengan lebarnya, katakanlah bujur sangkar. Kita bisa melakukan ini menggunakan nama elemen

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

Namun dalam kasus di atas kita wajib menunjukkan nama objek yang mengikat, yaitu persegi panjang. Kita dapat mencapai tujuan yang sama secara berbeda menggunakan RelativeSource

<Rectangle Fill="Red" Height="100" 
                   Stroke="Black" 
                   Width="{Binding RelativeSource={RelativeSource Self},
                   Path=Height}"/>

Untuk kasus itu kami tidak berkewajiban untuk menyebutkan nama objek yang mengikat dan Lebar akan selalu sama dengan Ketinggian setiap kali tinggi diubah.

Jika Anda ingin parameter Lebar menjadi setengah dari tinggi maka Anda dapat melakukan ini dengan menambahkan konverter ke ekstensi markup Binding. Mari kita bayangkan kasus lain sekarang:

 <TextBlock Width="{Binding RelativeSource={RelativeSource Self},
                   Path=Parent.ActualWidth}"/>

Kasing di atas digunakan untuk mengikat properti yang diberikan dari elemen yang diberikan ke salah satu orang tua langsung sebagai elemen ini memegang properti yang disebut Parent. Ini membawa kita ke mode sumber relatif lain yang merupakan FindAncestor.

40

Bechir Bejaoui memaparkan kasus penggunaan RelativeSources di WPF di artikelnya di sini :

RelativeSource adalah ekstensi markup yang digunakan dalam kasus-kasus pengikatan tertentu ketika kami mencoba untuk mengikat properti dari objek yang diberikan ke properti lain dari objek itu sendiri, ketika kami mencoba untuk mengikat properti dari objek ke yang lain dari orang tua relatifnya, ketika mengikat nilai properti dependensi ke sepotong XAML dalam kasus pengembangan kontrol kustom dan akhirnya dalam kasus menggunakan diferensial dari serangkaian data terikat. Semua situasi tersebut dinyatakan sebagai mode sumber relatif. Saya akan mengungkap semua kasus itu satu per satu.

  1. Mode Mandiri:

Bayangkan kasus ini, persegi panjang yang kita inginkan tingginya selalu sama dengan lebarnya, katakanlah bujur sangkar. Kita bisa melakukan ini menggunakan nama elemen

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

Namun dalam kasus di atas kita wajib menunjukkan nama objek yang mengikat, yaitu persegi panjang. Kita dapat mencapai tujuan yang sama secara berbeda menggunakan RelativeSource

<Rectangle Fill="Red" Height="100" 
               Stroke="Black" 
               Width="{Binding RelativeSource={RelativeSource Self},
               Path=Height}"/>

Untuk kasus itu kami tidak berkewajiban untuk menyebutkan nama objek yang mengikat dan Lebar akan selalu sama dengan Ketinggian setiap kali tinggi diubah.

Jika Anda ingin parameter Lebar menjadi setengah dari tinggi maka Anda dapat melakukan ini dengan menambahkan konverter ke ekstensi markup Binding. Mari kita bayangkan kasus lain sekarang:

 <TextBlock Width="{Binding RelativeSource={RelativeSource Self},
               Path=Parent.ActualWidth}"/>

Kasing di atas digunakan untuk mengikat properti yang diberikan dari elemen yang diberikan ke salah satu orang tua langsung sebagai elemen ini memegang properti yang disebut Parent. Ini membawa kita ke mode sumber relatif lain yang merupakan FindAncestor.

  1. Mode FindAncestor

Dalam hal ini, properti dari elemen yang diberikan akan diikat ke salah satu orang tuanya, Of Corse. Perbedaan utama dengan kasus di atas adalah kenyataan bahwa, terserah Anda untuk menentukan jenis leluhur dan peringkat leluhur dalam hierarki untuk mengikat properti. Omong-omong cobalah bermain dengan XAML ini

<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>

Situasi di atas adalah dari dua elemen TextBlock yang tertanam dalam serangkaian perbatasan dan elemen kanvas yang mewakili orang tua hierarkis mereka. TextBlock kedua akan menampilkan nama induk yang diberikan pada tingkat sumber relatif.

Jadi coba ubah AncestorLevel = 2 menjadi AncestorLevel = 1 dan lihat apa yang terjadi. Kemudian cobalah untuk mengubah jenis leluhur dari AncestorType = Border ke AncestorType = Kanvas dan lihat apa yang terjadi.

Teks yang ditampilkan akan berubah sesuai dengan jenis dan level Ancestor. Lalu apa yang terjadi jika level leluhur tidak cocok dengan tipe leluhur? Ini pertanyaan yang bagus, saya tahu Anda akan menanyakannya. Responsnya adalah tidak ada pengecualian yang akan dilemparkan dan tak berguna akan ditampilkan pada tingkat TextBlock.

  1. TemplatedParent

Mode ini memungkinkan untuk mengikat properti ControlTemplate yang diberikan ke properti kontrol yang diterapkan ControlTemplate. Untuk memahami dengan baik masalah di sini adalah contoh di bawah ini

<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>

Jika saya ingin menerapkan properti dari kontrol yang diberikan ke template kontrolnya maka saya dapat menggunakan mode TemplatedParent. Ada juga yang mirip dengan ekstensi markup ini yang merupakan Templat Binding yang merupakan semacam kependekan dari yang pertama, tetapi Templat Binding dievaluasi pada waktu kompilasi pada kontras TemplatedParent yang dievaluasi tepat setelah run time pertama. Seperti yang dapat Anda katakan pada gambar di bawah, latar belakang dan konten diterapkan dari dalam tombol ke templat kontrol.

34
Cornel Marian

Dalam WPF RelativeSource binding memperlihatkan tiga properties untuk mengatur:

1. Mode: Ini adalah enum yang dapat memiliki empat nilai:

a. PreviousData (value=0): Ini memberikan nilai sebelumnya dari property ke yang terikat

b. TemplatedParent (value=1): Ini digunakan ketika mendefinisikan templates dari sembarang kontrol dan ingin mengikat ke nilai/Properti control.

Misalnya, define ControlTemplate:

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

c. Diri (value=2): Ketika kita ingin mengikat dari self atau property dari diri.

Sebagai contoh: Kirim keadaan diperiksa checkbox sebagai CommandParameter sambil mengatur Command pada CheckBox

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

d. FindAncestor (value=3): Ketika ingin mengikat dari control induk dalam Visual Tree.

Misalnya: Ikatkan checkbox di records jika grid, jika headercheckbox dicentang

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

2. AncestorType: ketika mode adalah FindAncestor lalu tentukan jenis leluhur apa

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

3. AncestorLevel: ketika mode FindAncestor lalu level leluhur apa (jika ada dua tipe induk yang sama di visual tree)

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

Di atas adalah semua use case untuk RelativeSource binding.

Berikut ini tautan referensi .

23
Kylo Ren

Jangan lupa TemplatedParent:

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

atau

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

Saya membuat perpustakaan untuk menyederhanakan sintaks WPF yang mengikat termasuk membuatnya lebih mudah untuk menggunakan RelativeSource. Berikut ini beberapa contohnya. Sebelum:

{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}

Setelah:

{BindTo PathToProperty}
{BindTo Ancestor.typeOfAncestor.PathToProperty}
{BindTo Template.PathToProperty}
{BindTo #MyTextBox.Text}

Berikut adalah contoh bagaimana metode pengikatan disederhanakan. Sebelum:

// 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}

Setelah:

// C# code
private void SaveObject() {
 // do something
}

// XAML
{BindTo SaveObject()}

Anda dapat menemukan perpustakaan di sini: http://www.simplygoodcode.com/2012/08/simpler-wpf-binding.html

Catatan dalam contoh 'SEBELUM' yang saya gunakan untuk metode yang mengikat kode itu sudah dioptimalkan dengan menggunakan RelayCommand yang terakhir saya periksa bukan bagian asli WPF. Tanpa itu contoh 'SEBELUM' akan lebih lama.

13
Luis Perez

Patut dicatat bahwa bagi mereka yang tersandung pemikiran Silverlight ini:

Silverlight menawarkan pengurangan subset saja, dari perintah-perintah ini

13
Matthew Black

Beberapa potongan berguna:

Berikut cara melakukannya sebagian besar dalam kode:

Binding b = new Binding();
b.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, this.GetType(), 1);
b.Path = new PropertyPath("MyElementThatNeedsBinding");
MyLabel.SetBinding(ContentProperty, b);

Saya sebagian besar menyalin ini dari Binding Sumber Relatif dalam kode di belakang.

Juga, halaman MSDN cukup bagus sejauh contoh: Kelas RelativeSource

12
Nathan Cooper

Saya baru saja memposting solusi lain untuk mengakses DataContext dari elemen induk di Silverlight yang berfungsi untuk saya. Ini menggunakan Binding ElementName.

10
Juve

Saya tidak membaca setiap jawaban, tetapi saya hanya ingin menambahkan informasi ini dalam kasus pengikatan perintah sumber relatif tombol.

Saat Anda menggunakan sumber relatif dengan Mode=FindAncestor, pengikatannya harus seperti:

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

Jika Anda tidak menambahkan DataContext di jalur Anda, pada saat eksekusi tidak dapat mengambil properti.

9
Kevin VDF

Ini adalah contoh penggunaan pola ini yang bekerja untuk saya pada datagrid kosong.

<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

Jika suatu elemen bukan bagian dari pohon visual, maka RelativeSource tidak akan pernah berfungsi.

Dalam hal ini, Anda perlu mencoba teknik yang berbeda, dipelopori oleh Thomas Levesque.

Dia memiliki solusinya di blognya di bawah [WPF] Cara mengikat data ketika DataContext tidak diwarisi . Dan itu bekerja dengan sangat brilian!

Jika blognya tidak aktif, Lampiran A berisi salinan cermin artikelnya .

Tolong jangan berkomentar di sini, tolong komentar langsung pada posting blognya .

Lampiran A: Cermin posting blog

Properti DataContext di WPF sangat berguna, karena ia secara otomatis diwarisi oleh semua anak dari elemen tempat Anda menetapkannya; karena itu Anda tidak perlu mengaturnya lagi pada setiap elemen yang ingin Anda ikat. Namun, dalam beberapa kasus, DataContext tidak dapat diakses: itu terjadi untuk elemen yang bukan bagian dari pohon visual atau logis. Maka bisa sangat sulit untuk mengikat properti pada elemen-elemen itu ...

Mari kita ilustrasikan dengan contoh sederhana: kami ingin menampilkan daftar produk di DataGrid. Di kotak, kami ingin dapat menampilkan atau menyembunyikan kolom Harga, berdasarkan nilai properti ShowPrice yang diekspos oleh ViewModel. Pendekatan yang jelas adalah untuk mengikat Visibilitas kolom ke properti ShowPrice:

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

Sayangnya, mengubah nilai ShowPrice tidak berpengaruh, dan kolom selalu terlihat ... mengapa? Jika kita melihat jendela Output di Visual Studio, kita perhatikan baris berikut:

Kesalahan System.Windows.Data: 2: Tidak dapat menemukan mengatur FrameworkElement atau FrameworkContentElement untuk elemen target. BindingExpression: Path = ShowPrice; DataItem = null; elemen target adalah ‘DataGridTextColumn’ (HashCode = 32685253); properti target adalah ‘Visibilitas’ (ketik ‘Visibilitas’)

Pesannya agak samar, tetapi artinya sebenarnya cukup sederhana: WPF tidak tahu FrameworkElement mana yang akan digunakan untuk mendapatkan DataContext, karena kolom itu bukan milik pohon visual atau logis dari DataGrid.

Kita dapat mencoba untuk Tweak yang mengikat untuk mendapatkan hasil yang diinginkan, misalnya dengan mengatur RelativeSource ke DataGrid itu sendiri:

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

Atau kita dapat menambahkan Kotak Centang terikat ke ShowPrice, dan mencoba untuk mengikat visibilitas kolom ke properti IsChecked dengan menentukan nama elemen:

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

Tapi tidak satu pun dari solusi ini yang berhasil, kami selalu mendapatkan hasil yang sama ...

Pada titik ini, tampaknya satu-satunya pendekatan yang layak adalah mengubah visibilitas kolom dalam kode-belakang, yang biasanya kita lebih suka hindari ketika menggunakan pola MVVM ... Tapi saya tidak akan menyerah begitu cepat, setidaknya tidak sementara ada opsi lain untuk dipertimbangkan ????

Solusi untuk masalah kita sebenarnya cukup sederhana, dan memanfaatkan kelas Freezable. Tujuan utama kelas ini adalah untuk mendefinisikan objek yang memiliki keadaan yang dapat dimodifikasi dan hanya-baca, tetapi fitur menarik dalam kasus kami adalah bahwa objek Freezable dapat mewarisi DataContext bahkan ketika mereka tidak berada di pohon visual atau logis. Saya tidak tahu mekanisme pasti yang memungkinkan perilaku ini, tetapi kami akan memanfaatkannya untuk membuat pekerjaan mengikat kami ...

Idenya adalah untuk membuat kelas (saya menyebutnya BindingProxy untuk alasan yang harus segera menjadi jelas) yang mewarisi Freezable dan mendeklarasikan properti ketergantungan data:

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));
}

Kami kemudian dapat mendeklarasikan instance kelas ini di sumber daya DataGrid, dan mengikat properti Data ke DataContext saat ini:

<DataGrid.Resources>
    <local:BindingProxy x:Key="proxy" Data="{Binding}" />
</DataGrid.Resources>

Langkah terakhir adalah menentukan objek BindingProxy ini (mudah diakses dengan StaticResource) sebagai Sumber untuk pengikatan:

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

Perhatikan bahwa jalur pengikatan telah diawali dengan "Data", karena jalur sekarang relatif terhadap objek BindingProxy.

Pengikatan sekarang berfungsi dengan benar, dan kolom ditampilkan dengan benar atau disembunyikan berdasarkan properti ShowPrice.

4
Contango