Domanda

Come si usa RelativeSource con le associazioni WPF e quali sono i diversi casi d'uso?

È stato utile?

Soluzione

Se vuoi associarti 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 vuoi ottenere una proprietà sul genitore basato su modello (in modo da poter eseguire associazioni a 2 vie in un ControlTemplate)

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

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

{TemplateBinding Path=PathToProperty}

Altri suggerimenti

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

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

  • Dati precedenti Consente di associare l'elemento dati precedente (non il controllo che contiene l'elemento dati) nell'elenco degli elementi dati visualizzati.

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

  • Se stesso 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.

  • TrovaAncestor Si riferisce all'antenato nella catena principale dell'elemento associato a dati.Puoi usarlo per legarti a un antenato di un tipo specifico o alle sue sottoclassi.Questa è la modalità da utilizzare se si desidera specificare AncestorType e/o AncestorLevel.

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

enter image description here

Immagina questo caso, un rettangolo di cui vogliamo che la sua altezza sia sempre uguale alla sua larghezza, diciamo un quadrato.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 in questo caso siamo obbligati a indicare il nome dell'oggetto vincolante, cioè 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 di rilegatura e la Larghezza sarà sempre uguale all'Altezza ogni volta che si modifica l'altezza.

Se desideri parametrizzare la larghezza in modo che sia la metà dell'altezza, puoi farlo aggiungendo un convertitore all'estensione di markup Binding.Immaginiamo ora un altro caso:

 <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 genitori diretti poiché questo elemento contiene una proprietà chiamata Parent.Questo ci porta ad un'altra modalità di origine relativa che è quella FindAncestor.

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

La parenteurce è un'estensione di markup che viene utilizzata in casi vincolanti particolari quando proviamo a legare una proprietà di un dato oggetto a un'altra proprietà dell'oggetto stesso, quando proviamo a vincolare una proprietà di un oggetto a un altro dei suoi genitori relativi, quando si vincola un valore di proprietà di dipendenza a un pezzo di XAML in caso di sviluppo del controllo personalizzato e infine in caso di utilizzo di un differenziale di una serie di dati legati.Tutte queste situazioni sono espresse come modalità di sorgente relativa.Esporrò tutti questi casi uno per uno.

  1. Modalità Auto:

Immagina questo caso, un rettangolo che vogliamo che la sua altezza sia sempre uguale alla sua larghezza, diciamo un quadrato.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 in questo caso sopra siamo obbligati a indicare il nome dell'oggetto vincolante, vale a dire il rettangolo.Possiamo raggiungere lo stesso scopo in modo diverso usando la parenteurce

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

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

Se si desidera parametro la larghezza per essere la metà dell'altezza, puoi farlo aggiungendo un convertitore all'estensione di markup di legame.Immaginiamo ora un altro caso:

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

Il caso di cui sopra viene utilizzato per legare una data proprietà di un determinato elemento a uno dei suoi genitori diretti in quanto questo elemento contiene una proprietà che si chiama genitore.Questo ci porta a un'altra modalità di origine relativa che è quella di Findancestor.

  1. Modalità TrovaAncestor

In questo caso, una proprietà di un determinato elemento sarà legata a uno dei suoi genitori, di Corse.La differenza principale con il caso sopra è il fatto che, spetta a te determinare il tipo di antenato e il rango 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 è di due elementi di blocco di testo che sono incorporati all'interno di una serie di bordi e elementi di tela che rappresentano i loro genitori gerarchici.Il secondo blocco di testo visualizzerà il nome del genitore dato a livello di sorgente relativa.

Quindi prova a cambiare antenatura = 2 ad antenatevel = 1 e vedi cosa succede.Quindi prova a cambiare il tipo di antenato da antentortype = bordo all'antiertype = tela e guarda cosa succede.

Il testo visualizzato cambierà in base al tipo e al livello antenato.Allora cosa succede se il livello degli antenati non è adatto al tipo di antenato?Questa è una buona domanda, so che stai per chiederlo.La risposta non verrà lanciata eccezioni e nulla verrà visualizzato a livello di blocco di testo.

  1. TemplatedParent

Questa modalità abilita il legame una determinata proprietà ControlTemplate a una proprietà del controllo a cui viene applicato il controllo di controllo.Per capire bene il problema qui è un esempio 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, posso usare la modalità TemplatedParent.C'è anche uno simile a questa estensione di markup che è la legame del modello che è una specie di mano corta del primo, ma il legame del modello viene valutato al momento della compilazione al contrasto del TemplatedParent che viene valutato subito dopo il primo tempo di esecuzione.Come puoi osservare nella figura qui sotto, lo sfondo e il contenuto vengono applicati all'interno del pulsante al modello di controllo.

Nel WPF RelativeSource il legame ne espone tre properties impostare:

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

UN.Dati precedenti(value=0): Assegna il valore precedente di property al limite

B.ModelloParent(value=1): Viene utilizzato per definire il file templates di qualsiasi controllo e desideri legarsi a un valore/proprietà del control.

Per esempio, definire ControlTemplate:

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

C.Se stesso(value=2): Quando vogliamo associare da a self o a property di sé.

Per esempio: Invia stato controllato di checkbox COME CommandParameter durante l'impostazione di Command SU CheckBox

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

D.TrovaAntenato(value=3): Quando vuoi vincolarti da un genitore controlIn Visual Tree.

Per esempio: Rilegare a checkbox In records se un grid,Se header checkbox è controllato

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

2.Tipo antenato: quando la modalità è FindAncestor quindi definire quale tipo di antenato

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

3.Livello antenato: quando la modalità è FindAncestor quindi quale livello di antenato (se ci sono due genitori dello stesso tipo in visual tree)

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

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

Ecco un link di riferimento.

Non dimenticare TemplatedParent:

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

O

{Binding RelativeSource={RelativeSource TemplatedParent}}

È degno di nota che per coloro che si imbattono in questo pensiero su Silverlight:

Silverlight offre solo un sottoinsieme ridotto di questi comandi

Ho creato una libreria per semplificare la sintassi di associazione di WPF, inclusa la semplificazione dell'utilizzo 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 viene semplificata l'associazione del metodo.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 "BEFORE" che utilizzo per l'associazione del metodo che il codice era già ottimizzato utilizzando RelayCommand l'ultimo che ho controllato non è una parte nativa di WPF.Senza di ciò l'esempio "PRIMA" sarebbe stato ancora più lungo.

Alcuni frammenti utili:

Ecco come farlo principalmente nel codice:

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

L'ho in gran parte copiato da Associazione dell'origine relativa nel codice dietro.

Inoltre, la pagina MSDN è piuttosto buona per quanto riguarda gli esempi: Classe RelativeSource

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

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

Quando usi una fonte relativa con Mode=FindAncestor, l'associazione deve essere del tipo:

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

Se non aggiungi DataContext nel percorso, in fase di esecuzione non sarà possibile recuperare la proprietà.

Questo è un esempio dell'uso di questo modello che ha funzionato per me su griglie dati vuote.

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

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

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

Ha la soluzione sul suo blog sotto [WPF] Come eseguire l'associazione ai dati quando DataContext non viene ereditato.E funziona in modo assolutamente brillante!

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

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

Appendice A:Specchio del post del blog

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

Illustriamolo 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 Price, in base al valore di una proprietà ShowPrice esposta da ViewModel.L'approccio ovvio è associare la Visibilità della colonna alla proprietà ShowPrice:

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

Purtroppo cambiare il valore di ShowPrice non ha alcun effetto, e la colonna è sempre visibile… perchè?Se guardiamo la finestra di output in Visual Studio, notiamo la seguente riga:

Errore System.Windows.Data:2:Impossibile trovare FrameworkElement o FrameworkContentElement governanti per l'elemento di destinazione.BindingExpression:Path=ShowPrice;Elemento dati=null;l'elemento di destinazione è "DataGridTextColumn" (HashCode=32685253);la proprietà di destinazione è "Visibilità" (tipo "Visibilità")

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 ad 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 nessuna di queste soluzioni alternative sembra funzionare, otteniamo sempre lo stesso risultato...

A questo punto, sembra che l'unico approccio praticabile sarebbe quello di modificare la visibilità delle colonne nel code-behind, cosa 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 uno di sola lettura, ma la caratteristica interessante nel nostro caso è che gli oggetti Freezable possono ereditare DataContext anche quando non si trovano nell'albero visivo o logico.Non conosco l'esatto meccanismo che abilita questo comportamento, ma ne approfitteremo per far funzionare il nostro legame...

L'idea è quella di creare una classe (l'ho chiamata BindingProxy per ragioni che dovrebbero diventare ovvie molto presto) che erediti Freezable e dichiari una proprietà di dipendenza dei dati:

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 passaggio consiste nel specificare questo oggetto BindingProxy (facilmente accessibile con StaticResource) come origine per l'associazione:

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

Tieni presente che il percorso di associazione è stato preceduto da "Dati", poiché il percorso è ora relativo all'oggetto BindingProxy.

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

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top