it-swarm-pt.tech

Como faço para usar ligações do WPF com RelativeSource?

Como eu uso RelativeSource com ligações do WPF e quais são os diferentes casos de uso?

550
David Schmitt

Se você deseja ligar a 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ê deseja obter uma propriedade no pai modelo (assim você pode fazer ligações de 2 vias em um ControlTemplate)

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

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

{TemplateBinding Path=PathToProperty}
735
Abe Heidebrecht
Binding RelativeSource={
    RelativeSource Mode=FindAncestor, AncestorType={x:Type ItemType}
}
...

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

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

  • TemplatedParent Refere-se ao elemento ao qual o modelo (no qual o elemento ligado a dados existe) é aplicado. Isso é semelhante a definir um TemplateBindingExtension e é aplicável apenas se a vinculação estiver dentro de um modelo.

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

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

126
Drew Noakes

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

enter image description here

120
Jeffrey Knight

Imagine este caso, um retângulo que nós queremos que sua altura seja sempre igual a sua largura, um quadrado digamos. Nós 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, somos obrigados a indicar o nome do objeto de ligação, ou seja, o retângulo. Podemos alcançar o mesmo propósito de maneira diferente usando o RelativeSource

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

Para esse caso, não somos obrigados a mencionar o nome do objeto de ligação e a Largura será sempre igual à Altura sempre que a altura for alterada.

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

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

O caso acima é usado para vincular uma determinada propriedade de um determinado elemento a um de seus parent parent diretos, já que esse elemento mantém uma propriedade que é chamada de Parent. Isso nos leva a outro modo de fonte relativa, que é o FindAncestor.

40
lasitha edirisooriya

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

O RelativeSource é uma extensão de marcação que é usada em casos de ligação específicos quando tentamos vincular uma propriedade de um determinado objeto a outra propriedade do próprio objeto, quando tentamos vincular uma propriedade de um objeto a outro de seus parentes, ao vincular um valor de propriedade de dependência a uma parte de XAML no caso de desenvolvimento de controle personalizado e, finalmente, no caso de usar um diferencial de uma série de dados vinculados. Todas essas situações são expressas como modos de origem relativos. Vou expor todos esses casos um por um.

  1. Modo Self:

Imagine este caso, um retângulo que nós queremos que sua altura seja sempre igual a sua largura, um quadrado digamos. Nós 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, somos obrigados a indicar o nome do objeto de ligação, ou seja, o retângulo. Podemos alcançar o mesmo propósito de maneira diferente usando o RelativeSource

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

Para esse caso, não somos obrigados a mencionar o nome do objeto de ligação e a Largura será sempre igual à Altura sempre que a altura for alterada.

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

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

O caso acima é usado para vincular uma determinada propriedade de um determinado elemento a um de seus parent parent diretos, já que esse elemento mantém uma propriedade que é chamada de Parent. Isso nos leva a outro modo de fonte relativa, que é o FindAncestor.

  1. Modo FindAncestor

Neste caso, uma propriedade de um determinado elemento será vinculada a um de seus pais, Of Corse. A principal diferença com o caso acima é o fato de que, cabe a você determinar o tipo de ancestral e o grau de ancestral na hierarquia para amarrar a propriedade. A propósito, tente brincar 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 que são incorporados em uma série de bordas e elementos de canvas que representam seus pais hierárquicos. O segundo TextBlock exibirá o nome do pai especificado no nível da fonte relativa.

Então tente mudar AncestorLevel = 2 para AncestorLevel = 1 e ver o que acontece. Em seguida, tente alterar o tipo de ancestral de AncestorType = Border para AncestorType = Canvas e veja o que acontece.

O texto exibido será alterado de acordo com o tipo e o nível do Ancestor. Então, o que acontece se o nível do ancestral não for adequado ao tipo de ancestral? Esta é uma boa pergunta, eu sei que você está prestes a perguntar. A resposta é sem exceções será lançada e nothings serão exibidos no nível TextBlock.

  1. TemplatedParent

Esse modo permite vincular uma determinada propriedade ControlTemplate a uma propriedade do controle ao qual o ControlTemplate é aplicado. Para entender bem a questão aqui está 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 ao seu modelo de controle, posso usar o modo TemplatedParent. Há também um similar a esta extensão de marcação que é o TemplateBinding que é um tipo de ponteiro curto do primeiro, mas o TemplateBinding é avaliado em tempo de compilação no contraste do TemplatedParent que é avaliado logo após o primeiro tempo de execução. Como você pode observar na figura abaixo, o fundo e o conteúdo são aplicados de dentro do botão ao modelo de controle.

34
Cornel Marian

No WPF RelativeSource binding expõe três properties para definir:

1. Mode: Este é um enum que pode ter quatro valores:

a. PreviousData (value=0): Atribui o valor anterior de property ao valor limite

b. TemplatedParent (value=1): Isso é usado ao definir o templates de qualquer controle e deseja vincular a um valor/Property do control.

Por exemplo, defina ControlTemplate:

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

c. Self (value=2): Quando queremos vincular de um self ou um property de self.

Por exemplo: Envia o estado marcado de checkbox como CommandParameter enquanto configura o Command em CheckBox

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

d. FindAncestor (value=3): Quando quiser vincular um pai control em Visual Tree.

Por exemplo: Vincular um checkbox em records se um grid, se headercheckbox estiver marcado

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

2. AncestorType: quando o modo for FindAncestor, em seguida, defina o tipo de ancestral

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

3. AncestorLevel: quando o modo é FindAncestor, em seguida, que nível de ancestral (se houver dois mesmo tipo de pai em visual tree)

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

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

Aqui está um link de referência .

23
Kylo Ren

Não se esqueça do TemplatedParent:

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

ou

{Binding RelativeSource={RelativeSource TemplatedParent}}
18
Bob King

Eu criei uma biblioteca para simplificar a sintaxe de ligação do WPF, incluindo facilitar o uso de 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 de:

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

Aqui está um exemplo de como a ligação de métodos é simplificada. 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 de:

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

// XAML
{BindTo SaveObject()}

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

Observe no exemplo 'BEFORE' que eu uso para vinculação de método que o código já estava otimizado usando RelayCommand que a última vez que verifiquei não é uma parte nativa do WPF. Sem isso, o exemplo 'ANTES' teria sido ainda maior.

13
Luis Perez

É digno de nota que para aqueles que tropeçam neste pensamento do Silverlight:

O Silverlight oferece apenas um subconjunto reduzido desses comandos

13
Matthew Black

Alguns bits e peças úteis:

Veja como fazer isso principalmente no código:

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

Eu basicamente copiei isto de Fonte Relativa de Encadernação no código Atrás.

Além disso, a página do MSDN é muito boa em termos de exemplos: Classe RelativeSource

12
Nathan Cooper

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

10
Juve

Eu não li todas as respostas, mas eu só quero adicionar esta informação no caso de ligação de comando de fonte relativa de um botão.

Quando você usa uma origem relativa com Mode=FindAncestor, a ligação deve ser como:

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

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

9
Kevin VDF

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

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

Se um elemento não fizer parte da árvore visual, o RelativeSource nunca funcionará.

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

Ele tem a solução em seu blog em [WPF] Como ligar dados quando o DataContext não é herdado . E isso funciona de forma absolutamente brilhante!

No caso improvável de que seu blog esteja inativo, o Apêndice A contém uma cópia espelhada de seu artigo .

Por favor, não comente aqui, por favor comentar diretamente em seu blog .

Apêndice A: Espelho da postagem do blog

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

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

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

Infelizmente, alterar o valor de ShowPrice não tem efeito, e a coluna está sempre visível ... por quê? Se olharmos para a janela Saída no Visual Studio, notamos a seguinte linha:

Erro System.Windows.Data: 2: Não é possível localizar FrameworkElement ou FrameworkContentElement para o elemento de destino. BindingExpression: Path = ShowPrice; DataItem = nulo; elemento de destino é "DataGridTextColumn" (HashCode = 32685253); a propriedade de destino é "Visibilidade" (digite "Visibilidade")

A mensagem é bastante críptica, mas o significado é bem simples: o WPF não sabe qual FrameworkElement usar para obter o DataContext, porque a coluna não pertence à árvore visual ou lógica do DataGrid.

Podemos tentar ajustar a ligação para obter o resultado desejado, por exemplo, definindo o RelativeSource para o 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 vinculado a ShowPrice e tentar vincular a visibilidade da coluna à 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 soluções parece funcionar, nós sempre obtemos o mesmo resultado ...

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

A solução para o nosso problema é bastante simples e aproveita a classe Freezable. O objetivo principal dessa classe é definir objetos que têm um estado modificável e somente leitura, mas o recurso interessante no nosso caso é que objetos Freezable podem herdar o DataContext mesmo quando eles não estão na árvore visual ou lógica. Não sei o mecanismo exato que permite esse comportamento, mas vamos aproveitar isso para tornar nosso trabalho obrigatório ...

A idéia é criar uma classe (chamei de BindingProxy por razões que devem se tornar óbvias muito em breve) que herda o 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 uma instância dessa classe nos recursos do DataGrid e vincular a propriedade Data ao DataContext atual:

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

A última etapa é especificar esse objeto BindingProxy (facilmente acessível com StaticResource) como a Origem da ligação:

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

Observe que o caminho de ligação foi prefixado com “Data”, já que o caminho agora é relativo ao objeto BindingProxy.

A ligação agora funciona corretamente e a coluna é mostrada ou ocultada adequadamente com base na propriedade ShowPrice.

4
Contango