Come si usano RelativeSource
con i collegamenti WPF e quali sono i diversi casi d'uso?
Come si usano RelativeSource
con i collegamenti WPF e quali sono i diversi casi d'uso?
Risposte:
Se si desidera associare 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 padre basato sul modello (in modo da poter eseguire collegamenti a 2 vie in un ControlTemplate)
{Binding Path=PathToProperty, RelativeSource={RelativeSource TemplatedParent}}
o, più breve (funziona solo per i collegamenti OneWay):
{TemplateBinding Path=PathToProperty}
AncestorType
.
FindAncestor
, prima AncestorType
, ottengo il seguente errore: "RelativeSource non è in modalità FindAncestor". (In VS2013, versione Community)
{Binding Path=DataContext.SomeProperty, RelativeSource=...
. Questo è stato un po 'inaspettato per me come principiante quando stavo cercando di associare DataContext di un genitore all'interno di un DataTemplate.
Binding RelativeSource={
RelativeSource Mode=FindAncestor, AncestorType={x:Type ItemType}
}
...
L'attributo predefinito di RelativeSource
è la Mode
proprietà. Un set completo di valori validi è fornito qui ( da MSDN ):
Dati precedenti Consente di associare l'elemento di dati precedente (non quel controllo che contiene l'elemento di dati) nell'elenco degli elementi di dati visualizzati.
TemplatedParent Si riferisce all'elemento a cui viene applicato il modello (in cui esiste l'elemento con associazione a dati). Ciò è simile all'impostazione di TemplateBindingExtension ed è applicabile solo se il Binding si trova all'interno di un modello.
Auto si riferisce all'elemento su cui si imposta l'associazione e permette di associare una proprietà di quell'elemento ad un'altra proprietà nella stessa elemento.
FindAncestor Si riferisce all'antenato nella catena padre dell'elemento associato a dati. Puoi usarlo per legare un antenato di un tipo specifico o delle sue sottoclassi. Questa è la modalità utilizzata se si desidera specificare AncestorType e / o AncestorLevel.
Ecco una spiegazione più visiva nel contesto di un'architettura MVVM:
{Binding Message}
(un po 'più semplice ...)
Path=DataContext.Message
per far funzionare la rilegatura. Ciò ha senso, dato che è possibile eseguire collegamenti relativi a larghezza / altezza / ecc. di un controllo.
Bechir Bejaoui espone i casi d'uso delle fonti relative in WPF nel suo articolo qui :
RelativeSource è un'estensione di markup che viene utilizzata in casi di associazione particolari quando proviamo a associare una proprietà di un determinato oggetto a un'altra proprietà dell'oggetto stesso, quando proviamo a associare una proprietà di un oggetto a un altro dei suoi genitori relativi, quando si associa un valore della proprietà di dipendenza a un pezzo di XAML in caso di sviluppo di controlli personalizzati e infine in caso di utilizzo di un differenziale di una serie di dati associati. Tutte queste situazioni sono espresse come modalità sorgente relative. Esporrò tutti questi casi uno per uno.
- Modalità Self:
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 utilizzando RelativeSource
<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 modificata.
Se si desidera impostare la larghezza come metà dell'altezza, è possibile farlo aggiungendo un convertitore all'estensione di markup Binding. Immaginiamo un altro caso ora:
<TextBlock Width="{Binding RelativeSource={RelativeSource Self}, Path=Parent.ActualWidth}"/>
Il caso sopra è usato per legare una data proprietà di un dato elemento a uno dei suoi genitori diretti in quanto questo elemento contiene una proprietà che si chiama Parent. Questo ci porta ad un'altra modalità sorgente relativa che è FindAncestor.
- Modalità FindAncestor
In questo caso, una proprietà di un determinato elemento sarà legata a uno dei suoi genitori, Corse. La differenza principale con il caso precedente è il fatto che dipende da 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 sopra è di due elementi TextBlock che sono incorporati in una serie di bordi e gli elementi canvas che rappresentano i loro genitori gerarchici. Il secondo TextBlock mostrerà il nome del genitore dato a livello di sorgente relativa.
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 Ancestor. Quindi cosa succede se il livello degli antenati non è adatto al tipo di antenato? Questa è una buona domanda, so che lo stai per fare. La risposta è che non verranno generate eccezioni e nulla verrà visualizzato a livello di TextBlock.
- 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
<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 dato controllo al suo modello di controllo, allora posso usare la modalità TemplatedParent. Ce n'è anche uno simile a questa estensione di markup che è TemplateBinding che è una specie di scorciatoia del primo, ma TemplateBinding viene valutato in fase di compilazione al contrasto di TemplatedParent che viene valutato subito dopo il primo tempo di esecuzione. Come puoi notare nella figura qui sotto, lo sfondo e il contenuto vengono applicati dal pulsante al modello di controllo.
ListView
. Il genitore ha altri 2 ListView
livelli sotto di esso. Questo mi ha aiutato a evitare la trasmissione di dati in ogni successivo vm di ogni ListView
s'DataTemplate
In RelativeSource
associazione WPF espone tre properties
per impostare:
1. Modalità: questa enum
può avere quattro valori:
un. PreviousData (
value=0
): assegna il valore precedente diproperty
a quello associatob. TemplatedParent (
value=1
): utilizzato per la definizionetemplates
di qualsiasi controllo e si desidera associare un valore / proprietà dicontrol
.Ad esempio, definire
ControlTemplate
:
<ControlTemplate>
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</ControlTemplate>
c. Self (
value=2
): quando vogliamo legare da aself
o aproperty
di sé.Ad esempio: Invia stato verificato di
checkbox
asCommandParameter
durante l'impostazione diCommand
onCheckBox
<CheckBox ...... CommandParameter="{Binding RelativeSource={RelativeSource Self},Path=IsChecked}" />
d. FindAncestor (
value=3
): quando si desidera eseguire il binding da un genitorecontrol
inVisual Tree
.Ad esempio: associare a
checkbox
inrecords
if agrid
, ifheader
checkbox
è selezionato
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid}}, Path=DataContext.IsHeaderChecked, Mode=TwoWay}" />
2. AncestorType: quando mode è FindAncestor
quindi definire quale 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 stessi tipi di parent invisual tree
)
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid, AncestorLevel=1}}
Sopra sono tutti i casi d'uso per
RelativeSource binding
.
È degno di nota che per coloro che si imbattono in questo pensiero di Silverlight:
Silverlight offre solo un sottoinsieme ridotto di questi comandi
Ho creato una libreria per semplificare la sintassi di associazione di WPF e per semplificare l'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 biblioteca qui: http://www.simplygoodcode.com/2012/08/simpler-wpf-binding.html
Nota nell'esempio 'PRIMA' che uso per il metodo di associazione che il codice era già ottimizzato usando l' RelayCommand
ultimo che ho controllato non è una parte nativa di WPF. Senza quello l'esempio "PRIMA" sarebbe stato ancora più lungo.
Alcuni pezzi 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 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 a DataContext di un elemento padre 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 relativa di un pulsante.
Quando si utilizza una fonte relativa con Mode=FindAncestor
, l'associazione deve essere simile a:
Command="{Binding Path=DataContext.CommandProperty, RelativeSource={...}}"
Se non aggiungi DataContext nel tuo percorso, al momento dell'esecuzione non può recuperare la proprietà.
Questo è un esempio dell'uso di questo modello che ha funzionato per me su datagrid 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>
Se un elemento non fa parte della struttura ad albero visuale, RelativeSource non funzionerà mai.
In questo caso, devi provare una tecnica diversa, introdotta da Thomas Levesque.
Ha la soluzione sul suo blog in [WPF] Come associare i dati quando DataContext non viene ereditato . E funziona assolutamente alla perfezione!
Nell'improbabile caso in cui il suo blog non sia attivo, l'Appendice A contiene una copia speculare del suo articolo .
Si prega di non commentare qui, si prega di commentare direttamente sul suo post sul blog .
La proprietà DataContext in WPF è estremamente utile, poiché viene ereditata automaticamente da tutti i figli dell'elemento in cui lo si assegna; pertanto non è necessario impostarlo nuovamente su ogni 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 associare 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 è associare la visibilità della colonna alla proprietà ShowPrice:
<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
Visibility="{Binding ShowPrice,
Converter={StaticResource visibilityConverter}}"/>
Sfortunatamente, la modifica del valore di ShowPrice non ha alcun effetto e la colonna è sempre visibile ... perché? Se osserviamo 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 target. BindingExpression: PATH = ShowPrice; DataItem = null; l'elemento target è 'DataGridTextColumn' (HashCode = 32685253); la proprietà target è "Visibilità" (digitare "Visibilità")
Il messaggio è piuttosto enigmatico, ma il significato è in realtà abbastanza 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 nessuna di queste soluzioni alternative sembra funzionare, otteniamo sempre lo stesso risultato ...
A questo punto, sembra che l'unico approccio praticabile sarebbe quello di cambiare la visibilità della colonna in code-behind, che di solito preferiamo evitare quando si utilizza il modello 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 DataContext anche quando non si trovano nella struttura visiva o logica. Non conosco il meccanismo esatto che abilita questo comportamento, ma ne trarremo vantaggio per far funzionare il nostro legame ...
L'idea è di creare una classe (l'ho chiamata BindingProxy per motivi che dovrebbero diventare ovvi 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 classe nelle risorse di DataGrid e associare la proprietà Data all'attuale DataContext:
<DataGrid.Resources>
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
</DataGrid.Resources>
L'ultimo passaggio è 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}}"/>
Si noti che il percorso di associazione è stato preceduto da "Dati", poiché il percorso è ora relativo all'oggetto BindingProxy.
Il binding ora funziona correttamente e la colonna viene mostrata o nascosta correttamente in base alla proprietà ShowPrice.