Come posso utilizzare i collegamenti WPF con RelativeSource?


Risposte:


783

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}

15
Per questo "{Binding Path = PathToProperty, RelativeSource = {RelativeSource AncestorType = {x: Type typeOfAncestor}}}", sembra che debba avere "Mode = FindAncestor", prima di "AncestorType"
EdwardM

1
Per quale tecnologia? In WPF, ciò viene dedotto quando si specifica un AncestorType.
Abe Heidebrecht,

2
Sono d'accordo con @EdwardM. Quando ometto FindAncestor, prima AncestorType, ottengo il seguente errore: "RelativeSource non è in modalità FindAncestor". (In VS2013, versione Community)
kmote

1
@kmote, questo ha funzionato per me dal .net 3.0, e ho verificato ancora una volta che funziona così in kaxaml ... Ancora una volta, quale tecnologia stai usando? Il processore XAML è diverso per WPF / Silverlight / UWP, quindi potresti avere risultati diversi su tecnologie diverse. Hai anche menzionato VS Community, quindi forse è un avviso IDE, ma funziona in fase di esecuzione?
Abe Heidebrecht,

6
Volevo solo notare qui che, se si desidera associare a una proprietà nel DataContext della RelativeSource allora è necessario specificare esplicitamente: {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.
DrEsperanto,

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

L'attributo predefinito di RelativeSourceè la Modeproprietà. 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.


128

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

inserisci qui la descrizione dell'immagine


19
Ho dimenticato qualcosa? Come puoi considerarlo una grafica semplice e chiara? 1: le caselle sul significato di sinistra non sono realmente correlate a quelle di destra (perché c'è un file .cs all'interno di ViewModel?) 2: a cosa indicano queste frecce DataContext? 3: perché la proprietà Message non è in ViewModel1? e, soprattutto, 5: Perché è necessario un collegamento RelativeSource per accedere al DataContext di Window se TextBlock ha già lo stesso DataContext? Mi manca chiaramente qualcosa qui, quindi o sono piuttosto stupido o questa grafica non è così semplice e chiara come tutti pensano! Per favore, illuminami
Markus Hütter l'

2
@ MarkusHütter Il diagramma mostra un gruppo di viste nidificate e ViewModels corrispondenti. DataContext di View1 è ViewModel1, ma desidera associarsi a una proprietà di BaseViewModel. Poiché BaseViewModel è il DataContext di BaseView (che è una finestra), può farlo trovando il primo contenitore padre che è una finestra e prendendo il suo DataContext.
McCargille,

6
@MatthewCargille So benissimo cosa dovrebbe significare, non era questo il punto. Ma mettiti nei panni di qualcuno che non conosce bene XAML e MVVM e vedrai che questo non è semplice e chiaro .
Markus Hütter,

1
Devo essere d'accordo con @ MarkusHütter, a proposito, la rilegatura a sinistra potrebbe essere così semplice: {Binding Message}(un po 'più semplice ...)
florien,

@florien Non la penso così, almeno per il mio caso d'uso. Ho un DataTemplate che deve fare riferimento al DataContext di MainWindow (la mia classe viewmodel) per ottenere un elenco di opzioni per un menu a discesa (caricato da un database). DataTemplate è associato a un oggetto modello che viene anche caricato dal database, ma ha accesso solo all'opzione selezionata. Ho dovuto impostare esplicitamente Path=DataContext.Messageper far funzionare la rilegatura. Ciò ha senso, dato che è possibile eseguire collegamenti relativi a larghezza / altezza / ecc. di un controllo.
DrEsperanto,

47

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.

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

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

  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

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


Per me esempi molto belli, ho usato Trova antenato per legare un comando nel contesto dei dati di un genitore ListView. Il genitore ha altri 2 ListViewlivelli sotto di esso. Questo mi ha aiutato a evitare la trasmissione di dati in ogni successivo vm di ogni ListViews'DataTemplate
Caleb W.

34

In RelativeSourceassociazione WPF espone tre propertiesper impostare:

1. Modalità: questa enumpuò avere quattro valori:

un. PreviousData ( value=0): assegna il valore precedente dipropertya quello associato

b. TemplatedParent ( value=1): utilizzato per la definizionetemplatesdi 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 aselfo aproperty di sé.

Ad esempio: Invia stato verificato di checkboxas CommandParameterdurante l'impostazione di CommandonCheckBox

<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 checkboxin recordsif a grid, if header checkboxè selezionato

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

2. AncestorType: quando mode è FindAncestorquindi definire quale tipo di antenato

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

3. AncestorLevel: quando mode èFindAncestorallora 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.

Ecco un link di riferimento .


2
Fantastico ... questo ha funzionato per me: <DataGridCheckBoxColumn Header = "Paid" Width = "35" Binding = "{Binding RelativeSource = {RelativeSource Mode = FindAncestor, AncestorType = {x: Type Window}}, Path = DataContext.SelectedBuyer.IsPaid , Mode = OneWay} "/> dove stavo provando a legare alla proprietà selezionatabuyer.IsPaid della finestra padre
Michael K,

21

Non dimenticare TemplatedParent:

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

o

{Binding RelativeSource={RelativeSource TemplatedParent}}


16

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' RelayCommandultimo che ho controllato non è una parte nativa di WPF. Senza quello l'esempio "PRIMA" sarebbe stato ancora più lungo.


2
Questo tipo di esercitazioni manuali dimostrano la debolezza di XAML; modo troppo complicato.
dudeNumber4

16

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


5
La mia vaga memoria di WPF è che fare legature nel codice probabilmente non è in genere la cosa migliore.
Nathan Cooper,

12

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


10

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


9

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>

6

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 .

Appendice A: specchio del post del 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.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.