WPF vinculação de dados e IValueConverter
-
05-07-2019 - |
Pergunta
Por que é que quando eu uso um conversor na minha expressão de ligação em WPF, o valor não é atualizado quando os dados são atualizados.
Eu tenho um modelo de dados Pessoa simples:
class Person : INotifyPropertyChanged
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
minha expressão olhares de ligação como este:
<TextBlock Text="{Binding Converter={StaticResource personNameConverter}" />
meu conversor esta aparência:
class PersonNameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Person p = value as Person;
return p.FirstName + " " + p.LastName;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Se eu ligar os dados sem um conversor ele funciona muito bem:
<TextBlock Text="{Binding Path=FirstName}" />
<TextBlock Text="{Binding Path=LastName}" />
O que eu estou ausente?
EDIT: Só para esclarecer algumas coisas, tanto Joel e Alan estão corretas sobre a interface INotifyPropertyChanged que precisa ser implementado. Na realidade eu realmente implementá-lo, mas ele ainda não funciona.
Eu não posso usar vários elementos TextBlock porque eu estou tentando ligar o título da janela para o nome completo, e não o título da janela não ter um modelo.
Finalmente, é uma opção para adicionar uma propriedade composto "FullName" e se ligam a ele, mas eu ainda estou querendo saber por que a atualização não acontece quando o uso obrigatório de um conversor. Mesmo quando eu colocar um break point no código conversor, o depurador só não fica lá quando uma atualização é feita para os dados subjacentes: - (
Obrigado, Uri
Solução
(ver edições abaixo; mais recente: Número 2)
Não é atualizar porque seu objeto Person
não é capaz de notificar qualquer coisa que o valor de FirstName
ou LastName
mudou. Veja esta Pergunta .
E aqui está como você implementar INotifyPropertyChanged
. ( Atualizado, consulte Editar 2 )
using System.ComponentModel;
class Person : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
string _firstname;
public string FirstName {
get {
return _firstname;
}
set {
_firstname = value;
onPropertyChanged( "FirstName", "FullName" );
}
}
string _lastname;
public string LastName {
get {
return _lastname;
}
set {
_lastname = value;
onPropertyChanged( "LastName", "FullName" );
}
}
public string FullName {
get {
return _firstname + " " + _lastname;
}
}
void onPropertyChanged( params string[] propertyNames ) {
PropertyChangedEventHandler handler = PropertyChanged;
if ( handler != null ) {
foreach ( var pn in propertyNames ) {
handler( this, new PropertyChangedEventArgs( pn ) );
}
}
}
}
Editar 1
Na verdade, desde que você está após o primeiro nome eo nome da última atualização e Path=FirstName
e essas obras muito bem, eu não acho que você vai precisar do conversor em tudo. TextBlocks
múltipla são tão válido, e pode realmente funcionar melhor quando você está de localizações para um idioma da direita para a esquerda.
Editar 2
Eu percebi isso. Não está sendo notificado de que as propriedades tenham atualizado porque ele é vinculativo para o objeto em si, não é um daqueles propriedades. Mesmo quando eu fiz Person
um DependencyObject
e fez FirstName
e LastName
DependencyProperties
, não seria atualizar.
Você irá tem que usar uma propriedade FullName
, e eu atualizar o código da classe Person
acima para refletir isso. Depois, você pode vincular a Title
. ( Nota:. eu definir o objeto Person
como Window
do DataContext
)
Title="{Binding Path=FullName, Mode=OneWay}"
Se você estiver editando os nomes em uma TextBox
e quer que o nome mudou reflete imediatamente em vez de quando o TextBox
perde o foco, você pode fazer isso:
<TextBox Name="FirstNameEdit"
Text="{Binding Path=FirstName, UpdateSourceTrigger=PropertyChanged}" />
Eu sei que você não quer usar uma propriedade FullName
, mas nada que pudesse realizar o que você quer provavelmente seria um pouco de um dispositivo de Rube Goldberg. Tais como a implementação INotifyPropertyChanged
e uma propriedade Person
na própria classe Window
, tendo o Window
ouvir sobre o evento PropertyChanged
para disparar evento Window
do PropertyChanged
, e usando uma ligação como o seguinte relativa. Você também teria defina a propriedade Person
antes InitializeComponent()
ou incêndio PropertyChanged
depois de definir a propriedade Person
para que ele aparece, é claro. (Caso contrário, será null
durante InitializeComponent()
e precisa saber quando é um Person
.)
<Window.Resources>
<loc:PersonNameConverter
x:Key="conv" />
</Window.Resources>
<Window.Title>
<Binding
RelativeSource="{RelativeSource Self}"
Converter="{StaticResource conv}"
Path="Person"
Mode="OneWay" />
</Window.Title>
Outras dicas
Você também pode usar um MultiBinding .. Bind para o objeto Pessoa, o nome e sobrenome. Dessa forma, o valor é atualizado assim que FirstName ou LastName lança o evento de propriedade alterada.
<MultiBinding Converter="{IMultiValueConverter goes here..}">
<Binding />
<Binding Path="FirstName" />
<Binding Path="LastName" />
</MultiBinding>
Ou se você só usar o nome e sobrenome, tira o objeto Pessoa da ligação a algo como isto:
<MultiBinding Converter="{IMultiValueConverter goes here..}">
<Binding Path="FirstName" />
<Binding Path="LastName" />
</MultiBinding>
E os olhares MultiValueConverter como este:
class PersonNameConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values[0].ToString() + " " + values[1].ToString();
}
public object ConvertBack(object[] values, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Mas, claro, a resposta selecionada funciona bem, mas um MultiBinding funciona mais elegante ...
Para que a ligação a ser atualizado, suas necessidades de classe pessoa para implementar INotifyPropertyChanged para deixar o know vinculativo que as propriedades do objeto foram udpated. Você também pode salvar-se do conversor adicional, fornecendo uma propriedade fullName.
using System.ComponentModel;
namespace INotifyPropertyChangeSample
{
public class Person : INotifyPropertyChanged
{
private string firstName;
public string FirstName
{
get { return firstName; }
set
{
if (firstName != value)
{
firstName = value;
OnPropertyChanged("FirstName");
OnPropertyChanged("FullName");
}
}
}
private string lastName;
public string LastName
{
get { return lastName; }
set
{
if (lastName != value)
{
lastName = value;
OnPropertyChanged("LastName");
OnPropertyChanged("FullName");
}
}
}
public string FullName
{
get { return firstName + " " + lastName; }
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
#endregion
}
}
A sua ligação vai ficar assim:
<TextBlock Text="{Binding Person.FullName}" />
Eu não verificá-lo, mas você também pode tentar o seguinte
<TextBlock Text="{Binding Path=/, Converter={StaticResource personNameConverter}}" />