Come utilizzo i binding WPF con RelativeSource?

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

Se si desidera associare a un’altra proprietà sull’object:

 {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 modelli (in modo che sia ansible eseguire binding in 2 modi in un object ControlTemplate)

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

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

 {TemplateBinding Path=PathToProperty} 
 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 è 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 imposta 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. È ansible utilizzare questo per associare un antenato di un tipo specifico o le sue sottoclassi. Questa è la modalità che usi se vuoi specificare AncestorType e / o AncestorLevel.

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

inserisci la descrizione dell'immagine qui

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

  

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

  

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

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:

   

Il caso precedente viene utilizzato per bind 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.

Bechir Bejaoui espone i casi d’uso delle RelativeSources in WPF nel 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 object a un’altra proprietà dell’object stesso, quando si tenta di associare una proprietà di un object 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

  

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

  

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

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:

   

Il caso precedente viene utilizzato per bind 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 bind la proprietà. A proposito, prova a giocare con questo pezzo di XAML

             

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 guarda 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 farà eccezione e le nothings verranno visualizzate a livello di TextBlock.

  1. TemplatedParent

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

                

Se voglio applicare le proprietà di un determinato controllo al suo modello di controllo, allora posso usare la modalità TemplatedParent. C’è anche uno simile a questa estensione di markup che è il 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.

Non dimenticare TemplatedParent:

  

o

 {Binding RelativeSource={RelativeSource TemplatedParent}} 

Nel collegamento WPF RelativeSource espone tre properties da impostare:

1. Modalità: questa è una enum che può avere quattro valori:

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

b. TemplatedParent ( value=1 ): viene utilizzato quando si definiscono i templates di qualsiasi controllo e si desidera associare a un valore / Proprietà del control .

Ad esempio, definire ControlTemplate :

     

c. Sé ( value=2 ): quando vogliamo bind da un self o una property di sé.

Ad esempio: Invia stato selezionato di checkbox di checkbox come CommandParameter mentre si imposta il Command su CheckBox

  

d. FindAncestor ( value=3 ): quando si desidera eseguire il binding da un control parent in Visual Tree .

Ad esempio: associare una checkbox di checkbox nei records se è selezionata una grid , se la checkbox controllo header

  

2. AncestorType: quando la modalità è FindAncestor definisci il tipo di antenato

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

3. AncestorLevel: quando la modalità è FindAncestor quale livello di antenato (se ci sono due tipi di genitore nella struttura visual tree )

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

Sopra sono tutti i casi d’uso per l’ RelativeSource binding .

Ecco un link di riferimento .

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

Ho creato una libreria per semplificare la syntax 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 biblioteca 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.

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

L’ho copiato in gran parte da Binding Relative Source nel codice Behind .

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

Ho appena pubblicato un’altra soluzione per accedere al DataContext di un elemento padre in Silverlight che funziona per me. Utilizza Binding ElementName .

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

              

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 è ansible recuperare la proprietà.

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 bind ai 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 del 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 impostarlo nuovamente 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 bind 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 hide 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:

  

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: Imansible 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 sul DataGrid stesso:

  

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

  

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 class Freezable. Lo scopo principale di questa class è 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 class (l’ho chiamata BindingProxy per ragioni che dovrebbero diventare ovvie molto presto) che eredita Freezable e dichiara 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 class nelle risorse di DataGrid e associare la proprietà Data al DataContext corrente:

    

L’ultimo passo è specificare questo object BindingProxy (facilmente accessibile con StaticResource) come origine per il binding:

  

Si noti che il percorso di bind è stato preceduto da “Dati”, poiché il percorso è ora relativo all’object BindingProxy.

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