it-swarm-eu.dev

Jak mohu používat vazby WPF s RelativeSource?

Jak mohu použít RelativeSource s vazbami WPF a jaké jsou různé případy použití?

550
David Schmitt

Pokud se chcete vázat k jiné vlastnosti objektu:

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

Pokud chcete získat nemovitost na předka:

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

Pokud chcete získat vlastnost u nadřazeného rodiče (takže můžete udělat 2cestné vazby v ControlTemplate)

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

nebo kratší (to funguje pouze u vazeb OneWay):

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

Výchozí atribut RelativeSource je vlastnost Mode. Zde je uvedena kompletní sada platných hodnot ( z MSDN ):

  • PreviousData - Umožňuje vám svázat předchozí datovou položku (nikoli ovládací prvek, který obsahuje datovou položku) v seznamu zobrazovaných datových položek.

  • TemplatedParent Odkazuje na prvek, na který se použije šablona (ve které existuje prvek vázaný na data). Je to podobné jako při nastavení TemplateBindingExtension a je použitelné pouze v případě, že je vazba v rámci šablony.

  • Self Odkazuje na prvek, na kterém nastavujete vazbu, a umožňuje vám svázat jednu vlastnost tohoto prvku s jinou vlastností stejného prvku.

  • FindAncestor Odkazuje na předka v nadřazeném řetězci prvku vázaného na data. Můžete to použít k navázání na předka určitého typu nebo jeho podtříd. Toto je režim, který chcete použít, pokud chcete určit AncestorType a/nebo AncestorLevel.

126
Drew Noakes

Zde je vizuální vysvětlení v kontextu architektury MVVM:

enter image description here

120
Jeffrey Knight

Představte si tento případ, obdélník, který chceme, aby jeho výška byla vždy stejná jako jeho šířka, řekněme čtverec. Můžeme to udělat pomocí názvu prvku

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

V tomto výše uvedeném případě jsme však povinni uvést název závazného objektu, konkrétně obdélník. Stejného účelu můžeme dosáhnout různě pomocí zdroje RelativeSource

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

V takovém případě nejsme povinni uvádět název vazebného objektu a Šířka bude vždy stejná jako výška při každé změně výšky.

Pokud chcete parametrem Šířka nastavit na polovinu výšky, můžete to provést přidáním převaděče k rozšíření Značení vazby. Představme si nyní další případ:

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

Výše uvedený případ se používá k navázání dané vlastnosti daného prvku na jeden z jeho přímých nadřazených, protože tento prvek drží vlastnost, která se nazývá Rodič. To nás vede k dalšímu relativnímu zdrojovému režimu, kterým je FindAncestor.

40

Bechir Bejaoui odhaluje případy použití RelativeSources ve WPF v jeho článek zde :

RelativeSource je rozšíření značení, které se používá v konkrétních vazebných případech, když se pokusíme svázat vlastnost daného objektu s jinou vlastností samotného objektu, když se pokusíme vázat vlastnost objektu k jinému z jeho relativních rodičů, při vázání hodnoty vlastnosti závislosti na kus XAML v případě vývoje vlastní kontroly a konečně v případě použití rozdílu řady vázaných dat. Všechny tyto situace jsou vyjádřeny jako režimy relativního zdroje. Odhalím všechny tyto případy jeden po druhém.

  1. Vlastní režim:

Představte si tento případ, obdélník, který chceme, aby jeho výška byla vždy stejná jako jeho šířka, řekněme čtverec. Můžeme to udělat pomocí názvu prvku

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

V tomto výše uvedeném případě jsme však povinni uvést název závazného objektu, konkrétně obdélník. Stejného účelu můžeme dosáhnout různě pomocí zdroje RelativeSource

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

V takovém případě nejsme povinni uvádět název vazebného objektu a Šířka bude vždy stejná jako výška při každé změně výšky.

Pokud chcete parametrem Šířka nastavit na polovinu výšky, můžete to provést přidáním převaděče k rozšíření Značení vazby. Představme si nyní další případ:

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

Výše uvedený případ se používá k navázání dané vlastnosti daného prvku na jeden z jeho přímých nadřazených, protože tento prvek drží vlastnost, která se nazývá Rodič. To nás vede k dalšímu relativnímu zdrojovému režimu, kterým je FindAncestor.

  1. Režim FindAncestor

V tomto případě bude majetek daného prvku vázán na jednoho z jeho rodičů, Of Corse. Hlavní rozdíl oproti výše uvedenému případu spočívá v tom, že je na vás, abyste určili typ předchůdce a pořadí předků v hierarchii svázali vlastnost. Mimochodem, zkuste si hrát s tímto kouskem 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>

Výše uvedená situace je ze dvou prvků TextBlock, které jsou vloženy do řady hranic, a prvků plátna, které představují jejich hierarchické rodiče. Druhý blok TextBlock zobrazí jméno dané nadřízené na relativní úrovni zdroje.

Zkuste tedy změnit AncestorLevel = 2 na AncestorLevel = 1 a uvidíte, co se stane. Pak se pokuste změnit typ předka z AncestorType = Border na AncestorType = Canvas a podívejte se, co se stane.

Zobrazený text se změní podle typu a úrovně předka. Co se stane, když úroveň předků není vhodná pro typ předků? To je dobrá otázka, vím, že se jí chcete zeptat. Odpověď je, že nebudou vyvolány žádné výjimky a na úrovni TextBlock se zobrazí nothings.

  1. TemplatedParent

Tento režim umožňuje svázat danou vlastnost ControlTemplate s vlastností ovládacího prvku, na který se ControlTemplate použije. Zde je příklad níže

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

Pokud chci aplikovat vlastnosti daného ovládacího prvku na jeho ovládací šablonu, pak mohu použít režim TemplatedParent. Existuje také podobné rozšíření jako toto rozšíření značení, kterým je TemplateBinding, což je druh krátké ruky prvního, ale TemplateBinding je vyhodnocen v době kompilace na rozdíl od TemplatedParent, který je vyhodnocen těsně po prvním běhu. Jak můžete poznamenat na níže uvedeném obrázku, pozadí a obsah se aplikují z tlačítka na ovládací šablonu.

34
Cornel Marian

V WPF RelativeSource vazba vystavuje tři properties k nastavení:

1. Režim: Toto je enum, které může mít čtyři hodnoty:

a. PreviousData (value=0): Přiřadí předchozí hodnotu property vázané.

b. TemplatedParent (value=1): Toto se používá při definování templates libovolného ovládacího prvku a chcete se vázat k hodnotě/vlastnosti control.

Například definuje ControlTemplate:

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

c. Self (value=2): Když se chceme vázat z self nebo property self.

Například: Odeslat kontrolovaný stav checkbox jako CommandParameter při nastavení Command na CheckBox

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

d. FindAncestor (value=3): Když se chcete vázat od nadřazeného control v Visual Tree.

Například: Svázat checkbox v records pokud je grid, pokud je headercheckbox

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

2. AncestorType: , když je režim FindAncestor, pak definujte, jaký typ předků

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

3. AncestorLevel: , když je režim FindAncestor, jaká úroveň předků (pokud existují dva stejné typy nadřazených v visual tree)

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

Nahoře jsou všechny případy použití pro RelativeSource binding.

Zde je odkaz .

23
Kylo Ren

Nezapomeňte na TemplatedParent:

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

nebo

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

Vytvořil jsem knihovnu pro zjednodušení vazebné syntaxe WPF včetně usnadnění používání RelativeSource. Zde jsou nějaké příklady. Před:

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

Zde je příklad zjednodušení vazby metod. Před:

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

Knihovnu najdete zde: http://www.simplygoodcode.com/2012/08/simpler-wpf-binding.html

Všimněte si v příkladu „PŘED“, který používám pro vázání metod, že kód byl již optimalizován pomocí RelayCommand, které jsem naposledy zkontroloval, není nativní součástí WPF. Bez toho by byl příklad „PŘEDCHOZÍ“ ještě delší.

13
Luis Perez

Stojí za zmínku, že pro ty, kteří klopýtají přes toto myšlení Silverlight:

Silverlight nabízí pouze omezenou podmnožinu těchto příkazů

13
Matthew Black

Některé užitečné kousky a kousky:

Zde je návod, jak to udělat většinou v kódu:

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

Z velké části jsem to zkopíroval Vazba relativního zdroje v kódu za.

Také stránka MSDN je velmi dobrá, pokud jde o příklady: RelativeSource Class

12
Nathan Cooper

Právě jsem zveřejnil další řešení pro přístup k DataContext nadřazeného prvku v Silverlight, který pracuje pro mě. Používá Binding ElementName.

10
Juve

Nečetl jsem každou odpověď, ale chci jen přidat tuto informaci v případě vazby tlačítka na relativní zdrojový příkaz.

Při použití relativního zdroje s Mode=FindAncestor musí být vazba podobná:

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

Pokud do cesty nepřidáte DataContext, nemůže v době spuštění tuto vlastnost načíst.

9
Kevin VDF

Toto je příklad použití tohoto vzoru, který pro mě pracoval na prázdných datových mřížkách.

<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

Pokud prvek není součástí vizuálního stromu, nebude RelativeSource nikdy fungovat.

V tomto případě musíte vyzkoušet jinou techniku, kterou propagoval Thomas Levesque.

Má řešení na svém blogu pod [WPF] Jak se vázat k datům, když DataContext není zděděn . A funguje to naprosto skvěle!

V nepravděpodobném případě, že by jeho blog skončil, obsahuje příloha A zrcadlovou kopii jeho článek .

Prosím, zde nekomentujte, prosím komentujte přímo na jeho blog .

Dodatek A: Zrcadlení příspěvku na blogu

Vlastnost DataContext ve WPF je velmi užitečná, protože je automaticky zděděna všemi podřízenými elementy, ke kterým ji přiřadíte; proto jej nemusíte znovu nastavovat na každý prvek, který chcete svázat. V některých případech však DataContext není přístupný: dochází k prvkům, které nejsou součástí vizuálního nebo logického stromu. Může být velmi obtížné vázat na tyto prvky vlastnost…

Ukážeme si jednoduchý příklad: chceme zobrazit seznam produktů v DataGrid. V mřížce chceme mít možnost zobrazit nebo skrýt sloupec Cena na základě hodnoty vlastnosti ShowPrice vystavené ViewModel. Zřejmým přístupem je navázání viditelnosti sloupce na vlastnost ShowPrice:

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

Bohužel změna hodnoty ShowPrice nemá žádný účinek a sloupec je vždy viditelný ... proč? Podíváme-li se na okno Výstup v aplikaci Visual Studio, všimneme si následujícího řádku:

Chyba System.Windows.Data: 2: Nelze najít řídící prvek FrameworkElement nebo FrameworkContentElement pro cílový prvek. BindingExpression: Path = ShowPrice; DataItem = null; cílovým prvkem je „DataGridTextColumn“ (HashCode = 32685253); cílová vlastnost je „Viditelnost“ (typ „Viditelnost“)

Zpráva je poněkud kryptická, ale význam je ve skutečnosti celkem jednoduchý: WPF neví, který FrameworkElement použít k získání DataContext, protože sloupec nepatří do vizuálního nebo logického stromu DataGrid.

Můžeme se pokusit vyladit vazbu, abychom dosáhli požadovaného výsledku, například nastavením RelativeSource na samotný DataGrid:

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

Nebo můžeme přidat CheckBox vázaný na ShowPrice a pokusit se navázat viditelnost sloupce na vlastnost IsChecked zadáním názvu prvku:

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

Zdá se však, že žádné z těchto řešení nefunguje, vždycky získáme stejný výsledek ...

V tomto okamžiku se zdá, že jediným životaschopným přístupem by bylo změnit viditelnost sloupce v kódovém pozadí, kterému se obvykle při použití vzoru MVVM obvykle vyhýbáme ... Ale brzy se nevzdám tak brzy, alespoň ne zatímco existují i ​​jiné možnosti, aby zvážila ????

Řešení našeho problému je ve skutečnosti celkem jednoduché a využívá výhod třídy Freezable. Primárním účelem této třídy je definovat objekty, které mají stav modifikovatelný a jen pro čtení, ale v našem případě je zajímavé, že zmrazitelné objekty mohou zdědit DataContext, i když nejsou ve vizuálním nebo logickém stromu. Nevím přesný mechanismus, který toto chování umožňuje, ale my ho využijeme k tomu, abychom naši závaznou práci ...

Záměrem je vytvořit třídu (nazval jsem ji BindingProxy z důvodů, které by se měly objevit velmi brzy), která zdědí Freezable a deklaruje vlastnost závislosti dat:

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

Potom můžeme deklarovat instanci této třídy ve zdrojích DataGrid a vázat vlastnost Data na aktuální DataContext:

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

Posledním krokem je určení tohoto objektu BindingProxy (snadno přístupného pomocí StaticResource) jako zdroje vazby:

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

Všimněte si, že cesta vazby byla předponou „Data“, protože cesta je nyní relativní k objektu BindingProxy.

Vazba nyní funguje správně a sloupec je správně zobrazen nebo skryt na základě vlastnosti ShowPrice.

4
Contango