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.
Related
I am attempting to make an undo system, where when a property on an object that implements INotifyPropertyChanged is changed, the property name and its old value is pushed onto a stack via a KeyValuePair. When the user clicks "Undo" it then pops from the stack and uses reflection to set the property's value to its old value.
The problem with this is that it calls OnPropertyChanged again, so the property and its restored value is added to the undo stack a second time. On the other hand, I still want it to call OnPropertyChanged since I want the view to update its bindings.
There's obviously something wrong with how I'm designing it, but I can't seem to figure out another way of going about it.
Here's my model
internal class MyModel : INotifyPropertyChangedExtended
{
private string testProperty1 = "";
public string TestProperty1
{
get { return testProperty1; }
set {
var oldValue = testProperty1;
testProperty1 = value;
OnPropertyChanged(nameof(TestProperty1), oldValue);
}
}
private string testProperty2 = "";
public string TestProperty2
{
get { return testProperty2; }
set {
var oldValue = testProperty2;
testProperty2 = value;
OnPropertyChanged(nameof(TestProperty2), oldValue);
}
}
public event PropertyChangedEventHandler? PropertyChanged;
public void OnPropertyChanged(string propertyName, object oldValue)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgsExtended(propertyName, oldValue));
}
}
}
Here's my INotifyPropertyChangedExtended interface
public class PropertyChangedEventArgsExtended : PropertyChangedEventArgs
{
public virtual object OldValue { get; private set; }
public PropertyChangedEventArgsExtended(string propertyName, object oldValue)
: base(propertyName)
{
OldValue = oldValue;
}
}
public class INotifyPropertyChangedExtended : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName, object oldValue)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgsExtended(propertyName, oldValue));
}
}
And here's my view model
internal class MyViewModel
{
public MyModel MyModel { get; set; } = new();
public Stack<KeyValuePair<string, object>> PropertyStateStack = new();
public RelayCommand Undo { get; set; }
public MyViewModel()
{
SetupCommands();
MyModel.PropertyChanged += MyModel_PropertyChanged;
}
private void MyModel_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
var args = e as PropertyChangedEventArgsExtended;
if (args.OldValue != null)
{
PropertyStateStack.Push(new KeyValuePair<string, object>(args.PropertyName, args.OldValue));
}
}
private void SetupCommands()
{
Undo = new RelayCommand(o =>
{
KeyValuePair<string, object> propertyState = PropertyStateStack.Pop();
PropertyInfo? property = MyModel.GetType().GetProperty(propertyState.Key);
if (property != null)
{
property.SetValue(MyModel, Convert.ChangeType(propertyState.Value, property.PropertyType), null);
}
});
}
}
EDIT: I did research the "memento pattern" but I couldn't get it to work with INotifyPropertyChanged, since as soon as I set MyModel to a backup of it the bindings to the view stopped working.
Implementing Memento or a variant is the right way. Opposed to storing the particular modifying undo action e.g., Action<T> (another good solution), Memento has a higher memory footprint (as it stores the complete object state), but allows random access to the stored states.
The key point is that when implementing Memento properly, you don't have to rely on reflection, which will only make your code slow and heavy.
The following example uses the IEditableObject interface to implement the Memento pattern (variant). The implementation supports undo and redo. The TextBox class is implementing undo/redo in a similar way using the same interface. The advantage is that you have full control over when to record the object's state. You can even cancel the ongoing modification.
This example clones the complete object to backup the state. Because objects can be quite expensive, for example when they allocate resources, it could make sense to introduce an immutable data model that actually stores the values of the public editable properties. Now, instead of cloning the complete object you would only clone the immutable data model. This can improve the performance in critical scenarios.
See the example provided by the IEditableObject link above to learn how to introduce an immutable data model that holds the object's data.
The actual undo/redo logic is encapsulated in the example's abstract StateTracker<TStateObject> class. StateTracker<TStateObject> implements the aforementioned IEditableObject and the ICloneable interface. To add convenience, StateTracker<TStateObject> also implements a custom IUndoable interface (to enable anonymous usage of the public undo/redo API).
Every class that needs to support state tracking (undo/redo) must extend the abstract StateTracker<TStateObject> to provide a ICloneable.Clone and a StateTracker.UpdateState implementation.
The following example is very basic. It allows undo and redo, but does not support random access to undo/redo states. You would have to use an index based backing store like List<T> to implement such a feature.
IUndoable.cs
Enable anonymous access to the undo/redo API.
public interface IUndoable
{
bool TryUndo();
bool TryRedo();
}
StateTracker.cs
Encapsulates the actual undo/redo logic to avoid duplicate implementations
for each type that is supposed to support undo/redo.
You can consider to add a public UndoCommand and RedoCommand to this class and let the commands invoke TryUndo and TryRedo respectively.
public abstract class StateTracker<TStateObject> : IEditableObject, IUndoable, ICloneable
{
public bool IsInEditMode { get; private set; }
private Stack<TStateObject> UndoMemory { get; }
private Stack<TStateObject> RedoMemory { get; }
private TStateObject StateBeforeEdit { get; set; }
private bool IsUpdatingState { get; set; }
protected StateTracker()
{
this.UndoMemory = new Stack<TStateObject>();
this.RedoMemory = new Stack<TStateObject>();
}
public abstract TStateObject Clone();
protected abstract void UpdateState(TStateObject state);
object ICloneable.Clone() => Clone();
public bool TryUndo()
{
if (!this.UndoMemory.TryPop(out TStateObject previousState))
{
return false;
}
this.IsUpdatingState = true;
this.StateBeforeEdit = Clone();
this.RedoMemory.Push(this.StateBeforeEdit);
UpdateState(previousState);
this.IsUpdatingState = false;
return true;
}
public bool TryRedo()
{
if (!this.RedoMemory.TryPop(out TStateObject nextState))
{
return false;
}
this.IsUpdatingState = true;
this.StateBeforeEdit = Clone();
this.UndoMemory.Push(this.StateBeforeEdit);
UpdateState(nextState);
this.IsUpdatingState = false;
return true;
}
// Start recording the changes
public void BeginEdit()
{
if (this.IsInEditMode || this.IsUpdatingState)
{
return;
}
this.IsInEditMode = true;
// Create the snapshot before the instance is changed
this.StateBeforeEdit = Clone();
}
// Abort recording the changes
public void CancelEdit()
{
if (!this.IsInEditMode)
{
return;
}
// Restore the original state
UpdateState(this.StateBeforeEdit);
this.IsInEditMode = false;
}
// Commit recorded changes
public void EndEdit()
{
if (!this.IsInEditMode || this.IsUpdatingState)
{
return;
}
// Commit the snapshot of the original state after the instance was changed without cancellation
this.UndoMemory.Push(this.StateBeforeEdit);
this.IsInEditMode = false;
}
}
MyModel.cs
public class MyModel : StateTracker<MyModel>, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public MyModel()
{
}
// Copy constructor
private MyModel(MyModel originalInstance)
{
// Don't raise PropertyChanged to avoid the loop of death
this.testProperty1 = originalInstance.TestProperty1;
this.testProperty2 = originalInstance.TestProperty2;
}
// Create a deep copy using the copy constructor
public override MyModel Clone()
{
var copyOfInstance = new MyModel(this);
return copyOfInstance;
}
protected override void UpdateState(MyModel state)
{
// UpdateState() is called by the StateTracker
// which internally guards against the infinite loop
this.TestProperty1 = state.TestProperty1;
this.TestProperty2 = state.TestProperty2;
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
private string testProperty1;
public string TestProperty1
{
get => this.testProperty1;
set
{
this.testProperty1 = value;
OnPropertyChanged();
}
}
private string testProperty2;
public string TestProperty2
{
get => this.testProperty2;
set
{
this.testProperty2 = value;
OnPropertyChanged();
}
}
}
Example
The following example stores the state of a TextBox, that binds to a MyModel instance. When the TextBox receives focus, the MyModel.BeginEdit method is called to start recording the input. When the TextBox loses focus the recorded state is pushed onto the undo stack by calling the MyModel.EndEdit method.
MainWindow.xaml
<Window>
<Window.DataContext>
<local:MyModel />
</Window.DataContext>
<StackPanel>
<Button Content="Undo"
Click="OnUndoButtonClick" />
<Button Content="Redo"
Click="OnRedoButtonClick" />
<TextBox Text="{Binding TestProperty1, UpdateSourceTrigger=PropertyChanged}"
GotFocus="OnTextBoxGotFocus"
LostFocus="OnTextBoxLostFocus" />
</StackPanel>
</Window>
MainWindow.xaml.cs
Because of the defined interfaces we can handle undo/redo without knowing the actual data type.
private void OnTextBoxGotFocus(object sender, RoutedEventArgs e)
=> ((sender as FrameworkElement).DataContext as IEditableObject).BeginEdit();
private void OnTextBoxLostFocus(object sender, RoutedEventArgs e)
=> ((sender as FrameworkElement).DataContext as IEditableObject).EndEdit();
private void OnUndoButtonClick(object sender, RoutedEventArgs e)
=> _ = ((sender as FrameworkElement).DataContext as IUndoable).TryUndo();
private void OnRedoButtonClick(object sender, RoutedEventArgs e)
=> _ = ((sender as FrameworkElement).DataContext as IUndoable).TryRedo();
An alternative flow could be that the MyModel class internally calls BeginEdit and EndEdit inside the relevant property setters (before accepting the new value and after accepting the new value). In case of the TextBox, the advantage of this solution is that it allows to record every single input.
In this scenario, the GotFocus and LostFocus event handlers previously defined on the TextBox (example above) are not needed and related code must be removed:
MyModel.cs
public class MyModel : StateTracker<MyModel>, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public MyModel()
{
}
// Copy constructor
private MyModel(MyModel originalInstance)
{
// Don't raise PropertyChanged to avoid the loop of death
this.testProperty1 = originalInstance.TestProperty1;
this.testProperty2 = originalInstance.TestProperty2;
}
// Create a deep copy using the copy constructor
public override MyModel Clone()
{
var copyOfInstance = new MyModel(this);
return copyOfInstance;
}
protected override void UpdateState(MyModel state)
{
// UpdateState() is called by the StateTracker
// which internally guards against the infinite loop
this.TestProperty1 = state.TestProperty1;
this.TestProperty2 = state.TestProperty2;
}
private void RecordPropertyChange<TValue>(ref TValue backingField, TValue newValue)
{
BeginEdit();
backingField = newValue;
EndEdit();
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
private string testProperty1;
public string TestProperty1
{
get => this.testProperty1;
set
{
RecordPropertyChange(ref this.testProperty1, value);
OnPropertyChanged();
}
}
private string testProperty2;
public string TestProperty2
{
get => this.testProperty2;
set
{
RecordPropertyChange(ref this.testProperty2, value);
OnPropertyChanged();
}
}
}
Remarks
If extending StateTracker is not an option (e.g., because it would introduce a multi-inheritance issue), you can always make use of composition (for example add a private property of type StateTracker to your undoable model to replace inheritance).
Just create a new class that extends StateTracker to implement the abstract members. Then define a private property of this new type in your undoable model. Now, let the model reference this private property to access the undo/redo API.
While composition is to be favored, this example chooses inheritance as this concept feels more natural to most. It may helps to understand the basic idea.
I have a class that inherits from ObservableCollection<T>. In that class I have a method that changes the collection internally and for which I'd like to suppress CollectionChanged events.
public class ContentBlockList : ObservableCollection<int> {
public void SomeMethod() {
var handlers = CollectionChanged.GetInvocationList();
foreach (NotifyCollectionChangedEventHandler handler in handlers) {
CollectionChanged -= handler;
}
// do stuff here
foreach (NotifyCollectionChangedEventHandler handler in handlers) {
CollectionChanged += handler;
}
}
}
Intuitively it seems like this should work since I'm accessing the event from within its containing object. Unfortunately, the compiler says
The event 'ObservableCollection.CollectionChanged' can only
appear on the left hand side of += or -=
I can get the code to work if I override both CollectionChanged and OnCollectionChanged(), essentially replacing the .NET versions with copies of my own. However, having to do something like that makes me suspicious that I'm ignoring some reason why doing this is a bad idea in the first place. Thanks for any thoughts on this.
As unsubscribing and re-subscribing to an event is a relatively (not really painful but I don't know how many subscribers there is likely to be) slow process I would recommend that you look into overriding both the the OnCollectionChanged and OnPropertyChanged methods for the base ObservableCollection.
So have something that resembles:
public class ContentBlockList : ObservableCollection<int>
{
private bool internallyUpdating;
public void SomeMethod()
{
this.internallyUpdating = true;
// Do Stuff (Add to base collection)
this.internallyUpdating = false;
this.OnPropertyChanged(new PropertyChangedEventArgs(#"Count");
this.OnPropertyChanged(new PropertyChangedEventArgs(#"Item[]");
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if(this.internallyUpdating)
{
return;
}
base.OnCollectionChanged(e);
}
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
if(this.internallyUpdating)
{
return;
}
base.OnPropertyChanged(e);
}
}
What this allows for is the ability to suppress the events being raised while you update internally but doing this in a way that means you do not have to unsubscribe and resubscribe to events.
When adding to this collection normally (i.e. with contentBlockList.Add(1)) you'll fall straight through to calling the base event. But when you are trying to update internally you'll suppress these events until you have finished.
I'd say this is both more efficient in terms of performance but also much neater code than what you were looking at.
On a last note I'd also say that the NotifyCollectionChangedEventAction that you provide is Reset. You've probably done quite a big change to the collection and to handle it you'll want any subscriber to have to refresh their look on the collection, be it a control in a WPF view or even another class that uses the collection.
better use this :
public class ContentBlockList : ObservableCollection<int>
{
ContentBlockList()
{
this.CollectionChanged += ContentBlockList_CollectionChanged;
}
void ContentBlockList_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
}
}
if you maintain your code try this
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ContentBlockList pp = new ContentBlockList();
pp.CollectionChanged += pp_CollectionChanged;
pp.CollectionChanged += pp_CollectionChanged1;
pp.Add(11112);
pp.SomeMethod();
}
void pp_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
}
void pp_CollectionChanged1(object sender, NotifyCollectionChangedEventArgs e)
{
}
}
public class ContentBlockList : ObservableCollection<int>
{
public void SomeMethod()
{
var handlers = CollectionChanged.GetInvocationList();
foreach (NotifyCollectionChangedEventHandler handler in handlers)
{
CollectionChanged -= handler;
}
// do stuff here
foreach (NotifyCollectionChangedEventHandler handler in handlers)
{
CollectionChanged += handler;
}
}
public override event System.Collections.Specialized.NotifyCollectionChangedEventHandler CollectionChanged;
}
As far as I understood you need to interrupt firing of CollectionChanged to do some work silently. So you can create boolean field like __FireCollectionChanged and then override OnCollectionChanged() to do:
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (__FireCollectionChanged)
base.OnCollectionChanged(e);
}
Then you can control whether event is fired by that boolean field.
And answering the actual question: you can't use the invocation list directly, because the event is not the delegate type field. It's basically just two methods add and remove for subscribe/unsubscribe behavior. Underlying delegate field is created behind the scenes and you typically don't want to use it.
Can someone tell me how can I have a feature in my UserControl, that can let the host windowsform know what is the control is doing?
For example my usercontrol has a filebrowser, and if user uses this file browser to open a file I want in the statusstrip bar of my form to write "Loading file(s)".
Will this require using events? if so, how can I have a single event inside usercontrol to report anything it does (then I guess I have to call this event in all methods in the usercontrol).
Simple
Yes, expose an event on the user control that the Form can subscribe to. You should use the standard event pattern:
class MyUserControl : UserControl
{
public event EventHandler<EventArgs> FileOpened;
protected virtual void OnFileOpened(EventArgs e)
{
EventHandler<EventArgs> handler = FileOpened;
if (handler != null)
handler(this, e);
}
}
Then when the file is opened you call OnFileOpened(EventArgs.Empty) which fires the event.
With custom EventArgs
Now the Form probably needs to know what file was opened. You could expose a property on the user control that the Form can use to find out, or you can provide that information in your event like so:
public class FileOpenedEventArgs : EventArgs
{
private string filename;
public FileOpenedEventArgs(string filename)
{
this.filename = filename;
}
public string Filename { get { return filename; } }
}
class MyUserControl : UserControl
{
public event EventHandler<FileOpenedEventArgs> FileOpened;
protected virtual void OnFileOpened(FileOpenedEventArgs e)
{
EventHandler<FileOpenedEventArgs> handler = FileOpened;
if (handler != null)
handler(this, e);
}
}
Then you fire the event with OnFileOpened(new FileOpenedEventArgs(filename)).
Optimal
When you create an event handler public event delegate Name;, you are allocating storage for the delegate on your object. Objects (especially Controls) often have a huge number of events that are never subscribed to. That's a whole lot of allocated storage not being used. There's an optimization built into the framework in the form of a EventHandlerList. This handy object stores event handlers only when they are actually used. All System.Windows.Forms.Control objects derive from System.ComponentModel.Component and it already provides an (protected) EventHandlerList that you can access in your derived Control.
To use it, you first create a static object that uniquely identifies your event, and then you provide the add {} and remove {} methods manually. Like so:
class MyUserControl : UserControl
{
private static readonly object FileOpenedKey = new Object();
public event EventHandler<FileOpenedEventArgs> FileOpened
{
add { Events.AddHandler(FileOpenedKey, value); }
remove { Events.RemoveHandler(FileOpenedKey, value); }
}
protected virtual void OnFileOpened(FileOpenedEventArgs e)
{
var handler = (EventHandler<FileOpenedEventArgs>)Events[FileOpenedKey];
if (handler != null)
handler(this, e);
}
}
Yes, you will need to create an event and subscribe to it. One suggestion following the standard pattern for events:
enum ControlStatus {Idle, LoadingFile, ...}
class StatusChangedEventArgs : EventArgs
{
public ControlStatus Status {get; private set;}
public StatusChangedEventArgs(ControlStatus status)
: base()
{
this.Status = status;
}
}
partial class MyControl : UserControl
{
public ControlStatus Status {get; private set;}
public event EventHandler<StatusChangedEventArgs> StatusChanged;
protected virtual void OnStatusChanged(StatusChangedEventArgs e)
{
var hand = StatusChanged;
if(hand != null) hand(this, e);
}
void LoadFiles()
{
...
Status = ControlStatus.LoadingFiles;
OnStatusChanged(new StatusChangedEventArgs(this.Status));
...
Status = ControlStatus.Idle;
OnStatusChanged(new StatusChangedEventArgs(this.Status));
}
}
partial class MyHostWindowsForm : Form
{
public MyHostWindowsForm()
{
var ctl = new MyControl();
...
ctl.StatusChanged += ctl_StatusChanged;
}
void ctl_StatusChanged(object sender, StatusChangedEventArgs e)
{
switch(e.Status)
{
case ControlStatus.Idle:
statusStripBar.Text = null;
break;
case ControlStatus.LoadingFiles:
statusStripBar.Text = "Loading file(s)";
break;
...
}
}
}
I want my C# code to call an event whenever a value is assigned to my object.
How exactly would I need to go about that?
class MyClass {
ManualResetEvent mre;
public MyClass() {
mre = new ManualResetEvent(false);
Data = null;
}
public object Data { get; set; }
void DataSet(object sender, EventArgs e) {
Console.WriteLine("object Data has been set.");
mre.Set();
}
}
Delegates don't seem to be what I need. An event, maybe? How would I write such an event, if so?
MyClass mc;
void processA() {
mc = new MyClass();
mc.Data = GetDataFromLongProcess();
}
private object data;
public object Data {
get { return data;}
set {
if(value != data) {
data = value;
OnDataChanged();
}
}
}
protected virtual void OnDataChanged() {
EventHandler handler = DataChanged;
if(handler != null) handler(this, EventArgs.Empty);
}
public event EventHandler DataChanged;
then hook any code to the DataChanged event. For example:
MyClass mc = ...
mc.DataChanged += delegate {
Console.WriteLine("new data! wow!");
};
If you want to fire an event when your property is set, you would do something like this:
public event Action OnDataChanged;
protected object _data = null;
public object Data
{
get { return _data; }
set
{
_data = value;
if(OnDataChanged != null)
OnDataChanged();
}
}
Then you would simply wire up event handlers to your object like so:
mc = new MyClass();
mc.OnDataChanged += delegate() { Console.WriteLine("It changed!"); };
mc.Data = SomeValue();
I think you're on the right track with an event-based model. Also take a look at the Observer pattern (which is the basis for .Net delegates and events underneath it all, as I understand):
http://www.dofactory.com/Patterns/PatternObserver.aspx
But the bottom line, as the other useful answer so far (Mr. Gravell's implementation) indicates, you're going to have to have code IN the setter to get it hooked up. The only alternative would be to poll the value for changes, which just smells bad to me.
you could implement INotifyPropertyChanged (this is more or less a event) or you could take your class a Action (Trigger) and call this, whenn the property changed.
Just don't use automatic properties but a concrete setter and call your event/trigger from there.
Conceptually, you would define an event in your class, and in your property set blocks, you would invoke the event with the necessary arguments to determine what just happened.
public event SomeDelegateThatTakesIntAsParameter myEvent;
void SetData(int data)
{
if(myEvent!= null)
myEvent(data)
}
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?