简体   繁体   中英

Update ObservableCollection from BackgroundWorker/ProgressChanged Event

I'm writing a simple tool for troubleshooting computers. Basically its just a WPF Window with a ListBox bound to an ObservableCollection<ComputerEntry> where ComputerEntry is a simple class containing the computer host name, and Status. All the tool does is ping each compute name in the list, and if a response is received ComputerEntry.Status is updated to indicate the computer is connected to the network somewhere...

Pinging however can take some time, up to a couple seconds per computer depending on if it has to timeout or not. So I'm running the actual ping in a BackgroundWorker and using the ReportProgress method to update the UI.

Unfortunately the ObservableCollection does not seem raise the PropertyChanged event after the objects are updated. The collection does update with the new information, but the status never changes in the ListBox . Presumably because it does not know that the collection has changed.

[EDIT] Per fantasticfix, the key here is: "The ObservableCollection fires just when the list gets changed (added, exchanged, removed)." Since I was setting the properties of the object instead of modifying it, the ObservableCollection was not notifying the list of the change -- it didn't know how. After implenting INotifyPropertyChanged everything works fine. Conversly, replacing the object in the list with a new updated instance will also fix the problem. [/EDIT]

Btw I'm using C# 3.5 and I'm not in a position where I can add additional dependancies like TPL.

So as a simplified example [that won't compile without more work...]:

//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;
            }
        }
    }
}

Sorry for the jumble but its rather long with all the support code. Anyways, void Refresh() is called from a DispatcherTimer (which isn't shown), that starts RefreshWorker.RunWorkerAsync(this.ComputerList); .

I've been fighting this for a few days so I'm now to the point where I'm not actually attempting to modify the objects referenced in the ObservableCollection directly anymore. Hence the ugly looping through the ComputerList collection and setting the properties directly.

Any idea whats going on here and how I can fix it?

The observableCollection wont fire when you change properties of items which are inside of the collection (how should it even know that). The ObservableCollection fires just when the list gets changed (added, exchanged, removed).

If you want to detect the changes of the properties of the ComputerEntry the class has to Implement the INotifyPropertyChange interface (if you know MVVM, its like a lightweight MVVM pattern)

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");
                }
            }
        }
    }

Haven't used this in a long time, but don't you need something like INotifyPropertyChanged implemented?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM