Jak korzystać z powiązań WPF z RelativeSource?


Odpowiedzi:


783

Jeśli chcesz powiązać z inną właściwością obiektu:

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

Jeśli chcesz uzyskać właściwość od przodka:

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

Jeśli chcesz uzyskać właściwość nadrzędnego szablonu (aby można było tworzyć wiązania 2-kierunkowe w ControlTemplate)

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

lub krótszy (działa to tylko w przypadku powiązań OneWay):

{TemplateBinding Path=PathToProperty}

15
W przypadku tego „{Binding Path = PathToProperty, RelativeSource = {RelativeSource AncestorType = {x: Type typeOfAncestor}}}” wygląda na to, że musi mieć „Mode = FindAncestor” przed „AncestorType”
EdwardM

1
Do jakiej technologii? W WPF wynika to z określenia AncestorType.
Abe Heidebrecht

2
Zgadzam się z @EdwardM. Kiedy pominąć FindAncestor, przed AncestorType, pojawia się następujący błąd: „RelativeSource nie jest w trybie FindAncestor”. (W VS2013, wersja wspólnotowa)
kmote

1
@kmote, działało to dla mnie od .net 3.0, a ja po raz kolejny zweryfikowałem, że działa w ten sposób w kaxaml ... Znowu, jakiej technologii używasz? Procesor XAML jest inny dla WPF / Silverlight / UWP, więc możesz mieć różne wyniki dla różnych technologii. Wspomniałeś również o VS Community, więc może jest to ostrzeżenie IDE, ale działa w czasie wykonywania?
Abe Heidebrecht

6
Po prostu chciałem, aby zauważyć, że jeśli chcą wiązać się z nieruchomości w DataContext z RelativeSource następnie należy wyraźnie określić go: {Binding Path=DataContext.SomeProperty, RelativeSource=.... Dla mnie jako początkującego było to nieco nieoczekiwane, gdy próbowałem powiązać z obiektem DataContext rodzica w DataTemplate.
DrEsperanto,

133
Binding RelativeSource={
    RelativeSource Mode=FindAncestor, AncestorType={x:Type ItemType}
}
...

Domyślnym atrybutem RelativeSourcejest Modewłaściwość. Pełny zestaw prawidłowych wartości znajduje się tutaj ( z MSDN ):

  • PreviousData Umożliwia powiązanie poprzedniego elementu danych (nie formantu zawierającego element danych) z listy wyświetlanych elementów danych.

  • TemplatedParent Odnosi się do elementu, do którego stosuje się szablon (w którym istnieje element związany z danymi). Jest to podobne do ustawiania TemplateBindingExtension i ma zastosowanie tylko wtedy, gdy Powiązanie znajduje się w szablonie.

  • Self Odnosi się do elementu, na którym ustawia się wiązanie, i pozwala powiązać jedną właściwość tego elementu z inną właściwością tego samego elementu.

  • FindAncestor Odnosi się do przodka w łańcuchu macierzystym elementu związanego z danymi. Możesz użyć tego do powiązania z przodkiem określonego typu lub jego podklas. Jest to tryb, którego używasz, jeśli chcesz określić AncestorType i / lub AncestorLevel.


128

Oto bardziej wizualne wyjaśnienie w kontekście architektury MVVM:

wprowadź opis zdjęcia tutaj


19
przegapiłem coś? Jak możesz uznać tę prostą i przejrzystą grafikę? 1: pola po lewej stronie tak naprawdę nie są powiązane z tymi po prawej (dlaczego w pliku ViewModel znajduje się plik .cs?) 2: Na co wskazują te strzałki DataContext? 3: dlaczego właściwość Message nie znajduje się w ViewModel1? i co najważniejsze 5: Dlaczego potrzebujesz RelativeSource Binding, aby dostać się do DataContext okna, jeśli TextBlock ma już ten sam DataContext? Wyraźnie mi czegoś brakuje, więc albo jestem głupi, albo ta grafika nie jest tak prosta i przejrzysta, jak wszyscy myślą! Proszę, oświeć mnie
Markus Hütter,

2
@ MarkusHütter Schemat pokazuje grupie zagnieżdżone widoki i odpowiadające im modele ViewModels. Kontekst danych View1 to ViewModel1, ale chce się powiązać z właściwością BaseViewModel. Ponieważ BaseViewModel jest DataContext z BaseView (który jest oknem), może to zrobić poprzez znalezienie pierwszego kontenera nadrzędnego, którym jest Window i pobranie jego DataContext.
mcargille

6
@MatthewCargille wiem bardzo dobrze, co to miało znaczyć, że nie był mój punkt. Ale postaw się w sytuacji kogoś, kto nie zna dobrze XAML i MVVM, a zobaczysz, że nie jest to proste i jasne .
Markus Hütter

1
Nawiasem mówiąc, muszę się zgodzić z @ MarkusHütter, wiązanie po lewej stronie może być tak proste: {Binding Message}(nieco prostsze ...)
florien

@florien Nie sądzę, przynajmniej w moim przypadku użycia. Mam DataTemplate, który musi odwoływać się do DataContext MainWindow (moja klasa viewmodel), aby uzyskać listę opcji menu rozwijanego (ładowanego z bazy danych). DataTemplate jest powiązany z obiektem modelu, który jest również ładowany z bazy danych, ale ma dostęp tylko do wybranej opcji. Musiałem wyraźnie ustawić, Path=DataContext.Messageaby wiązanie zadziałało. Ma to sens, biorąc pod uwagę, że można tworzyć powiązania względne do szerokości / wysokości / itp. kontroli.
DrEsperanto,

47

Bechir Bejaoui ujawnia przypadki użycia RelativeSources w WPF w swoim artykule tutaj :

RelativeSource to rozszerzenie znaczników, które jest używane w szczególnych przypadkach wiązania, gdy próbujemy powiązać właściwość danego obiektu z inną właściwością samego obiektu, gdy próbujemy powiązać właściwość obiektu z innym jego względnym rodzicem, podczas wiązania wartości właściwości zależności z kawałkiem XAML w przypadku niestandardowego opracowania sterowania i wreszcie w przypadku użycia różnicy serii powiązanych danych. Wszystkie te sytuacje są wyrażone jako względne tryby źródła. Wszystkie te przypadki ujawnię jeden po drugim.

  1. Tryb własny:

Wyobraźmy sobie ten przypadek: prostokąt, który chcemy, aby jego wysokość była zawsze równa jego szerokości, powiedzmy kwadrat. Możemy to zrobić za pomocą nazwy elementu

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

Ale w powyższym przypadku jesteśmy zobowiązani podać nazwę wiążącego obiektu, a mianowicie prostokąta. Możemy osiągnąć ten sam cel inaczej, korzystając z RelativeSource

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

W takim przypadku nie jesteśmy zobowiązani do podania nazwy obiektu wiążącego, a szerokość będzie zawsze równa wysokości przy każdej zmianie wysokości.

Jeśli chcesz ustawić szerokość jako połowę wysokości, możesz to zrobić, dodając konwerter do rozszerzenia znaczników Binding. Wyobraźmy sobie teraz inny przypadek:

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

Powyższy przypadek służy do powiązania danej właściwości danego elementu z jedną z jego bezpośrednich nadrzędnych, ponieważ element ten zawiera właściwość o nazwie Nadrzędny. To prowadzi nas do innego względnego trybu źródła, którym jest FindAncestor.

  1. Mode FindAncestor

W takim przypadku właściwość danego elementu zostanie powiązana z jednym z jego rodziców, Of Corse. Główną różnicą w powyższym przypadku jest fakt, że to od Ciebie zależy określenie rodzaju przodka i rangi przodka w hierarchii, aby powiązać właściwość. Nawiasem mówiąc, spróbuj zagrać z tym kawałkiem XAML

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

Powyższa sytuacja dotyczy dwóch elementów TextBlock, które są osadzone w szeregu ramek i elementów canvas reprezentujących ich hierarchicznych rodziców. Drugi TextBlock wyświetli nazwę danego rodzica na względnym poziomie źródła.

Spróbuj więc zmienić AncestorLevel = 2 na AncestorLevel = 1 i zobacz, co się stanie. Następnie spróbuj zmienić typ przodka z AncestorType = Border na AncestorType = Canvas i zobacz, co się stanie.

Wyświetlany tekst zmieni się w zależności od typu i poziomu Przodka. Co się wtedy stanie, jeśli poziom przodka nie jest odpowiedni dla typu przodka? To dobre pytanie, wiem, że zaraz o to zapytasz. Odpowiedź jest taka, że ​​nie zostaną zgłoszone wyjątki, a na poziomie TextBlock zostaną wyświetlone informacje.

  1. TemplatedParent

Ten tryb umożliwia powiązanie danej właściwości ControlTemplate z właściwością formantu, do którego zastosowano ControlTemplate. Aby dobrze zrozumieć problem, oto przykład poniżej

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

Jeśli chcę zastosować właściwości danej kontrolki do jej szablonu kontrolnego, mogę użyć trybu TemplatedParent. Istnieje również podobne do tego rozszerzenia znaczników, które jest szablonowym wiązaniem, które jest rodzajem krótkiego układu pierwszego, ale wiązanie szablonowe jest oceniane w czasie kompilacji, w przeciwieństwie do szablonu TemplatedParent, który jest oceniany tuż po pierwszym uruchomieniu. Jak można zauważyć na poniższym rysunku, tło i treść są stosowane z przycisku do szablonu kontrolnego.


Bardzo fajne przykłady dla mnie, użyłem Find Ancestor do powiązania z poleceniem w kontekście danych rodzica ListView. Rodzic ma jeszcze 2 ListViewpoziomy poniżej. To pomogło mi dopuścić do przeniesienia danych do każdego kolejnego VM każdego ListView„sDataTemplate
Caleb W.

34

W RelativeSourcewiązaniu WPF ujawnia trzyproperties do ustawienia:

1. Tryb: To enummoże mieć cztery wartości:

za. PreviousData ( value=0): Przypisuje poprzednią wartość parametruproperty powiązanej

b. TemplatedParent ( value=1): Jest używany podczas definiowaniatemplates dowolnego formantu i chce powiązać wartość / właściwość obiektu control.

Na przykład zdefiniuj ControlTemplate:

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

do. Self ( value=2): Kiedy chcemy powiązać zself lub propertyself.

Na przykład: Wysłać sprawdził stan od checkboxjak CommandParameterpodczas ustawiania CommandnaCheckBox

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

re. FindAncestor ( value=3): Kiedy chcesz powiązać od rodzicacontrol w Visual Tree.

Na przykład: wiążą checkboxsię recordsczy grid, jeśli header checkboxjest zaznaczone

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

2. AncestorType: kiedy tryb FindAncestorokreśla następnie typ przodka

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

3. AncestorLevel: kiedy tryb jestFindAncestorwtedy na jakim poziomie przodka (jeśli są dwa takie same typy rodzicavisual tree)

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

Powyżej są wszystkie przypadki użycia dla RelativeSource binding.

Oto link referencyjny .


2
Niesamowite .. to zadziałało dla mnie: <DataGridCheckBoxColumn Header = "Paid" Width = "35" Binding = "{Binding RelativeSource = {RelativeSource Mode = FindAncestor, AncestorType = {x: Type Window}}, Path = DataContext.SelectedBuyer.IsPaid , Mode = OneWay} "/>, w którym próbowałem powiązać z właściwością selectedbuyer okna nadrzędnego. IsPaid
Michael K

21

Nie zapomnij TemplatedParent:

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

lub

{Binding RelativeSource={RelativeSource TemplatedParent}}


16

Utworzyłem bibliotekę, aby uprościć składnię powiązań WPF, w tym ułatwić korzystanie z RelativeSource. Oto kilka przykładów. Przed:

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

Po:

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

Oto przykład uproszczenia wiązania metod. Przed:

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

Po:

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

// XAML
{BindTo SaveObject()}

Bibliotekę znajdziesz tutaj: http://www.simplygoodcode.com/2012/08/simpler-wpf-binding.html

Zauważ, że w przykładzie „ZANIM”, którego używam do wiązania metody, kod został już zoptymalizowany przy użyciu tego, RelayCommandktóry ostatnio sprawdziłem, nie jest natywną częścią WPF. Bez tego przykład „PRZED” byłby jeszcze dłuższy.


2
Tego rodzaju ćwiczenia polegające na trzymaniu się za ręce pokazują słabość XAML; sposób zbyt skomplikowane.
dudeNumber4

16

Kilka przydatnych fragmentów:

Oto jak to zrobić, głównie w kodzie:

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

W dużej mierze skopiowałem to z Binding Relative Source w kodzie Behind .

Ponadto strona MSDN jest całkiem dobra pod względem przykładów: Klasa RelativeSource


5
Moja niejasna pamięć WPF jest taka, że ​​tworzenie powiązań w kodzie prawdopodobnie nie jest zwykle najlepszą rzeczą.
Nathan Cooper

12

Właśnie opublikowałem inne rozwiązanie dostępu do DataContext elementu nadrzędnego w Silverlight, które działa dla mnie. Używa Binding ElementName.


10

Nie przeczytałem każdej odpowiedzi, ale chcę tylko dodać tę informację w przypadku względnego powiązania przycisku polecenia źródłowego.

Gdy używasz źródła względnego z Mode=FindAncestor, powiązanie musi wyglądać następująco:

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

Jeśli nie dodasz DataContext do swojej ścieżki, w czasie wykonywania nie będzie można pobrać właściwości.


9

To jest przykład użycia tego wzorca, który działał dla mnie na pustych siatkach danych.

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

6

Jeśli element nie jest częścią drzewa wizualnego, RelativeSource nigdy nie będzie działać.

W takim przypadku musisz wypróbować inną technikę, której pionierem jest Thomas Levesque.

Ma rozwiązanie na swoim blogu pod [WPF] Jak powiązać z danymi, gdy DataContext nie jest dziedziczony . I działa absolutnie genialnie!

W mało prawdopodobnym przypadku, gdy jego blog jest wyłączony, załącznik A zawiera kopię lustrzaną jego artykułu .

Nie komentuj tutaj, skomentuj bezpośrednio na jego blogu .

Dodatek A: Odbicie posta na blogu

Właściwość DataContext w WPF jest niezwykle przydatna, ponieważ jest automatycznie dziedziczona przez wszystkie elementy podrzędne elementu, do którego ją przypisujesz; dlatego nie musisz ustawiać go ponownie dla każdego elementu, który chcesz powiązać. Jednak w niektórych przypadkach DataContext nie jest dostępny: dzieje się tak w przypadku elementów, które nie są częścią drzewa wizualnego lub logicznego. Powiązanie właściwości z tymi elementami może być bardzo trudne…

Zilustrujmy prostym przykładem: chcemy wyświetlić listę produktów w DataGrid. W siatce chcemy mieć możliwość pokazywania lub ukrywania kolumny Cena na podstawie wartości właściwości ShowPrice ujawnionej przez ViewModel. Oczywistym podejściem jest powiązanie Widoczności kolumny z właściwością ShowPrice:

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

Niestety zmiana wartości ShowPrice nie ma wpływu, a kolumna jest zawsze widoczna… dlaczego? Jeśli spojrzymy na okno Output w Visual Studio, zauważymy następujący wiersz:

System.Windows.Data Błąd: 2: Nie można znaleźć rządzących FrameworkElement lub FrameworkContentElement dla elementu docelowego. BindingExpression: Path = ShowPrice; DataItem = null; elementem docelowym jest „DataGridTextColumn” (HashCode = 32685253); właściwość docelowa to „Widoczność” (wpisz „Widoczność”)

Wiadomość jest raczej tajemnicza, ale jej znaczenie jest dość proste: WPF nie wie, którego FrameworkElement użyć do uzyskania DataContext, ponieważ kolumna nie należy do wizualnego ani logicznego drzewa DataGrid.

Możemy spróbować poprawić powiązanie, aby uzyskać pożądany wynik, na przykład ustawiając RelativeSource na samą siatkę danych:

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

Lub możemy dodać CheckBox związany z ShowPrice i spróbować powiązać widoczność kolumny z właściwością IsChecked, podając nazwę elementu:

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

Ale żadne z tych obejść nie wydaje się działać, zawsze uzyskujemy ten sam wynik…

W tym momencie wydaje się, że jedynym realnym podejściem byłaby zmiana widoczności kolumny z tyłu kodu, czego zwykle wolimy unikać, gdy używamy wzorca MVVM… Ale nie zamierzam się tak szybko poddawać, a przynajmniej nie podczas gdy istnieją inne opcje do rozważenia 😉

Rozwiązanie naszego problemu jest w rzeczywistości dość proste i wykorzystuje klasę Freezable. Głównym celem tej klasy jest zdefiniowanie obiektów, które mają stan modyfikowalny i tylko do odczytu, ale interesującą cechą w naszym przypadku jest to, że obiekty Freezable mogą dziedziczyć DataContext, nawet jeśli nie znajdują się w drzewie wizualnym lub logicznym. Nie znam dokładnego mechanizmu, który umożliwia takie zachowanie, ale wykorzystamy go, aby nasze wiążące działanie…

Chodzi o to, aby utworzyć klasę (nazwałem ją BindingProxy z powodów, które powinny wkrótce stać się oczywiste), która dziedziczy Freezable i deklaruje właściwość zależności danych:

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

Następnie możemy zadeklarować wystąpienie tej klasy w zasobach DataGrid i powiązać właściwość Data z bieżącym DataContext:

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

Ostatnim krokiem jest określenie tego obiektu BindingProxy (łatwo dostępnego w StaticResource) jako źródła powiązania:

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

Zauważ, że ścieżka wiązania została poprzedzona przedrostkiem „Data”, ponieważ ścieżka jest teraz względna do obiektu BindingProxy.

Powiązanie działa teraz poprawnie, a kolumna jest poprawnie wyświetlana lub ukryta na podstawie właściwości ShowPrice.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.