Pergunta

Como faço para usar RelativeSource com ligações WPF e quais são os diferentes casos de uso?

Foi útil?

Solução

Se você quiser ligar para outra propriedade no objeto:

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

Se você deseja obter uma propriedade em um ancestral:

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

Se você quiser obter uma propriedade no pai templated (assim você pode fazer 2 maneira ligações em um ControlTemplate)

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

ou, mais curta (isso só funciona para OneWay ligações):

{TemplateBinding Path=PathToProperty}

Outras dicas

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

O atributo padrão de RelativeSource é propriedade Mode. Um conjunto completo de valores válidos é dado aqui ( do MSDN ):

  • PreviousData Permite ligar o item de dados anterior (não que o controle que contém o item de dados) na lista de itens de dados que está sendo exibido.

  • TemplatedParent Refere-se ao elemento ao qual se aplica o modelo (em que existe o elemento de dados com destino). Isto é semelhante à fixação de um TemplateBindingExtension e só é aplicável se a ligação está dentro de um modelo.

  • Auto Refere-se ao elemento sobre o qual você está definindo a ligação e permite que você vincular uma propriedade desse elemento para outra propriedade no mesmo elemento.

  • FindAncestor Refere-se ao antepassado na cadeia principal do elemento de dados com destino. Você pode usar isso para se ligam a um antepassado de um tipo específico ou de suas subclasses. Este é o modo que você usa, se você quiser especificar AncestorType e / ou AncestorLevel.

Aqui está uma explicação mais visual no contexto de uma arquitetura MVVM:

enter descrição da imagem aqui

Imagine que neste caso, um retângulo que nós queremos que a sua altura é sempre igual à sua largura, digamos de um let quadrado. Podemos fazer isso usando o nome do elemento

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

Mas neste caso acima temos a obrigação de indicar o nome do objeto de ligação, ou seja, o retângulo. Nós podemos alcançar a mesma finalidade diferente usando o RelativeSource

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

Para esse caso não estamos obrigados a mencionar o nome do objeto de ligação e a largura será sempre igual à altura sempre que a altura é alterada.

Se você quiser parâmetro a largura para ser a metade da altura, então você pode fazer isso adicionando um conversor para a extensão de marcação de vinculação. Vamos imaginar um outro caso agora:

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

O caso acima é usada para amarrar uma determinada propriedade de um determinado elemento a um dos seus queridos controladores diretos como esse elemento tem uma propriedade que é chamado de Pai. Isso nos leva a um outro modo de fonte relativo que é o FindAncestor.

Bechir Bejaoui expõe os casos de uso dos RelativeSources em WPF em seu artigo aqui :

O RelativeSource é uma extensão de marcação que é usada em particular casos de ligação quando tentamos vincular uma propriedade de um determinado objeto para outra propriedade do objeto em si, quando tentamos ligar uma propriedade de um objeto para outro de seus pais relativos, ao ligar um valor de propriedade de dependência a um pedaço de XAML em caso de controle personalizado desenvolvimento e, finalmente, no caso da utilização de um diferencial de uma série de um limite de dados. Todas essas situações são expressos como fonte relativa modos. Vou expor todos esses casos, um por um.

  1. Modo Auto:

Imagine que neste caso, um retângulo que nós queremos que a sua altura é sempre igual à sua largura, digamos de um let quadrado. Podemos fazer isso usando o nome do elemento

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

Mas neste caso acima temos a obrigação de indicar o nome do objeto, ou seja, o rectângulo de ligação. Nós podemos alcançar a mesma finalidade de forma diferente usando o RelativeSource

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

Para esse caso não estamos obrigados a mencionar o nome da ligação objecto e a largura será sempre igual à altura, sempre que o altura é alterada.

Se você quiser parâmetro a largura para ser a metade da altura, em seguida, você pode fazer isso adicionando um conversor para a extensão de marcação de vinculação. Vamos imaginar um outro caso agora:

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

O caso acima é usada para amarrar uma determinada propriedade de um determinado elemento para um de seus queridos controladores diretos como esse elemento tem uma propriedade que é chamado Pai. Isso nos leva a um outro modo de fonte relativo que é o FindAncestor.

  1. Modo FindAncestor

Neste caso, uma propriedade de um determinado elemento vai ser amarrado a um dos seus pais, dos Corse. A principal diferença com o caso acima é o fato isso, é até você para determinar o tipo ancestral e o ancestral posto na hierarquia para amarrar a propriedade. Pela maneira tentar jogar com este pedaço de 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>

A situação acima é de dois elementos TextBlock aqueles são incorporados dentro de uma série de fronteiras e elementos de lona aqueles representar seu pais hierárquica. O segundo TextBlock irá exibir o nome do o pai dada no nível de fonte relativo.

Portanto, tente mudar AncestorLevel = 2 a AncestorLevel = 1 e ver o que acontece. Em seguida, tentar alterar o tipo de ancestral de AncestorType = Border para AncestorType = Canvas e ver o que acontece.

O texto exibido mudará de acordo com o tipo ancestral e nível. Então o que está acontecer se o nível ancestral não é adequado para o ancestral digitar? Esta é uma boa pergunta, eu sei que você está prestes a pergunte isso. A resposta é nenhuma exceção será lançada e nothings vai ser exibido no nível TextBlock.

  1. TemplatedParent

Este modo permite amarrar uma determinada propriedade ControlTemplate a uma propriedade do controlo que o ControlTemplate é aplicado a. para bem compreender a questão aqui é um exemplo abaixo

<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 eu quiser aplicar as propriedades de um determinado controle de seu controle template, então eu posso usar o modo TemplatedParent. Há também um uma semelhante a esta extensão de marcação que é o TemplateBinding que é um tipo de mão curta do primeiro, mas o TemplateBinding é avaliada no momento de compilação no contraste do TemplatedParent que é avaliada logo após o primeiro tempo de execução. Como você pode observar na figura abaixo, o fundo eo conteúdo são aplicados a partir de dentro o botão para o modelo de controle.

Em WPF de ligação RelativeSource expõe três properties definir:

1. Modo: Esta é uma enum que poderia ter quatro valores:

a. PreviousData (value=0): Ele atribui o valor anterior do property para o limite

b. TemplatedParent (value=1): Isto é usado na definição do templates de qualquer controle e quer se ligam a um valor / propriedade do control.

Por exemplo, definir ControlTemplate:

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

c. Auto (value=2):. Quando queremos vincular a partir de um self ou um property de auto

Por exemplo: Enviar verificado estado de checkbox como CommandParameter ao definir a Command em CheckBox

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

d. FindAncestor (value=3): Quando deseja vincular a partir de um control pai em Visual Tree.

Por exemplo: Bind um checkbox em records se um grid, se header checkbox está marcada

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

2. AncestorType: quando o modo é FindAncestor em seguida, definir o tipo de ancestral

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

3. AncestorLevel: quando o modo é FindAncestor então o nível de ancestral (se houver dois mesmo tipo de pai na visual tree)

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

Acima são todos os casos de uso para RelativeSource binding .

Aqui está um link de referência .

Não se esqueça TemplatedParent:

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

ou

{Binding RelativeSource={RelativeSource TemplatedParent}}

É digno de nota que, para aqueles tropeçar em este pensamento de Silverlight:

Silverlight oferece um subconjunto reduzido apenas, destes comandos

Eu criei uma biblioteca para simplificar a sintaxe ligação do WPF inclusive tornando-se mais fácil de usar RelativeSource. Aqui estão alguns exemplos. Antes:

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

Depois:

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

Aqui está um exemplo de como a ligação método é simplificado. Antes:

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

Depois:

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

// XAML
{BindTo SaveObject()}

Você pode encontrar a biblioteca aqui: http://www.simplygoodcode.com/2012/ 08 / simples-wpf-binding.html

Note no exemplo 'antes' que eu uso para a ligação que o código método já foi otimizado usando RelayCommand que última vez que verifiquei não é uma parte natural de WPF. Sem que o exemplo 'antes' teria sido ainda mais tempo.

Alguns bits úteis e peças:

Aqui está como fazê-lo principalmente em código:

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

I copiado em grande parte este do ligação Relativa Fonte no código por trás .

Além disso, a página MSDN é bom bastante, tanto quanto exemplos ir: RelativeSource Classe

Acabei de publicar outra solução para acessar o DataContext de um elemento pai no Silverlight que funciona para mim. Ele usa Binding ElementName.

Eu não li cada resposta, mas eu só quero acrescentar esta informação em caso de relação de comando fonte de ligação de um botão.

Quando você usa uma fonte parente com Mode=FindAncestor, o deve ligação ser como:

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

Se você não adicionar DataContext em seu caminho, em tempo de execução não pode recuperar a propriedade.

Este é um exemplo do uso desse padrão que trabalhou para mim em datagrids vazias.

<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 um elemento não é parte da árvore visual, então RelativeSource nunca vai funcionar.

Neste caso, você precisa para tentar uma técnica diferente, iniciada por Thomas Levesque.

Ele tem a solução em seu blog sob [WPF] Como vincular a dados quando o DataContext não é herdada . E funciona absolutamente brilhante!

No caso improvável de que seu blog é para baixo, Apêndice A contém uma cópia espelho do seu artigo .

Por favor, não comente aqui, por favor comentário diretamente em seu blog pós .

Apêndice A: Mirror of post

A propriedade DataContext em WPF é extremamente útil, porque é herdada automaticamente por todos os filhos do elemento onde atribuí-lo; portanto, você não precisará configurá-lo novamente em cada elemento que você deseja ligar. No entanto, em alguns casos, o DataContext não é acessível: isso acontece para elementos que não fazem parte da árvore visual ou lógica. Pode ser muito difícil, então para vincular uma propriedade sobre os elementos ...

Vamos ilustrar com um exemplo simples: queremos exibir uma lista de produtos em um DataGrid. Na grade, nós queremos ser capazes de mostrar ou ocultar a coluna Preço, com base no valor de uma propriedade ShowPrice exposta pelo ViewModel. A abordagem óbvia é a de vincular a visibilidade da coluna para a propriedade ShowPrice:

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

Infelizmente, alterando o valor do ShowPrice não tem nenhum efeito, ea coluna é sempre visível ... por quê? Se olharmos para a janela de saída no Visual Studio, observamos a seguinte linha:

System.Windows.Data de erro: 2: Não é possível encontrar governar FrameworkElement ou FrameworkContentElement para o elemento alvo. BindingExpression: Path = ShowPrice; DataItem = nulo; elemento alvo é ‘DataGridTextColumn’ (HashCode = 32685253); propriedade de destino é ‘Visibilidade’ (tipo ‘Visibilidade’)

A mensagem é bastante enigmática, mas o significado é realmente muito simples: WPF não sabe qual FrameworkElement para começar a usar o DataContext, porque a coluna não pertence à árvore visual ou lógico do DataGrid

Podemos tentar ajustar a ligação para obter o resultado desejado, por exemplo, definindo o RelativeSource ao próprio DataGrid:

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

Ou podemos adicionar um CheckBox obrigado a ShowPrice, e tentar vincular a visibilidade da coluna para a propriedade IsChecked, especificando o nome do elemento:

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

Mas nenhuma dessas alternativas parece trabalho, estamos sempre obter o mesmo resultado ...

Neste ponto, parece que a única abordagem viável seria a de alterar a visibilidade coluna no code-behind, que geralmente preferem evitar ao usar o padrão MVVM ... Mas eu não vou desistir tão cedo, pelo menos não enquanto há outras opções a considerar ??

A solução para o nosso problema é realmente muito simples, e tira proveito da classe Freezable. O objetivo principal desta classe é definir objetos que têm uma modificáveis ??e um estado somente leitura, mas a característica interessante no nosso caso é que Freezable objetos podem herdar o DataContext mesmo quando eles não estão na árvore visual ou lógica. Eu não sei o mecanismo exato que permite esse comportamento, mas nós vamos aproveitá-la para fazer o nosso trabalho de ligação ...

A idéia é criar uma classe (eu o chamei BindingProxy por razões que devem se tornar óbvio muito em breve) que herda Freezable e declara uma propriedade de dependência de dados:

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

Podemos então declarar um instance desta classe nos recursos do DataGrid e vincular a propriedade de dados para o DataContext atual:

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

O último passo é especificar o objeto BindingProxy (facilmente acessível com StaticResource) como a origem para a ligação:

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

Note que o caminho de ligação foi prefixado com “dados”, uma vez que o caminho está agora em relação ao objeto BindingProxy.

A ligação agora funciona corretamente, ea coluna está devidamente mostradas ou ocultadas com base na propriedade ShowPrice.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top