Bagaimana cara saya menggunakan RelativeSource
binding WPF dan apa perbedaan kasus penggunaan?
Bagaimana cara saya menggunakan RelativeSource
binding WPF dan apa perbedaan kasus penggunaan?
Jawaban:
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}
AncestorType
.
FindAncestor
, sebelumnya AncestorType
, saya mendapatkan kesalahan berikut: "RelativeSource tidak dalam mode FindAncestor". (Dalam VS2013, versi Komunitas)
{Binding Path=DataContext.SomeProperty, RelativeSource=...
. Ini agak tak terduga bagi saya sebagai pemula ketika saya mencoba untuk mengikat DataContext orangtua dalam DataTemplate.
Binding RelativeSource={
RelativeSource Mode=FindAncestor, AncestorType={x:Type ItemType}
}
...
Atribut default RelativeSource
adalah Mode
properti. 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 sedang 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.
Diri 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.
Berikut adalah penjelasan yang lebih visual dalam konteks arsitektur MVVM:
{Binding Message}
(sedikit lebih sederhana ...)
Path=DataContext.Message
agar ikatannya bekerja. Ini masuk akal, mengingat bahwa Anda dapat melakukan binding relatif dengan lebar / tinggi / dll. sebuah kontrol.
Bechir Bejaoui memaparkan kasus penggunaan RelativeSources di WPF dalam 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 membuka semua kasus itu satu per satu.
- Mode Diri:
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}"/>
Tetapi 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 ketinggian 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.
- 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 di tingkat TextBlock.
- 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 Anda dapat katakan pada gambar di bawah, latar belakang dan konten diterapkan dari dalam tombol ke templat kontrol.
ListView
. Orang tua memiliki 2 ListView
level lebih di bawahnya. Ini membantu saya mencegah melewati data ke masing-masing vm berikutnya masing-masing ListView
'sDataTemplate
Dalam WPF RelativeSource
binding memperlihatkan tiga properties
untuk mengatur:
1. Mode: Ini adalah enum
yang bisa memiliki empat nilai:
Sebuah. PreviousData (
value=0
): Ini memberikan nilai sebelumnya dariproperty
yang terikatb. TemplatedParent (
value=1
): Ini digunakan ketika mendefinisikantemplates
kontrol apa saja dan ingin mengikat ke nilai / Properticontrol
.Misalnya, tentukan
ControlTemplate
:
<ControlTemplate>
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</ControlTemplate>
c. Self (
value=2
): Ketika kita ingin mengikat dariself
atauproperty
dari diri.Sebagai contoh: Kirim keadaan dicentang
checkbox
sebagaiCommandParameter
saat mengaturCommand
padaCheckBox
<CheckBox ...... CommandParameter="{Binding RelativeSource={RelativeSource Self},Path=IsChecked}" />
d. FindAncestor (
value=3
): Saat ingin mengikat dari orang tuacontrol
diVisual Tree
.Misalnya: Bind a
checkbox
inrecords
if agrid
, ifheader
checkbox
dicentang
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid}}, Path=DataContext.IsHeaderChecked, Mode=TwoWay}" />
2. AncestorType: ketika mode FindAncestor
kemudian menentukan tipe leluhur apa
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid}}
3. AncestorLevel: ketika modeFindAncestor
maka tingkat leluhur apa (jika ada dua tipe induk yang samavisual tree
)
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid, AncestorLevel=1}}
Di atas semuanya ada use case
RelativeSource binding
.
Patut dicatat bahwa bagi mereka yang tersandung pemikiran Silverlight ini:
Silverlight hanya menawarkan pengurangan sebagian dari perintah-perintah ini
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.
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 Relative Source dalam kode Behind .
Juga, halaman MSDN cukup bagus sejauh contoh: RelativeSource Class
Saya baru saja memposting solusi lain untuk mengakses DataContext dari elemen induk di Silverlight yang berfungsi untuk saya. Itu menggunakanBinding ElementName
.
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
, ikatan harus seperti:
Command="{Binding Path=DataContext.CommandProperty, RelativeSource={...}}"
Jika Anda tidak menambahkan DataContext di jalur Anda, pada saat eksekusi tidak dapat mengambil properti.
Ini adalah contoh penggunaan pola ini yang bekerja untuk saya di 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>
Jika 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 solusi di blognya di bawah [WPF] Cara mengikat data ketika DataContext tidak diwarisi . Dan itu bekerja dengan sangat brilian!
Jika blognya sedang down, Lampiran A berisi salinan mirror dari artikelnya .
Tolong jangan berkomentar di sini, silakan komentar langsung di posting blog-nya .
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 kisi, 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 harus digunakan untuk mendapatkan DataContext, karena kolom itu bukan milik pohon visual atau logis dari DataGrid.
Kita dapat mencoba mengubah ikatan 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 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 dapat dilakukan 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 yang 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 kita akan memanfaatkannya untuk membuat pekerjaan mengikat kita ...
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.