Làm cách nào để sử dụng RelativeSource
với các ràng buộc WPF và các trường hợp sử dụng khác nhau là gì?
Làm cách nào để sử dụng RelativeSource
với các ràng buộc WPF và các trường hợp sử dụng khác nhau là gì?
Câu trả lời:
Nếu bạn muốn liên kết với một thuộc tính khác trên đối tượng:
{Binding Path=PathToProperty, RelativeSource={RelativeSource Self}}
Nếu bạn muốn có một tài sản trên tổ tiên:
{Binding Path=PathToProperty,
RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}}
Nếu bạn muốn có một thuộc tính trên cha mẹ templated (vì vậy bạn có thể thực hiện các ràng buộc 2 chiều trong ControlTemplate)
{Binding Path=PathToProperty, RelativeSource={RelativeSource TemplatedParent}}
hoặc, ngắn hơn (điều này chỉ hoạt động cho các ràng buộc OneWay):
{TemplateBinding Path=PathToProperty}
AncestorType
.
FindAncestor
, trước đó AncestorType
, tôi gặp lỗi sau: "RelativeSource không ở chế độ FindAncestor". (Trong VS2013, phiên bản Cộng đồng)
{Binding Path=DataContext.SomeProperty, RelativeSource=...
. Điều này hơi bất ngờ đối với tôi khi là người mới khi tôi đang cố gắng liên kết với DataContext của cha mẹ trong DataTemplate.
Binding RelativeSource={
RelativeSource Mode=FindAncestor, AncestorType={x:Type ItemType}
}
...
Thuộc tính mặc định của RelativeSource
là thuộc Mode
tính. Một bộ đầy đủ các giá trị hợp lệ được đưa ra ở đây ( từ MSDN ):
BeforeData Cho phép bạn liên kết mục dữ liệu trước đó (không phải điều khiển có chứa mục dữ liệu) trong danh sách các mục dữ liệu được hiển thị.
TemplatedParent Đề cập đến phần tử mà mẫu (trong đó phần tử ràng buộc dữ liệu tồn tại) được áp dụng. Điều này tương tự với việc đặt TemplateBindingExtension và chỉ được áp dụng nếu Binding nằm trong một mẫu.
Tự Tham chiếu đến phần tử mà bạn đang đặt ràng buộc và cho phép bạn liên kết một thuộc tính của phần tử đó với thuộc tính khác trên cùng một phần tử.
FindAncestor Đề cập đến tổ tiên trong chuỗi cha của phần tử ràng buộc dữ liệu. Bạn có thể sử dụng điều này để liên kết với tổ tiên của một loại cụ thể hoặc các lớp con của nó. Đây là chế độ bạn sử dụng nếu bạn muốn chỉ định AneoporType và / hoặc AneoporLevel.
Đây là một lời giải thích trực quan hơn trong bối cảnh của kiến trúc MVVM:
{Binding Message}
(đơn giản hơn một chút ...)
Path=DataContext.Message
để có được ràng buộc để làm việc. Điều này có ý nghĩa, cho rằng bạn có thể thực hiện các ràng buộc tương đối với chiều rộng / chiều cao / vv. của một điều khiển.
Bechir Bejaoui trưng bày các trường hợp sử dụng của RelativeSource trong WPF trong bài viết của mình tại đây :
RelativeSource là một phần mở rộng đánh dấu được sử dụng trong các trường hợp ràng buộc cụ thể khi chúng ta cố gắng liên kết một thuộc tính của một đối tượng nhất định với một thuộc tính khác của chính đối tượng đó, khi chúng ta cố gắng liên kết một thuộc tính của đối tượng với một đối tượng cha mẹ tương đối của nó, khi ràng buộc một giá trị thuộc tính phụ thuộc vào một phần XAML trong trường hợp phát triển điều khiển tùy chỉnh và cuối cùng trong trường hợp sử dụng vi sai của một chuỗi dữ liệu bị ràng buộc. Tất cả các tình huống đó được thể hiện dưới dạng chế độ nguồn tương đối. Tôi sẽ phơi bày tất cả các trường hợp một.
- Chế độ tự:
Hãy tưởng tượng trường hợp này, một hình chữ nhật mà chúng ta muốn rằng chiều cao của nó luôn bằng chiều rộng của nó, một hình vuông hãy nói. Chúng ta có thể làm điều này bằng cách sử dụng tên thành phần
<Rectangle Fill="Red" Name="rectangle" Height="100" Stroke="Black" Canvas.Top="100" Canvas.Left="100" Width="{Binding ElementName=rectangle, Path=Height}"/>
Nhưng trong trường hợp trên, chúng tôi có nghĩa vụ phải chỉ ra tên của đối tượng ràng buộc, cụ thể là hình chữ nhật. Chúng ta có thể đạt được cùng một mục đích khác nhau bằng cách sử dụng RelativeSource
<Rectangle Fill="Red" Height="100" Stroke="Black" Width="{Binding RelativeSource={RelativeSource Self}, Path=Height}"/>
Trong trường hợp đó, chúng tôi không bắt buộc phải đề cập đến tên của đối tượng ràng buộc và Chiều rộng sẽ luôn bằng với Chiều cao mỗi khi thay đổi chiều cao.
Nếu bạn muốn tham số Width là một nửa chiều cao thì bạn có thể làm điều này bằng cách thêm một trình chuyển đổi vào phần mở rộng đánh dấu Binding. Bây giờ hãy tưởng tượng một trường hợp khác:
<TextBlock Width="{Binding RelativeSource={RelativeSource Self}, Path=Parent.ActualWidth}"/>
Trường hợp trên được sử dụng để buộc một thuộc tính nhất định của một phần tử đã cho với một trong các phần tử cha mẹ trực tiếp của nó vì phần tử này giữ một thuộc tính được gọi là Parent. Điều này dẫn chúng ta đến một chế độ nguồn tương đối khác, đó là chế độ FindAncestor.
- Chế độ FindAncestor
Trong trường hợp này, một tài sản của một yếu tố nhất định sẽ được gắn với một trong những cha mẹ của nó, Of Corse. Sự khác biệt chính với trường hợp trên là ở chỗ, tùy thuộc vào bạn để xác định loại tổ tiên và thứ hạng tổ tiên trong hệ thống phân cấp để buộc tài sản. Nhân tiện, hãy thử chơi với đoạn XAML này
<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>
Tình huống trên là của hai phần tử TextBlock được nhúng trong một loạt các phần tử viền và phần tử canvas đại diện cho cha mẹ phân cấp của chúng. TextBlock thứ hai sẽ hiển thị tên của cha mẹ đã cho ở mức nguồn tương đối.
Vì vậy, hãy thử thay đổi AneoporLevel = 2 thành AneoporLevel = 1 và xem điều gì sẽ xảy ra. Sau đó thử thay đổi loại của tổ tiên từ AneoporType = Border thành AneoporType = Canvas và xem điều gì sẽ xảy ra.
Văn bản được hiển thị sẽ thay đổi theo loại và cấp độ Tổ tiên. Vậy thì điều gì sẽ xảy ra nếu cấp độ tổ tiên không phù hợp với loại tổ tiên? Đây là một câu hỏi hay, tôi biết rằng bạn sắp hỏi nó. Phản hồi không có ngoại lệ sẽ được đưa ra và các thông báo sẽ được hiển thị ở cấp độ TextBlock.
- TemplatedParent
Chế độ này cho phép buộc một thuộc tính ControlTemplate đã cho vào một thuộc tính của điều khiển mà ControlTemplate được áp dụng. Để hiểu rõ vấn đề ở đây là một ví dụ dưới đây
<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>
Nếu tôi muốn áp dụng các thuộc tính của một điều khiển nhất định cho mẫu điều khiển của nó thì tôi có thể sử dụng chế độ TemplatedParent. Ngoài ra còn có một tiện ích tương tự với tiện ích mở rộng đánh dấu này, đó là TemplateBinding, một loại tay ngắn của cái đầu tiên, nhưng TemplateBinding được đánh giá tại thời điểm biên dịch theo độ tương phản của TemplatedParent được đánh giá ngay sau lần chạy đầu tiên. Như bạn có thể nhận xét trong hình dưới đây, nền và nội dung được áp dụng từ bên trong nút cho mẫu điều khiển.
ListView
. Phụ huynh có thêm 2 ListView
cấp độ bên dưới nó. Điều này đã giúp tôi ngăn chặn thông qua dữ liệu vào mỗi vm tiếp theo của mỗi ListView
'sDataTemplate
Trong WPF RelativeSource
ràng buộc hiển thị ba properties
để thiết lập:
1. Chế độ: Đây là một enum
giá trị có thể có bốn giá trị:
a. BeforeData (
value=0
): Nó gán giá trị trước đó cho giá trịproperty
ràng buộcb. TemplatedParent (
value=1
): Điều này được sử dụng khi xác địnhtemplates
bất kỳ điều khiển nào và muốn liên kết với một giá trị / Thuộc tính củacontrol
.Ví dụ
ControlTemplate
: xác định :
<ControlTemplate>
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</ControlTemplate>
c. Self (
value=2
): Khi chúng ta muốn ràng buộc từ mộtself
hoặc mộtproperty
cái tôi.Ví dụ: Gửi trạng thái được kiểm tra
checkbox
nhưCommandParameter
trong khi cài đặtCommand
bậtCheckBox
<CheckBox ...... CommandParameter="{Binding RelativeSource={RelativeSource Self},Path=IsChecked}" />
d. FindAncestor (
value=3
): Khi muốn liên kết từ cha mẹcontrol
trongVisual Tree
.Ví dụ: Ràng buộc a
checkbox
trongrecords
nếu agrid
, nếuheader
checkbox
được chọn
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid}}, Path=DataContext.IsHeaderChecked, Mode=TwoWay}" />
2. AneoporType: khi chế độ được FindAncestor
xác định loại tổ tiên
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid}}
3. AneoporLevel: khi chế độ làFindAncestor
cấp độ của tổ tiên (nếu có hai loại cha mẹ giống nhauvisual tree
)
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid, AncestorLevel=1}}
Trên đây là tất cả các trường hợp sử dụng cho
RelativeSource binding
.
Thật đáng lưu ý rằng đối với những người vấp phải suy nghĩ này của Silverlight:
Silverlight chỉ cung cấp một tập hợp con giảm, trong số các lệnh này
Tôi đã tạo một thư viện để đơn giản hóa cú pháp ràng buộc của WPF bao gồm việc sử dụng RelativeSource dễ dàng hơn. Dưới đây là một số ví dụ. Trước:
{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}
Sau:
{BindTo PathToProperty}
{BindTo Ancestor.typeOfAncestor.PathToProperty}
{BindTo Template.PathToProperty}
{BindTo #MyTextBox.Text}
Dưới đây là một ví dụ về cách liên kết phương thức được đơn giản hóa. Trước:
// 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}
Sau:
// C# code
private void SaveObject() {
// do something
}
// XAML
{BindTo SaveObject()}
Bạn có thể tìm thấy thư viện ở đây: http://www.simplygoodcode.com/2012/08/simpler-wpf-binding.html
Lưu ý trong ví dụ 'TRƯỚC' mà tôi sử dụng để liên kết phương thức mà mã đã được tối ưu hóa bằng cách sử dụng RelayCommand
cái cuối cùng tôi đã kiểm tra không phải là một phần nguyên gốc của WPF. Nếu không có điều đó, ví dụ 'TRƯỚC' sẽ còn dài hơn nữa.
Một số bit và phần hữu ích:
Đây là cách để làm điều đó chủ yếu trong mã:
Binding b = new Binding();
b.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, this.GetType(), 1);
b.Path = new PropertyPath("MyElementThatNeedsBinding");
MyLabel.SetBinding(ContentProperty, b);
Tôi đã sao chép phần lớn từ Binding Relative Source trong mã Phía sau .
Ngoài ra, trang MSDN khá tốt theo như ví dụ: Lớp RelativeSource
Tôi vừa đăng một giải pháp khác để truy cập DataContext của một phần tử cha trong Silverlight phù hợp với tôi. Nó sử dụng Binding ElementName
.
Tôi đã không đọc mọi câu trả lời, nhưng tôi chỉ muốn thêm thông tin này trong trường hợp ràng buộc lệnh nguồn tương đối của một nút.
Khi bạn sử dụng một nguồn tương đối với Mode=FindAncestor
, ràng buộc phải như sau:
Command="{Binding Path=DataContext.CommandProperty, RelativeSource={...}}"
Nếu bạn không thêm DataContext vào đường dẫn của mình, tại thời điểm thực thi, nó không thể truy xuất thuộc tính.
Đây là một ví dụ về việc sử dụng mẫu này hoạt động với tôi trên các bảng dữ liệu trống.
<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>
Nếu một phần tử không phải là một phần của cây trực quan, thì RelativeSource sẽ không bao giờ hoạt động.
Trong trường hợp này, bạn cần thử một kỹ thuật khác, được tiên phong bởi Thomas Levesque.
Anh ta có giải pháp trên blog của mình trong [WPF] Cách liên kết với dữ liệu khi DataContext không được kế thừa . Và nó hoạt động hoàn toàn rực rỡ!
Trong trường hợp không chắc là blog của anh ta bị sập, Phụ lục A chứa bản sao phản chiếu bài viết của anh ta .
Xin vui lòng không bình luận ở đây, xin vui lòng bình luận trực tiếp trên bài viết blog của mình .
Thuộc tính DataContext trong WPF cực kỳ tiện dụng, bởi vì nó được tự động kế thừa bởi tất cả các phần tử con mà bạn gán nó; do đó bạn không cần phải đặt lại trên mỗi thành phần bạn muốn liên kết. Tuy nhiên, trong một số trường hợp, DataContext không thể truy cập được: nó xảy ra đối với các phần tử không phải là một phần của cây trực quan hoặc logic. Sau đó có thể rất khó khăn để ràng buộc một tài sản trên các yếu tố đó.
Hãy minh họa bằng một ví dụ đơn giản: chúng tôi muốn hiển thị danh sách các sản phẩm trong DataGrid. Trong lưới, chúng tôi muốn có thể hiển thị hoặc ẩn cột Giá, dựa trên giá trị của thuộc tính Showprice được hiển thị bởi ViewModel. Cách tiếp cận rõ ràng là liên kết Khả năng hiển thị của cột với thuộc tính Showprice:
<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
Visibility="{Binding ShowPrice,
Converter={StaticResource visibilityConverter}}"/>
Thật không may, việc thay đổi giá trị của Showprice không có hiệu lực và cột luôn hiển thị, vì sao? Nếu chúng ta nhìn vào cửa sổ đầu ra trong Visual Studio, chúng ta sẽ thấy dòng sau:
System.Windows.Data Lỗi: 2: Không thể tìm thấy quản lý FrameworkEuity hoặc FrameworkContentE bổ sung cho phần tử đích. BindingExpression: Đường dẫn = Showprice; DataItem = null; phần tử đích là 'DataGridTextColumn' (HashCode = 32685253); thuộc tính mục tiêu là 'Tầm nhìn' (loại 'Tầm nhìn')
Thông điệp này khá khó hiểu, nhưng ý nghĩa thực sự khá đơn giản: WPF không biết sử dụng FrameworkEuity nào để lấy DataContext, vì cột không thuộc về cây trực quan hoặc logic của DataGrid.
Ví dụ, chúng ta có thể cố gắng điều chỉnh liên kết để có được kết quả mong muốn bằng cách đặt RelativeSource thành DataGrid:
<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
Visibility="{Binding DataContext.ShowPrice,
Converter={StaticResource visibilityConverter},
RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}}"/>
Hoặc chúng ta có thể thêm một CheckBox bị ràng buộc vào Showprice và cố gắng liên kết mức độ hiển thị của cột với thuộc tính IsChecked bằng cách chỉ định tên thành phần:
<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
Visibility="{Binding IsChecked,
Converter={StaticResource visibilityConverter},
ElementName=chkShowPrice}"/>
Nhưng dường như không có cách giải quyết nào có hiệu quả, chúng tôi luôn nhận được kết quả tương tự
Tại thời điểm này, dường như cách tiếp cận khả thi duy nhất là thay đổi mức độ hiển thị của cột theo mã phía sau, điều mà chúng ta thường muốn tránh khi sử dụng mẫu MVVM, Nhưng tôi sẽ không từ bỏ sớm, ít nhất là không trong khi có những lựa chọn khác để xem xét
Giải pháp cho vấn đề của chúng tôi thực sự khá đơn giản và tận dụng lớp Freezable. Mục đích chính của lớp này là xác định các đối tượng có trạng thái có thể sửa đổi và chỉ đọc, nhưng tính năng thú vị trong trường hợp của chúng ta là các đối tượng Freezable có thể kế thừa DataContext ngay cả khi chúng không ở trong cây trực quan hoặc logic. Tôi không biết cơ chế chính xác cho phép thực hiện hành vi này, nhưng chúng tôi sẽ tận dụng lợi thế của nó để thực hiện công việc ràng buộc của chúng tôi
Ý tưởng là tạo ra một lớp (tôi gọi nó là BindingProxy vì những lý do sẽ sớm trở nên rõ ràng) kế thừa Freezable và tuyên bố một thuộc tính phụ thuộc dữ liệu:
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));
}
Sau đó chúng ta có thể khai báo một thể hiện của lớp này trong các tài nguyên của DataGrid và liên kết thuộc tính Dữ liệu với DataContext hiện tại:
<DataGrid.Resources>
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
</DataGrid.Resources>
Bước cuối cùng là chỉ định đối tượng BindingProxy này (có thể truy cập dễ dàng bằng StaticResource) làm Nguồn cho liên kết:
<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
Visibility="{Binding Data.ShowPrice,
Converter={StaticResource visibilityConverter},
Source={StaticResource proxy}}"/>
Lưu ý rằng đường dẫn liên kết đã được thêm tiền tố với dữ liệu dữ liệu, vì đường dẫn này hiện có liên quan đến đối tượng BindingProxy.
Liên kết bây giờ hoạt động chính xác và cột được hiển thị hoặc ẩn chính xác dựa trên thuộc tính Showprice.