it-swarm-vi.tech

Làm cách nào để sử dụng các liên kết WPF với RelativeSource?

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

550
David Schmitt

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ó đượ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 liên kết 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}
735
Abe Heidebrecht
Binding RelativeSource={
    RelativeSource Mode=FindAncestor, AncestorType={x:Type ItemType}
}
...

Thuộc tính mặc định của RelativeSource là thuộc tính Mode. 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ự Đề cập đế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.

126
Drew Noakes

Đâ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:

enter image description here

120
Jeffrey Knight

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.

40
lasitha edirisooriya

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 anh ấy ở đâ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.

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

  1. 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à 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 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 là 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.

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

34
Cornel Marian

Trong WPF RelativeSource ràng buộc hiển thị ba properties để đặt:

1. Chế độ: Đây là enum có thể có bốn giá trị:

a. BeforeData (value=0): Nó gán giá trị trước đó của property cho ràng buộc

b. TemplatedParent (value=1): Cái này được sử dụng khi xác định templates của 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ủa control.

Ví dụ: xác định ControlTemplate:

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

c. Tự (value=2): Khi chúng tôi muốn liên kết từ một self hoặc property của bản thân.

Ví dụ: Gửi trạng thái đã kiểm tra của checkbox dưới dạng CommandParameter trong khi đặt Command trên CheckBox

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

d. FindAncestor (value=3): Khi muốn liên kết từ cha mẹ control trong Visual Tree.

Ví dụ: Liên kết một checkbox trong records nếu a grid, nếu headercheckbox được chọn

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

2. AneoporType: khi chế độ là FindAncestor sau đó xác định loại tổ tiên

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

3. Tổ tiên: khi chế độ là FindAncestor thì cấp độ của tổ tiên (nếu có hai loại cha mẹ giống nhau trong visual 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.

Đây là liên kết tham chiế .

23
Kylo Ren

Đừng quên TemplatedParent:

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

hoặc là

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

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 mà tôi đã kiểm tra lần cuối không phải là một phầ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.

13
Luis Perez

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

13
Matthew Black

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ừ Liên kết nguồn tương đối trong mã phía sa.

Ngoài ra, trang MSDN khá tốt theo các ví dụ: Lớp tương đối

12
Nathan Cooper

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.

10
Juve

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.

9
Kevin VDF

Đâ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>
8
Edd

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 ấy bị sập, Phụ lục A chứa bản sao nhân bản của bài viết của anh ấy .

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 đăng trên blog của anh ấy .

Phụ lục A: Gương của bài đăng trên blog

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 thiết lập lại nó 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 một 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à ‘Hiển thị rõ ràng (loại’ Hiển thị trực tiếp)

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, bởi vì cột không có tên thuộc về cây trực quan hoặc logic của DataGrid.

Ví dụ, chúng ta có thể cố gắng tinh chỉnh liên kết để có được kết quả mong muốn bằng cách đặt RelativeSource thành chí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 sẽ 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.

4
Contango