2011-02-08 3 views
4

Проверка, чтобы убедиться, что мои предположения верны.Обновление свойств объекта ObservableCollection с использованием INotifyPropertyChanged

У меня есть класс ObservableCollection. Я звоню в веб-службу и получаю массив устройств. Затем я перечисляю ObservableCollection и устанавливаю каждый элемент на соответствующее устройство, полученное из веб-службы. Устройства, которые я получил, имеют разные значения свойств, чем элементы в ObservableCollection, но события PropertyChanged не срабатывают.

Я предполагаю, что это ByDesign, и что для того, чтобы иметь огонь события PropertyChanged, я действительно должен перечислить каждое свойство и установить значение?

Например, в случае, указанном ниже, события PropertyChanged не срабатывают ни в одном из свойств класса Device.

ObservableCollection<Device> Items = new ObservableCollection<Device>(); 
Items = LoadItems(); 

List<Device> devices = GetDevices(); 

foreach (var item in Items) 
{ 
    var currentDevice = devices.Single(d1 => d1.ID == item.ID); 
    item = currentDevice; 
} 

Однако, если я вручную обновлять каждое свойство, я в бизнесе:

ObservableCollection<Device> Items = new ObservableCollection<Device>(); 
Items = LoadItems(); 

List<Device> devices = GetDevices(); 

foreach (var item in Items) 
{ 
    var currentDevice = devices.Single(d1 => d1.ID == item.ID); 
    item.Latitude = currentDevice.Latitude; 
    item.Longitude= currentDevice.Longitude; 
} 

В случае, описанном выше, и широту и долготу огонь их события.

Поскольку у моего класса есть куча свойств, есть ли лучший способ сделать это, чем один за другим?

ответ

1

Для этого может быть полезен метод Load, поэтому вы не перезаписываете ссылку, а устанавливаете все свойства старого объекта. Вот общий метод расширения я просто написал, который назначит все записываемые свойства, очень грубо:

public static class ExtensionMethods 
{ 
    public static void Load<T>(this T target, Type type, T source, bool deep) 
    { 
     foreach (PropertyInfo property in type.GetProperties()) 
     { 
      if (property.CanWrite && property.CanRead) 
      { 
       if (!deep || property.PropertyType.IsPrimitive || property.PropertyType == typeof(String)) 
       { 
        property.SetValue(target, property.GetValue(source, null), null); 
       } 
       else 
       { 
        object targetPropertyReference = property.GetValue(target, null); 
        targetPropertyReference.Load(targetPropertyReference.GetType(), property.GetValue(source, null), deep); 
       } 
      } 
     } 
    } 
} 

Затем вы должны быть в состоянии назвать

item.Load(item.GetType(), currentDevice, true); //false for shallow loading 

присвоить все значения (если они свойства).

Редактировать: Сделал метод рекурсивным, поэтому он будет вызывать Load для свойств, которые не имеют примитивный тип или тип значения (или строку). Вероятно, в некоторых случаях все еще неправильно.
Вы также можете добавить bool deep к методу и управлению, если это необходимо для глубокой загрузки, если это может понадобиться. (Просто добавьте || !deep к этому длинному if-выражению)

Примечание: Вы можете, конечно, также перезаписать ссылку на объект и использовать отражение, чтобы поднять событие PropertyChanged для всех разных свойств, если хотите.В любом случае вам не нужно обрабатывать каждое свойство вручную.

Edit2: Потому что PropertyInfo.GetValue возвращает object мой предыдущий код не сработал рекурсивно, к сожалению, с этим вы должны явно передать тип, см пересмотров для старой версии.

Редактирование 3: Для того, чтобы это сделать без ссылки на тип, см. Это dedicated question, что я спросил. Однако это не касается других проблем, таких как круговые ссылки и свойства, которые перечисляют объекты.

+0

Эй, это сработало очень хорошо. Один из типов, которые у меня есть в Device, - это свойство Location, которое является классом. Поэтому мне пришлось вызвать item.Load (currentDevice) и item.Location.Load (currentDevice.Location) –

+0

. Мой метод также должен был загружать 'Location', но в моем коде был недостаток. См. Раздел Edit2 и новый код для обновления. Конечно, я должен отметить, что если вы теперь вызываете только 'item.Load (item.GetType(), currentDevice, true)', это означает, что событие 'PropertyChanged' для свойства' Location' не будет запущено, но в зависимости от вашего кода это может быть не так много, поскольку все свойства «Местоположение» (надеюсь) будут обновлены. Возможно, вы можете изменить код, чтобы сначала назначить новый объект Location, а затем загрузить свойства, если это проблема. –

+0

Я задал вопрос о том, как избавиться от ссылки типа, см. Edit3 в ответе. –

0

Я думаю, вы можете перегрузить = оператора и выполнить задание свойств там. то события PropertyChanged будут сгенерированы, и вы по-прежнему будете иметь тот же синтаксис, что и в первом примере.

+1

Это действительно здорово, но волшебные операторы могут быть страшными. – bryanbcook

+0

@bryanbcook ha ha может быть немного ..: D .. но я предложил, как OP хотел использовать его в упомянутом синтаксисе. что может быть сделано только этой перегрузкой –

3

В первом примере установка элемента в коллекции приведет к событию CollectionChanged, а не событию PropertyChanged отдельного элемента.

Вы можете сообщить обо всех свойствах, указав пустую строку в событие PropertyChanged. Например:

item.RaisePropertyChanged(""); 

где RaisePropertyChanged это публичный метод, который вызывает событие PropertyChanged осуществления INotifyPropertyChanged.

+0

Я бы использовал 'String.Empty', это немного чище. –

0

Класс устройства должен реализовывать интерфейс INotifyPropertyChanged. Затем для каждого свойства fire свойство notify изменило событие как обычно.

Это позволит автоматически изменить уведомление об изменении свойства.

+0

Он писал: «Поскольку у моего класса есть куча свойств, есть ли лучший способ сделать это, чем один за другим?», Даже если обстрел событий немного меньше кода, чем назначение, он все еще довольно утомительный. –

+0

Я уже реализую INotifyPropertyChanged в классе Device, следовательно, мой вопрос –

Смежные вопросы