Update ObservableCollection from BackgroundWorker/ProgressChanged Event - c#

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?

Related

How to know when a string changed and do something?

I apologize in advance for a noob question.
I have a string which is changing without my control (another party's string) lets call it firstString,
every time this string changes I need to carry out an action in my program.
so I made a class that implements the "INotifyPropertyChanged" Interface,
and created a string in it with a property, lets call it secondString,
and on the main method of the form I've created a "PropertyChangedEventHandler" and an event method which shows a message box with the value of firstString.
everything works well if I manually test and change firstString by clicking a button to change its value and I get a message box with firstString's value after it went through secondString's Property, I set it like this:
SecondString(this is a property) = firstString;
but the thing is firstString is changing by itself, and I don't have control over it, so if it is set by code to equal secondString's property what happens is that it only works for the first time that it runs.
so now every time secondString's property is changing, the event fires and that part is working OK.
but I need to set secondString's value with firstString's value automatically every time that firstString's value changes. and I kind of figure that INotifyPropertyChanged should have somehow worked here for this part as well but I can't understand how.
so I was trying to figure our how to "bind" string's A value to secondString's property, and got into DataBinding, but I couldn't find any example to bind two strings together, only about binding to or from a control.
EDIT: here is a code to demo, I think the key that I've missed to note is that firstString is a string I get by another party's class library.
Using AnotherPartyLibrary;
FirstClass fc;
public Form1()
{
InitializeComponent();
fc = new FirstClass();
fc.PropertyChanged += new PropertyChangedEventHandler(fc_PropertyChanged);
fc.SecondString = AnotherPartyLibrary.firstString;
this.Disposed += new EventHandler(Form1_Disposed);
}
void fc_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
MessageBox.Show("Something changed!" + e.PropertyName);
}
public class firstClass :INotifyPropertyChanged
{
private string secondString = string.Empty;
public string SecondString
{
get { return this.secondString; }
set
{
this.secondString = value;
NotifyPropertyChanged("SecondString");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
How is this problem is usually solved? Many Thanks in advance!
Edit: can anybody offer another solution other than what a.azemia has offered?Thanks again!
Ray.
Based on the assumption that you do not have control over firstString then you can use BackgroundWorker to monitor firstString value and update SecondString
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += (s, e) =>
{
while (true)
{
if (!fc.SecondString.Equals(AnotherPartyLibrary.firstString))
{
fc.SecondString = AnotherPartyLibrary.firstString;
}
Thread.Sleep(1000);
}
};
bw.RunWorkerAsync();
You can also use Microsoft TPL to achieve the same result
public async void YourMethodName()
{
await Task.Run(() =>
{
while (true)
{
if (!fc.SecondString.Equals(AnotherPartyLibrary.firstString))
{
fc.SecondString = AnotherPartyLibrary.firstString;
}
Thread.Sleep(1000);
}
});
}
However, if you can update the code for firstString then you should implement INotifyPropertyChanged on firstString.

C# - Check if ObservableCollection CollectionChanged Event has finished executing

I have a XML Serializable List, which is of type ObservableCollection. I have an event implemented so that any time a change is made to this collection, it should be serialized, then deserialized.
There is an external class (Weapons Editor) that can add, delete, and modify weapons. As soon as the delete button is pressed, it comes to this WeaponsDB class to gather the ObservableCollection. However, it does its refresh of what's inside the ObservableCollection before my CollectionChanged Event has executed.
How can I ensure that the CollectionChanged event has fully executed and everything is up to date before any other class obtains the most updated data?
public class WeaponDatabase
{
[XmlArray("Weapons"), XmlArrayItem(typeof(Weapon), ElementName = "Weapon")]
public ObservableCollection<Weapon> Weapons = new ObservableCollection<Weapon>();
private string path = #"Inventory\WeaponsDB.xml";
public WeaponDatabase()
{
DeserializeData();
Weapons.CollectionChanged += Weapons_CollectionChanged;
}
void Weapons_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
SerializeData();
DeserializeData();
}
public void SerializeData()
{
XmlSerializer Serializer = new XmlSerializer(typeof(ObservableCollection<Weapon>));
TextWriter textWriter = new StreamWriter(path);
Serializer.Serialize(textWriter, Weapons);
textWriter.Close();
}
public void DeserializeData()
{
XmlSerializer Serializer = new XmlSerializer(typeof(ObservableCollection<Weapon>));
StreamReader reader = new StreamReader(path);
Weapons = (ObservableCollection<Weapon>)Serializer.Deserialize(reader);
reader.Close();
}
}
I haven't used an ObservableCollection yet so there's the possibility I'm a touch off-base, but hey.
Likely, your weakness is that you're viewing this backwards. CollectionChanged is what should be fired to let your external object accessing the collection know that the collection is updated, not to be used to update the actual collection (via dumping it out to a file or rebuilding it from an XML file).
To wit:
This external class should have a setup something along the lines of this:
public class WeaponsEditor
{
private WeaponsDatabase DB;
public WeaponsEditor()
{
DB = new WeaponsDatabase();
DB.CollectionChanged += CollectionChanged;
}
private object CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
// respond to the updated database
}
}
Basically, the CollectionChanged event is for other objects to respond to a changed collection, not for your class to make its updates. If the Weapons Editor object is manipulating the collection directly, that's your problem; you should have it call a custom method in WeaponsDatabase, which will forward the request to the collection and then call the Serialize/Deserialize methods immediately after.
You need to add the INotifyPropertyChanged interface to WeaponDatabase (I say use that interface instead of a custom event so things like BindingSources will automatically just work with it)
public class WeaponDatabase : INotifyPropertyChanged
{
[XmlArray("Weapons"), XmlArrayItem(typeof(Weapon), ElementName = "Weapon")]
public ObservableCollection<Weapon> Weapons {get; private set;}
private string path = #"Inventory\WeaponsDB.xml";
public event PropertyChangedEventHandler PropertyChanged;
public WeaponDatabase()
{
Weapons = new ObservableCollection<Weapon>();
DeserializeData();
Weapons.CollectionChanged += Weapons_CollectionChanged;
}
private void RaiseWeaponsChanged()
{
var temp = this.PropertyChanged;
if(temp != null)
temp(this, new PropertyChangedEventArgs("Weapons"));
}
void Weapons_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
SerializeData();
DeserializeData();
RaiseWeaponsChanged()
}
//Snip
}
Now other classes can subscribe to the WeaponDatabase.PropertyChanged event and be notified when the collection changed and the deserialization is complete.

Updating dependent properties using MVVM

Some properties on my viewmodel:
public ObservableCollection<Task> Tasks { get; set; }
public int Count
{
get { return Tasks.Count; }
}
public int Completed
{
get { return Tasks.Count(t => t.IsComplete); }
}
What's the best way to update these properties when Tasks changes?
My current method:
public TaskViewModel()
{
Tasks = new ObservableCollection<Task>(repository.LoadTasks());
Tasks.CollectionChanged += (s, e) =>
{
OnPropertyChanged("Count");
OnPropertyChanged("Completed");
};
}
Is there a more elegant way to do this?
With respect to Count, you don't have to do this at all. Simply bind to Tasks.Count and your bindings will get notified of the change by the ObservableCollection.
Completed is a different story, because this is outside of ObservableCollection. Still, from the level of the abstraction/interface, you really want Completed to be a property of that Tasks collection.
For this, I think a better approach would be to create "sub" view-model for your Tasks property:
public class TasksViewModel : ObservableCollection<Task>
{
public int Completed
{
get { return this.Count(t => t.IsComplete); }
}
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if(e.PropertyName == "Count") NotifyCompletedChanged();
}
protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
base.OnCollectionChanged(e);
NotifyCompletedChanged();
}
void NotifyCompletedChanged()
{
OnPropertyChanged(_completedChangedArgs);
}
readonly PropertyChangedEventArgs _completedChangedArgs = new PropertyChangedEventArgs("Completed");
}
This gives you all of the benefits of the ObservableCollection, and effectively makes the Completed property part of it. We still haven't captured only the cases where the number of completed items truly changes, but we have reduced the number of redundant notifications somewhat.
Now the viewmodel just has the property:
public TasksViewModel Tasks { get; set; }
…and you can bind to Tasks, Tasks.Count, and Tasks.Completed with ease.
As an alternative, if you would rather create these other properties on the "main" view-model, you can take this notion of a subclassed ObservableCollection<T> to create one with some method where you can pass in an Action<string> delegate, which would represent raising a property change notification on the main view-model, and some list of property names. This collection could then effectively raise the property change notifications on the view-model:
public class ObservableCollectionWithSubscribers<T> : ObservableCollection<T>
{
Action<string> _notificationAction = s => { }; // do nothing, by default
readonly IList<string> _subscribedProperties = new List<string>();
public void SubscribeToChanges(Action<string> notificationAction, params string[] properties)
{
_notificationAction = notificationAction;
foreach (var property in properties)
_subscribedProperties.Add(property);
}
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
NotifySubscribers();
}
protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
base.OnCollectionChanged(e);
NotifySubscribers();
}
void NotifySubscribers()
{
foreach (var property in _subscribedProperties)
_notificationAction(property);
}
}
You could even leave the property type as ObservableCollection<Task>.
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
var tasks = new ObservableCollectionWithSubscribers<Task>();
tasks.SubscribeToChanges(Notify, "Completed");
Tasks = tasks;
}
public ObservableCollection<Task> Tasks { get; private set; }
public int Completed
{
get { return Tasks.Count(t => t.IsComplete); }
}
public event PropertyChangedEventHandler PropertyChanged;
void Notify(string property)
{
var handler = PropertyChanged;
if(handler != null) handler(this, new PropertyChangedEventArgs(property));
}
}
Looks rather elegant to me. I really don't know how you'd make that more succinct.
(How odd to write an answer like this. If somebody actually comes up with something more elegant, I might delete this.)
Okay, I noticed one thing, unrelated to the original question: Your Tasks property has a public setter. Make it private set;, or you'll need to implement the set with a backing field so you can remove the delegate on the previous instance, replace and wire up the new one, and do OnPropertyChanged with "Tasks", "Count", and "Completed". (And seeing how Tasks is set in the constructor, I'm guessing private set; is the better option.)
Doesn't make notifying about Count and Completed more elegant, but it fixes a bug.
And many MVVM frameworks get the property name from a lambda, so that instead of OnPropertyChanged("Count"), you can write OnPropertyChanged(() => Count) so that it will follow renames done with the help of refactoring tools. I don't think renaming happens all that often, though, but it does avoid some string literals.

Object Property changes, but bound ListBox does not update

I have a List of complex objects, and a ListBox that is bound to this List with BindingSource. When user selects items in listbox, he can edit it's properties via PropertyGrid and it's text separately via TextBox. When property is changed via PropertyGrid, BindingSource's CurrentItemChanged is called, but I'm having problems with updating DataBinding when users just edits TextBox
Here is some code to explain my situation better:
class Song
{
public string Title{get;set}
[Browsable(false)]
public string Text{get;set;}
...
}
class SongBook
{
public List Songs {get;set;}
...
}
// Initialization: we are setting ListBox's DataSource to songBookBindingSource
private void InitializeComponent()
{
...
this.allSongsList.DataSource = this.songBookBindingSource;
...
}
// We create new SongBook object, and set BindingSource's DataSource to
// list of songs in songbook
private void OpenSongBook()
{
...
currentSongBook.Deserialize( path );
songBookBindingSource.DataSource = currentSongBook.Songs;
}
// When user selects a song in ListBox, we try to edit it's properties
private void allSongsList_SelectedValueChanged(object sender, EventArgs e)
{
...
songProps.SelectedObject = allSongsList.SelectedItem;
songTextEdit.Text = (allSongsList.SelectedItem as Song).Text;
}
// This get called whenever user changes something in TextBox.
// If it does, we want to mark song as Unsaved and refresh
// ListBox, so it would display a nice little "*" next to it!
private void songTextEdit_TextChanged(object sender, EventArgs e)
{
currentSong.Text = editSongTextBox.Text;
currentSong.Unsaved = true;
// As far as I understand, this SHOULD make ListBox bound to songBookBindingSource
// update its items. But it does not! How do I make it understand that data changed?
songBookBindingSource.RaiseListChangedEvents = true;
// And if I do this, ListBox DOES gets updated, but something also inserts A COPY OF CURRENT ITEM
// into it. If I select it, allSongsList.SelectedItem throws "Out of bounds" exception. As far
// as I understand, it gets added only to ListBox, but NOT to underlying List. But why is it
// even getting added at all?!
// songBookBindingSource.ResetCurrentItem();
}
I feel like .NET Framework hates me :)
Your objects need to implement INotifyPropertyChanged, that way the binding will refresh when a property changes:
class Song : INotifyPropertyChanged
{
private string _title;
public string Title
{
get { return _title; }
set
{
_title = value;
OnPropertyChanged("Title");
}
}
private string _text;
[Browsable(false)]
public string Text
{
get { return _text; }
set
{
_text = value;
OnPropertyChanged("Text");
}
}
...
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}

how to update listbox items with INotifyPropertyChanged

I have a listbox which is databound to a collection of objects.
I want to modify the way the items are displayed to show the user which one of these objects is the START object in my program.
I tried to do this the following way, but the listbox does not automatically update.
Invalidating the control also didn't work.
The only way I can find is to completely remove the databindings and add it back again. but in my case that is not desirable.
Is there another way?
class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _name;
public string Name
{
get
{
if (PersonManager.Instance.StartPerson == this)
return _name + " (Start)";
return _name;
}
set
{
_name = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Name"));
}
}
public Person(string name)
{
Name = name;
}
}
This is the class wich manages the list and the item that is the start
class PersonManager
{
public BindingList<Person> persons { get; set; }
public Person StartPerson { get; set; }
private static PersonManager _instance;
public static PersonManager Instance
{
get
{
if (_instance == null)
{
_instance = new PersonManager();
}
return _instance;
}
}
private PersonManager()
{
persons = new BindingList<Person>();
}
}
In the form I use the following code
private void button1_Click(object sender, EventArgs e)
{
PersonManager.Instance.StartPerson = (Person)listBox1.SelectedItem;
}
I'm pretty sure that the problem is that, when you do this, you're effectively making the Person.Name properties "get" accessor change the value (and act like a set accessor as far as the UI is concerned).
However, there is nothing that's updating the bindings to say that this is happening. If PropertyChanged got called when you set start, I believe this would update.
It's clunky, but the way you have it written, I believe you could add this and make it work (NOTE: I didn't test this, so it ~may~ have issues):
private void button1_Click(object sender, EventArgs e)
{
Person newStart = (Person)listBox1.SelectedItem;
if (newStart != null)
{
PersonManager.Instance.StartPerson = newStart;
newStart.Name = newStart.Name; // Dumb, but forces a PropertyChanged event so the binding updates
}
}

Categories

Resources