it-swarm.it

Come utilizzo i binding WPF con RelativeSource?

Come si usa RelativeSource con i binding WPF e quali sono i diversi use case?

550
David Schmitt

Se si desidera associare a un'altra proprietà sull'oggetto:

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

Se vuoi ottenere una proprietà su un antenato:

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

Se si desidera ottenere una proprietà sul genitore basato su modello (in modo che sia possibile eseguire binding in 2 modi in un oggetto ControlTemplate)

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

o, più breve (funziona solo per i collegamenti OneWay):

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

L'attributo predefinito di RelativeSource è la proprietà Mode. Qui viene fornito un set completo di valori validi ( da MSDN ):

  • PreviousData Consente di associare l'elemento di dati precedente (non quel controllo che contiene l'elemento di dati) nell'elenco di elementi di dati visualizzati.

  • TemplatedParent Si riferisce all'elemento a cui viene applicato il modello (in cui esiste l'elemento associato ai dati). Questo è simile all'impostazione di TemplateBindingExtension ed è applicabile solo se il Binding è all'interno di un modello.

  • Self Si riferisce all'elemento su cui si sta impostando l'associazione e consente di associare una proprietà di quell'elemento a un'altra proprietà sullo stesso elemento.

  • FindAncestor Si riferisce all'antenato nella catena padre dell'elemento con associazione a dati. Puoi usarlo per associare a un antenato di un tipo specifico o alle sue sottoclassi. Questa è la modalità che usi se vuoi specificare AncestorType e/o AncestorLevel.

126
Drew Noakes

Ecco una spiegazione più visiva nel contesto di un'architettura MVVM:

enter image description here

120
Jeffrey Knight

Immagina questo caso, un rettangolo che vogliamo che la sua altezza sia sempre uguale alla sua larghezza, un quadrato diciamo. Possiamo farlo usando il nome dell'elemento

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

Ma nel caso di cui sopra siamo obbligati a indicare il nome dell'oggetto vincolante, ovvero il rettangolo. Possiamo raggiungere lo stesso scopo in modo diverso utilizzando RelativeSource

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

In questo caso non siamo obbligati a menzionare il nome dell'oggetto vincolante e la larghezza sarà sempre uguale all'altezza ogni volta che viene modificata l'altezza.

Se vuoi impostare il parametro Larghezza come metà dell'altezza, puoi farlo aggiungendo un convertitore all'estensione del markup Binding. Immaginiamo un altro caso ora:

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

Il caso precedente viene utilizzato per legare una determinata proprietà di un dato elemento a uno dei suoi parentri diretti in quanto questo elemento contiene una proprietà che si chiama Parent. Questo ci porta ad un'altra modalità sorgente relativa che è quella di FindAncestor.

40

Bechir Bejaoui espone i casi d'uso delle RelativeSources in WPF in il suo articolo qui :

RelativeSource è un'estensione di markup che viene utilizzata in particolari casi di binding quando si tenta di associare una proprietà di un determinato oggetto a un'altra proprietà dell'oggetto stesso, quando si tenta di associare una proprietà di un oggetto a un altro dei relativi genitori, quando si associa un valore di proprietà di dipendenza a un pezzo di XAML in caso di sviluppo di controllo personalizzato e infine in caso di utilizzo di un differenziale di una serie di dati associati. Tutte queste situazioni sono espresse come modalità di origine relativa. Esporrò tutti quei casi uno per uno.

  1. Modalità Self:

Immagina questo caso, un rettangolo che vogliamo che la sua altezza sia sempre uguale alla sua larghezza, un quadrato diciamo. Possiamo farlo usando il nome dell'elemento

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

Ma nel caso di cui sopra siamo obbligati a indicare il nome dell'oggetto vincolante, ovvero il rettangolo. Possiamo raggiungere lo stesso scopo in modo diverso utilizzando RelativeSource

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

In questo caso non siamo obbligati a menzionare il nome dell'oggetto vincolante e la larghezza sarà sempre uguale all'altezza ogni volta che viene modificata l'altezza.

Se vuoi impostare il parametro Larghezza come metà dell'altezza, puoi farlo aggiungendo un convertitore all'estensione del markup Binding. Immaginiamo un altro caso ora:

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

Il caso precedente viene utilizzato per legare una determinata proprietà di un dato elemento a uno dei suoi parentri diretti in quanto questo elemento contiene una proprietà che si chiama Parent. Questo ci porta ad un'altra modalità sorgente relativa che è quella di FindAncestor.

  1. Modalità TrovaAncestore

In questo caso, una proprietà di un determinato elemento sarà legata a uno dei suoi genitori, Of Corse. La principale differenza con il caso precedente è il fatto che spetta a te determinare il tipo di antenato e il grado di antenato nella gerarchia per legare la proprietà. A proposito, prova a giocare con questo pezzo di 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>

La situazione di cui sopra è composta da due elementi TextBlock che sono incorporati in una serie di bordi e elementi canvas che rappresentano i loro genitori gerarchici. Il secondo TextBlock mostrerà il nome del genitore dato al relativo livello di origine.

Quindi prova a cambiare AncestorLevel = 2 in AncestorLevel = 1 e guarda cosa succede. Quindi prova a cambiare il tipo di antenato da AncestorType = Border a AncestorType = Canvas e vedi cosa succede.

Il testo visualizzato cambierà in base al tipo e al livello di antenato. Allora cosa succede se il livello di antenato non è adatto al tipo di antenato? Questa è una buona domanda, so che stai per chiederlo. La risposta non fa eccezione e le nothings verranno visualizzate a livello di TextBlock.

  1. TemplatedParent

Questa modalità consente di associare una proprietà ControlTemplate a una proprietà del controllo a cui è applicato ControlTemplate. Per capire bene il problema qui è un esempio qui sotto

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

Se voglio applicare le proprietà di un determinato controllo al suo modello di controllo, allora posso usare la modalità TemplatedParent. C'è anche un simile a questa estensione di markup che è TemplateBinding che è una sorta di mano corta del primo, ma TemplateBinding viene valutato in fase di compilazione al contrasto del TemplatedParent che viene valutato subito dopo il primo tempo di esecuzione. Come si può notare nella figura a soffietto, lo sfondo e il contenuto vengono applicati dal pulsante al modello di controllo.

34
Cornel Marian

In WPF RelativeSource binding espone tre properties per impostare:

1. Modalità: Questo è un enum che potrebbe avere quattro valori:

a. PreviousData (value=0): Assegna il valore precedente di property a quello associato

b. TemplatedParent (value=1): Viene utilizzato quando si definisce templates di qualsiasi controllo e si desidera associare a un valore/Property del control.

Ad esempio, definisci ControlTemplate:

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

c. Self (value=2): Quando vogliamo eseguire il binding da un self o un property di self.

Ad esempio: Invia stato verificato di checkbox come CommandParameter durante l'impostazione di Command su CheckBox

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

d. FindAncestor (value=3): Quando si desidera eseguire il bind da un control genitore in Visual Tree.

Ad esempio: Associare un checkbox in records se grid, se headercheckbox è selezionato

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

2. AncestorType: quando mode è FindAncestor, quindi definisci il tipo di antenato

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

3. AncestorLevel: quando mode è FindAncestor allora quale livello di antenato (se ci sono due lo stesso tipo di genitore in visual tree)

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

Sopra sono tutti i casi d'uso per RelativeSource binding.

Ecco un link di riferimento .

23
Kylo Ren

Non dimenticare TemplatedParent:

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

o

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

Ho creato una libreria per semplificare la sintassi del binding di WPF incluso rendere più semplice l'uso di RelativeSource. Ecco alcuni esempi. Prima:

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

Dopo:

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

Ecco un esempio di come il binding del metodo è semplificato. Prima:

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

Dopo:

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

// XAML
{BindTo SaveObject()}

Puoi trovare la libreria qui: http://www.simplygoodcode.com/2012/08/simpler-wpf-binding.html

Nota nell'esempio "PRIMA" che utilizzo per il binding di metodi che il codice era già ottimizzato usando RelayCommand che l'ultima volta che ho controllato non è una parte nativa di WPF. Senza che l'esempio "PRIMA" sarebbe stato ancora più lungo.

13
Luis Perez

Vale la pena notare che per coloro che inciampano in questo modo di pensare a Silverlight:

Silverlight offre solo un sottoinsieme ridotto di questi comandi

13
Matthew Black

Alcune parti utili:

Ecco come farlo per lo più nel codice:

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

Ho ampiamente copiato questo da Binding Relative Source nel codice Behind.

Inoltre, la pagina MSDN è abbastanza buona per quanto riguarda gli esempi: Class RelativeSource

12
Nathan Cooper

Ho appena pubblicato un'altra soluzione per accedere al DataContext di un elemento genitore in Silverlight che funziona per me. Usa Binding ElementName.

10
Juve

Non ho letto tutte le risposte, ma voglio solo aggiungere queste informazioni in caso di associazione relativa al comando del codice sorgente di un pulsante.

Quando si utilizza una fonte relativa con Mode=FindAncestor, l'associazione deve essere come:

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

Se non si aggiunge DataContext nel percorso, al momento dell'esecuzione non è possibile recuperare la proprietà.

9
Kevin VDF

Questo è un esempio dell'uso di questo modello che ha funzionato per me su datagrids vuoti.

<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

Se un elemento non fa parte dell'albero visivo, RelativeSource non funzionerà mai.

In questo caso, devi provare una tecnica diversa, sperimentata da Thomas Levesque.

Ha la soluzione sul suo blog sotto [WPF] Come associare i dati quando DataContext non è ereditato . E funziona assolutamente alla grande!

Nell'improbabile caso in cui il suo blog sia inattivo, l'Appendice A contiene una copia speculare di il suo articolo .

Per favore non commentare qui, per favore commenta direttamente sul suo post sul blog .

Appendice A: Specchio del post del blog

La proprietà DataContext in WPF è estremamente utile, poiché viene automaticamente ereditata da tutti i figli dell'elemento in cui viene assegnata; pertanto non è necessario reimpostarlo su ciascun elemento che si desidera associare. Tuttavia, in alcuni casi DataContext non è accessibile: accade per elementi che non fanno parte dell'albero visivo o logico. Può essere molto difficile quindi legare una proprietà a quegli elementi ...

Illustriamo con un semplice esempio: vogliamo visualizzare un elenco di prodotti in un DataGrid. Nella griglia, vogliamo essere in grado di mostrare o nascondere la colonna Prezzo, in base al valore di una proprietà ShowPrice esposta da ViewModel. L'approccio ovvio è quello di associare la visibilità della colonna alla proprietà ShowPrice:

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

Purtroppo, la modifica del valore di ShowPrice non ha alcun effetto e la colonna è sempre visibile ... perché? Se guardiamo la finestra Output in Visual Studio, notiamo la seguente riga:

Errore System.Windows.Data: 2: Impossibile trovare il framework FrameworkElement o FrameworkContentElement per l'elemento di destinazione. BindingExpression: Path = ShowPrice; DataItem = null; l'elemento di destinazione è 'DataGridTextColumn' (HashCode = 32685253); la proprietà target è "Visibility" (tipo "Visibility")

Il messaggio è piuttosto criptico, ma il significato è in realtà piuttosto semplice: WPF non sa quale FrameworkElement utilizzare per ottenere DataContext, perché la colonna non appartiene all'albero visivo o logico di DataGrid.

Possiamo provare a modificare l'associazione per ottenere il risultato desiderato, ad esempio impostando RelativeSource su DataGrid stesso:

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

Oppure possiamo aggiungere un CheckBox associato a ShowPrice e provare a associare la visibilità della colonna alla proprietà IsChecked specificando il nome dell'elemento:

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

Ma nessuno di questi workaround sembra funzionare, otteniamo sempre lo stesso risultato ...

A questo punto, sembra che l'unico approccio praticabile sarebbe quello di modificare la visibilità della colonna in code-behind, che di solito preferiamo evitare quando si utilizza il pattern MVVM ... Ma non mi arrenderò così presto, almeno non mentre ci sono altre opzioni da considerare ????

La soluzione al nostro problema è in realtà abbastanza semplice e sfrutta la classe Freezable. Lo scopo principale di questa classe è definire oggetti che hanno uno stato modificabile e di sola lettura, ma la caratteristica interessante nel nostro caso è che gli oggetti Freezable possono ereditare il DataContext anche quando non si trovano nell'albero visivo o logico. Non conosco il meccanismo esatto che consente questo comportamento, ma ne approfitteremo per rendere il nostro lavoro vincolante ...

L'idea è di creare una classe (l'ho chiamata BindingProxy per ragioni che dovrebbero diventare ovvie molto presto) che eredita Freezable e dichiara una proprietà di dipendenza 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));
}

Possiamo quindi dichiarare un'istanza di questa classe nelle risorse di DataGrid e associare la proprietà Data al DataContext corrente:

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

L'ultimo passo è specificare questo oggetto BindingProxy (facilmente accessibile con StaticResource) come origine per il bind:

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

Si noti che il percorso di bind è stato prefisso con "Dati", poiché il percorso è ora relativo all'oggetto BindingProxy.

L'associazione ora funziona correttamente e la colonna viene visualizzata correttamente o nascosta in base alla proprietà ShowPrice.

4
Contango