#c# #wpf
#c# #wpf
Вопрос:
Я пишу простой инструмент для устранения неполадок в компьютерах. По сути, это просто окно WPF с ListBox
привязкой к, ObservableCollection<ComputerEntry>
где ComputerEntry
— простой класс, содержащий имя хоста компьютера и статус. Все, что делает инструмент, это проверяет каждое имя вычисления в списке, и, если получен ответ, ComputerEntry.Status
обновляется, указывая, что компьютер где-то подключен к сети…
Однако проверка пинга может занять некоторое время, до пары секунд на компьютер, в зависимости от того, установлен тайм-аут или нет. Итак, я запускаю фактический ping в BackgroundWorker
и использую ReportProgress
метод для обновления пользовательского интерфейса.
К сожалению, ObservableCollection
похоже, что это не вызывает PropertyChanged
событие после обновления объектов. Коллекция обновляется с новой информацией, но статус никогда не меняется в ListBox
. Предположительно, потому что он не знает, что коллекция изменилась.
[РЕДАКТИРОВАТЬ] Согласно fantasticfix, ключевым моментом здесь является: «ObservableCollection запускается только тогда, когда список изменяется (добавляется, обменивается, удаляется)». Поскольку я устанавливал свойства объекта вместо того, чтобы изменять его, ObservableCollection не уведомлял список об изменении — он не знал как. После внедрения INotifyPropertyChanged
все работает нормально. И наоборот, замена объекта в списке новым обновленным экземпляром также устранит проблему. [/ РЕДАКТИРОВАТЬ]
Кстати, я использую C # 3.5, и я не в том положении, когда я могу добавить дополнительные зависимости, такие как TPL.
Итак, в качестве упрощенного примера [который не будет компилироваться без дополнительной работы …]:
//Real one does more but hey its an example...
public class ComputerEntry
{
public string ComputerName { get; private set; }
public string Status { get; set; }
public ComputerEntr(string ComputerName)
{
this.ComptuerName = ComputerName;
}
}
//...*In Window Code*...
private ObservableCollection<ComputerEntry> ComputerList { get; set; }
private BackgroundWorker RefreshWorker;
private void Init()
{
RefreshWorker = new BackgroundWorker();
RefreshWorker.WorkerReportsProgress = true;
RefreshWorker.DoWork = new DoWorkEventHandler(RefreshWorker_DoWork);
RefreshWorker.ProgressChanged = new ProgressChangedEventHandler(RefreshWorker_ProgressChanged);
}
private void Refresh()
{
RefreshWorker.RunWorkerAsync(this.ComputerList);
}
private void RefreshWorker_DoWork(object sender, DoWorkEventArgs e)
{
List<ComputerEntry> compList = e as List<ComputerEntry>;
foreach(ComputerEntry o in compList)
{
ComputerEntry updatedValue = new ComputerEntry();
updatedValue.Status = IndicatorHelpers.PingTarget(o.ComputerName);
(sender as BackgroundWorker).ReportProgress(0, value);
}
}
private void RefreshWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
ComputerEntry updatedValue = new ComputerEntry();
if(e.UserState != null)
{
updatedValue = (ComputerEntry)e.UserState;
foreach(ComputerEntry o in this.ComputerList)
{
if (o.ComputerName == updatedValue.ComputerName)
{
o.Status = updatedValue.Status;
}
}
}
}
Извините за беспорядок, но он довольно длинный со всем кодом поддержки. В любом случае, void Refresh()
вызывается из DispatcherTimer (который не показан), который запускается RefreshWorker.RunWorkerAsync(this.ComputerList);
.
Я боролся с этим в течение нескольких дней, так что теперь я дошел до того, что на самом деле больше не пытаюсь напрямую изменять объекты, на которые ссылаются в ObservableCollection
. Отсюда уродливый цикл по коллекции ComputerList и настройка свойств напрямую.
Есть идеи, что здесь происходит и как я могу это исправить?
Ответ №1:
ObservableCollection не будет срабатывать при изменении свойств элементов, которые находятся внутри коллекции (как он вообще должен это знать). ObservableCollection запускается только тогда, когда список изменяется (добавляется, обменивается, удаляется).
Если вы хотите обнаружить изменения свойств ComputerEntry, класс должен реализовать интерфейс INotifyPropertyChange (если вы знаете MVVM, это похоже на облегченный шаблон MVVM)
public class ComputerEntry : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
private void RaisePropertyChanged(String propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private String _ComputerName;
public String ComputerName
{
get
{
return _ComputerName;
}
set
{
if (_ComputerName != value)
{
_ComputerName = value;
this.RaisePropertyChanged("ComputerName");
}
}
}
}
Комментарии:
1. Возникает глупая ошибка. Спасибо, что ваша заглушка еще до редактирования помогла мне это исправить!
Ответ №2:
Давно этим не пользовался, но разве вам не нужно реализовать что-то вроде INotifyPropertyChanged?