WPF DataBindings раньше делали меня счастливым.Одна вещь, на которую я наткнулся только сейчас, - это то, что в какой-то момент они просто не обновляются, как задумано.Пожалуйста, взгляните на следующий (довольно простой) код:
<Window x:Class="CVFix.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition Height="40"></RowDefinition>
</Grid.RowDefinitions>
<ListBox Grid.Row="0" Grid.Column="0"
ItemsSource="{Binding Path=Persons}"
SelectedItem="{Binding Path=SelectedPerson}"
x:Name="lbPersons"></ListBox>
<TextBox Grid.Row="1" Grid.Column="0" Text="{Binding Path=SelectedPerson.Name, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</Window>
Код XAML:
using System.Windows;
namespace CVFix
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public ViewModel Model { get; set; }
public MainWindow()
{
InitializeComponent();
this.Model = new ViewModel();
this.DataContext = this.Model;
}
}
}
Наконец, вот классы ViewModel:
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace CVFix
{
public class ViewModel : INotifyPropertyChanged
{
private PersonViewModel selectedPerson;
public PersonViewModel SelectedPerson
{
get { return this.selectedPerson; }
set
{
this.selectedPerson = value;
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs("SelectedPerson"));
}
}
public ObservableCollection<PersonViewModel> Persons { get; set; }
public ViewModel()
{
this.Persons = new ObservableCollection<PersonViewModel>();
this.Persons.Add(new PersonViewModel() { Name = "Adam" });
this.Persons.Add(new PersonViewModel() { Name = "Bobby" });
this.Persons.Add(new PersonViewModel() { Name = "Charles" });
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
public class PersonViewModel : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return this.name; }
set
{
this.name = value;
if(this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs("Name"));
}
}
public override string ToString()
{
return this.Name;
}
public event PropertyChangedEventHandler PropertyChanged;
}
Что бы я хотел, чтобы произошло: когда я выбираю запись в ListBox и изменяю ее имя в TextBox, список обновляется для отображения нового значения.
Что происходит: ничего.И это правильное поведение, если я любой судья.Я убедился, что PropertyChanged объекта SelectedItem запущен, но это (конечно) не приводит к запуску CollectionChanged.
Чтобы исправить это, я создал производный от ObservableCollection класс, который имеет открытый метод OnCollectionChanged, см. Здесь:
public class PersonList : ObservableCollection<PersonViewModel>
{
public void OnCollectionChanged()
{
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Reset ));
}
}
Я получаю доступ к этому из конструктора ViewModel, как описано ниже:
public ViewModel()
{
PersonViewModel vm1 = new PersonViewModel()
{
Name = "Adam"
};
PersonViewModel vm2 = new PersonViewModel()
{
Name = "Bobby"
};
PersonViewModel vm3 = new PersonViewModel()
{
Name = "Charles"
};
vm1.PropertyChanged += this.PersonChanged;
this.Persons = new PersonList();
this.Persons.Add(vm1);
this.Persons.Add(vm2);
this.Persons.Add(vm3);
}
void PersonChanged(object sender, PropertyChangedEventArgs e)
{
this.Persons.OnCollectionChanged();
}
Это работает, но это не чистое решение.Моей следующей идеей было бы создать производную от ObservableCollection, которая автоматически выполняет связывание в обработчике CollectionChanged.
public class SynchronizedObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
{
foreach (INotifyPropertyChanged item in e.NewItems)
{
item.PropertyChanged += this.ItemChanged;
}
break;
}
case NotifyCollectionChangedAction.Remove:
{
foreach (INotifyPropertyChanged item in e.OldItems)
{
item.PropertyChanged -= this.ItemChanged;
}
break;
}
}
base.OnCollectionChanged(e);
}
void ItemChanged(object sender, PropertyChangedEventArgs e)
{
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
Вопрос: есть ли лучший способ сделать это?Это действительно необходимо?
Заранее большое спасибо за любой вклад!