Como uso RelativeSource
com ligações WPF e quais são os diferentes casos de uso?
Como uso RelativeSource
com ligações WPF e quais são os diferentes casos de uso?
Respostas:
Se você deseja vincular 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 (para poder fazer ligações de duas vias em um ControlTemplate)
{Binding Path=PathToProperty, RelativeSource={RelativeSource TemplatedParent}}
ou mais curto (isso funciona apenas para ligações OneWay):
{TemplateBinding Path=PathToProperty}
AncestorType
.
FindAncestor
, antes AncestorType
, recebo o seguinte erro: "O RelativeSource não está no modo FindAncestor". (No VS2013, versão comunitária)
{Binding Path=DataContext.SomeProperty, RelativeSource=...
. Isso foi algo inesperado para mim como novato quando estava tentando vincular ao DataContext de um pai dentro de um DataTemplate.
Binding RelativeSource={
RelativeSource Mode=FindAncestor, AncestorType={x:Type ItemType}
}
...
O atributo padrão de RelativeSource
é a Mode
propriedade Um conjunto completo de valores válidos é fornecido aqui ( do MSDN ):
PreviousData Permite vincular 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. É semelhante à configuração de TemplateBindingExtension e é aplicável apenas se a Vinculação estiver dentro de um modelo.
Auto 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 ligar-se a um ancestral de um tipo específico ou de suas subclasses. Este é o modo que você usa se deseja especificar AncestorType e / ou AncestorLevel.
Aqui está uma explicação mais visual no contexto de uma arquitetura MVVM:
{Binding Message}
(um pouco mais simples ...)
Path=DataContext.Message
para fazer a ligação funcionar. Isso faz sentido, já que você pode fazer ligações relativas à largura / altura / etc. de um controle.
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 específicos de ligação 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 outra de seus pais relativos, ao vincular um valor de propriedade de dependência a um pedaço 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 fonte relativa. Vou expor todos esses casos, um por um.
- Auto do modo:
Imagine este caso, um retângulo que queremos que sua altura seja sempre igual à sua largura, um quadrado, digamos. 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 objetivo de maneira diferente usando o RelativeSource
<Rectangle Fill="Red" Height="100" Stroke="Black" Width="{Binding RelativeSource={RelativeSource Self}, Path=Height}"/>
Nesse caso, não somos obrigados a mencionar o nome do objeto de encadernação e a Largura será sempre igual à Altura sempre que a altura for alterada.
Se você desejar parametrizar a Largura como a metade da altura, poderá fazer isso adicionando um conversor à extensão de marcação Binding. 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 pais diretos, pois esse elemento possui uma propriedade chamada Pai. Isso nos leva a outro modo de fonte relativo, que é o FindAncestor.
- Modo FindAncestor
Nesse 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 depende de você determinar o tipo de ancestral e a classificação do ancestral na hierarquia para vincular 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 é composta por dois elementos TextBlock incorporados a uma série de bordas e elementos de tela que representam seus pais hierárquicos. O segundo TextBlock exibirá o nome do pai fornecido no nível de origem relativo.
Portanto, tente alterar AncestorLevel = 2 para AncestorLevel = 1 e veja o que acontece. Em seguida, tente alterar o tipo do ancestral de AncestorType = Border para AncestorType = Canvas e veja o que está acontecendo.
O texto exibido será alterado de acordo com o tipo e o nível do ancestral. Então, o que acontece se o nível de ancestral não for adequado ao tipo de ancestral? Esta é uma boa pergunta, eu sei que você está prestes a perguntar. A resposta é que nenhuma exceção será lançada e nada será exibido no nível do TextBlock.
- 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. Também existe uma extensão semelhante a essa extensão de marcação, que é a TemplateBinding, que é uma espécie de mão curta da primeira, mas a TemplateBinding é avaliada em tempo de compilação, ao contrário do TemplatedParent, que é avaliado logo após o primeiro tempo de execução. Como você pode observar na figura abaixo, o plano de fundo e o conteúdo são aplicados de dentro do botão ao modelo de controle.
ListView
. O pai tem mais 2 ListView
níveis abaixo dele. Isso me ajudou a impedir a transmissão de dados em cada vm subsequente de cada ListView
umDataTemplate
No WPF, a RelativeSource
associação expõe três properties
para definir:
1. Modo: Este é um enum
que pode ter quatro valores:
uma. PreviousData (
value=0
): atribui o valor anterior doproperty
ao vinculadob. TemplatedParent (
value=1
): É usado ao definirtemplates
qualquer controle e deseja vincular a um valor / Propriedade docontrol
.Por exemplo, defina
ControlTemplate
:
<ControlTemplate>
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</ControlTemplate>
c. Self (
value=2
): quando queremos ligar de umself
ou de umproperty
self.Por exemplo: Enviar verificado estado de
checkbox
comoCommandParameter
ao definir oCommand
onCheckBox
<CheckBox ...... CommandParameter="{Binding RelativeSource={RelativeSource Self},Path=IsChecked}" />
d. FindAncestor (
value=3
): quando deseja ligar de um paicontrol
emVisual Tree
.Por exemplo: Bind um
checkbox
emrecords
que umgrid
, seheader
checkbox
estiver marcada
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid}}, Path=DataContext.IsHeaderChecked, Mode=TwoWay}" />
2. AncestorType: quando mode é, FindAncestor
então, defina que tipo de ancestral
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid}}
3. AncestorLevel: quando mode éFindAncestor
qual o nível de ancestral (se houver dois mesmos tipos de paisvisual tree
)
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid, AncestorLevel=1}}
Acima estão todos os casos de uso para
RelativeSource binding
.
É digno de nota que, para aqueles que se deparam com esse pensamento do Silverlight:
O Silverlight oferece apenas um subconjunto reduzido desses comandos
Criei uma biblioteca para simplificar a sintaxe de ligação do WPF, facilitando o uso do 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 do método é 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 'ANTES' que eu uso para ligação de método que o código já foi otimizado usando a RelayCommand
última verificação que não fiz parte do WPF. Sem isso, o exemplo 'ANTES' teria sido ainda mais longo.
Algumas partes ú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);
Copiei isso amplamente da Binding Relative Source no código Behind .
Além disso, a página MSDN é muito boa no que diz respeito aos exemplos: Classe RelativeSource
Acabei de publicar outra solução para acessar o DataContext de um elemento pai no Silverlight que funciona para mim. Ele usa Binding ElementName
.
Não li todas as respostas, mas só quero adicionar essas informações no caso de ligação de comando de origem relativa de um botão.
Quando você usa uma fonte relativa com Mode=FindAncestor
, a ligação deve ser como:
Command="{Binding Path=DataContext.CommandProperty, RelativeSource={...}}"
Se você não adicionar o DataContext ao seu caminho, no tempo de execução, ele não poderá recuperar a propriedade.
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>
Se um elemento não fizer parte da árvore visual, o RelativeSource nunca funcionará.
Nesse caso, você precisa tentar uma técnica diferente, pioneira em Thomas Levesque.
Ele tem a solução em seu blog em [WPF] Como vincular aos dados quando o DataContext não é herdado . E funciona absolutamente brilhante!
No caso improvável de seu blog estar inativo, o Apêndice A contém uma cópia espelhada de seu artigo .
Por favor, não comente aqui, por favor, comente diretamente em seu blog .
A propriedade DataContext no WPF é extremamente útil, porque é automaticamente herdada por todos os filhos do elemento em que você o 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: isso acontece com elementos que não fazem parte da árvore visual ou lógica. Pode ser muito difícil vincular uma propriedade nesses 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:
System.Windows.Data Erro: 2: Não é possível localizar o FrameworkElement ou o FrameworkContentElement que regem o elemento de destino. BindingExpression: Path = ShowPrice; DataItem = nulo; o elemento de destino é 'DataGridTextColumn' (HashCode = 32685253); a propriedade de destino é 'Visibilidade' (digite 'Visibilidade')
A mensagem é um tanto enigmática, mas o significado é bastante 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, configurando o RelativeSource para o 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 uma CheckBox vinculada 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 alternativas parece funcionar, sempre obtemos o mesmo resultado…
Neste ponto, parece que a única abordagem viável seria alterar a visibilidade da coluna no 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 a considerar 😉
A solução para o nosso problema é realmente bastante simples e tira proveito da classe Freezable. O objetivo principal desta classe é definir objetos que tenham um estado modificável e somente leitura, mas o recurso interessante em nosso caso é que objetos Freezable podem herdar o DataContext mesmo quando não estão na árvore visual ou lógica. Não sei o mecanismo exato que permite esse comportamento, mas vamos tirar proveito dele para fazer nossa ligação funcionar ...
A idéia é criar uma classe (chamei-o 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));
}
Em seguida, podemos 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 "Dados", pois o caminho agora é relativo ao objeto BindingProxy.
A ligação agora funciona corretamente e a coluna é mostrada ou ocultada corretamente com base na propriedade ShowPrice.