Get notified when a variable changed - c#

Is there any way to get when a variable has been changed? And if so, how can I achieve this?

The "official" way to do it, is INotifyPropertyChanged. E.g. it is used by UI's (Windows Forms, WPF) to automatically refresh controls, when the data object they are bound to updates.
public class MyClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
...
}
Then you can implement properties like this
private string _name;
public string Name
{
get { return _name; }
set {
if (value != _name) {
_name = value;
OnPropertyChanged(nameof(Name));
}
}
}
You can use it like this:
var myObj = new MyClass();
myObj.PropertyChanged += MyObj_PropertyChanged;
myObj.Name = "new name";
// Clean up (e.g. in a `Dispose()` method)
myObj.PropertyChanged -= MyObj_PropertyChanged;
Assuming this event handler:
// Will be called whenever a property of `MyClass` is updated.
private void MyObj_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
// Example
if (e.PropertyName == nameof(MyClass.Name)) {
var myObj = (MyClass)sender;
//TODO: do something.
}
}

Just check in the Update void. I would argue that this is the easiest way to achieve what you want in Unity.
var varToCheckIfChanged; //This is the variable you want to know if it is still the same
var tempVar; //This variable stores the original value, and every time the varToCheckIfChanged changed, you update it.
//Gets called on Start of the scene
void Start
{
tempVar = varToCheckIfChanged;
}
//Gets called every frame
void Update
{
if(varToCheckIfChanged != tempVar)
{
Debug.Log("Variable changed!"); //Debug when the variable is updated
var tempVar = varToCheckIfChanged;
}
}

Related

Is it possible to observe a boolean till change? (C#)

So I have
boolean variableName = false
Is it possible to write a event (observeVariableName) that is "observing" variableName all the time till it changes to true and when it is on true the event does something? Like for example:
public void observeVariableName() //triggers when variableName == true
{
// do actions here
variableName = false
}
It's not possible just with having a boolean variable. You can wrap that value in a class and add an event there, if you want the event to be triggered every time the value changes, you can do it in the setter method of the property.
Try to use implement interface INotifyPropertyChanged on your class that contains your boolean.
For exemple,
public class DemoCustomer : INotifyPropertyChanged
{
private bool _selected;
public bool Selected
{
get
{
return _selected;
}
set
{
_selected = value;
NotifyPropertyChanged("Selected");
}
}
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Then, you listen this event.
var d = new DemoCustomer();
d.PropertyChanged += (s,e) => { if(e.PropertyName = "Selected" && ((DemoCustomer)s).Selected) { //do something}};
You should use property variableName.
public bool variableName {
get {
return variableName;
}
set {
variableName = value;
if (value)
// do stuff;
}
}
Look for instructions.

Handing a Deleted flag with BindingList<> in WinForms

I have an application with a front end, and instead of deleting objects right away we have a flag on every object that says whether it is supposed to be deleted, so it can be handled later. So the problem is when I am using the object in front end with a DataGrid in WinForms. When I set the Deleted flag I would like the object to not be displayed in the DataGrid, with the BindingList<> as the DataSource of the DataGrid. Is there a way to force a filter every time the DataGrid is repainted? would this be a function of the DataGrid? Or a function of the BindingList<>? For those who are more visual here is a code example. (WARNING this is a code example for conceptual purposes)
test.cs
public class Person : INotifyProperyChanged
{
public string Name { get; set; }
public int Id { get; set; }
private bool _isForDelete;
public bool IsForDelete
{
get { return _isForDelete; }
set
{
_isForDelete = value;
OnPropertyChanged("IsForDelete")
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
MyForm.cs
private BindingList<Person> _persons;
private void MyForm_Load(object sender, EventArgs e)
{
_persons = GetPersonsFromServer();
//Obviously this doesn't work, but I can dream. This is the basic idea.
_myDataGrid.DataSource = _persons.Where(x=>!x.IsForDelete);
}
private void DeleteBtn_Click(object sender, EventArgs e)
{
foreach(var row in _myDataGrid.SelectedRows)
{
var person = (Person)row.DataBoundItem;
person.IsForDelete = true;
}
}
Any suggestions?
One solution to your problem would be to loop through each row of the datagrid, get the object bound to it, check the property, and then if it is set to true suspend the binding, set the row to invisible and then resume the binding. Something like:
CurrencyManager cur = (CurrencyManager)datagrid.BindingContext[datagrid.Datasource];
cur.SuspendBinding();
datagridviewrow.Visible = false;
cur.ResumeBinding();

Object is losing event handlers after being stored / retrieved from Session

Similar to DataTable saved to Session state loses event handlers although the recommended solution there did not apply. I am not serializing Session data. Objects are being kept InProc.
I have a class called Application with these relevant bits:
public class Application {
public event PropertyChangedEventHandler PropertyChanged;
private Lazy<Asset> _asset;
public String AssetId { get; set; }
public Application() {
RestoreEventHandlers(new StreamingContext());
}
[OnDeserialized]
public void RestoreEventHandlers(StreamingContext context) {
PropertyChanged += AssetId_PropertyChanged;
PropertyChanged.Invoke(this, new PropertyChangedEventArgs("AssetId"));
}
private void AssetId_PropertyChanged(Object sender, PropertyChangedEventArgs e) {
if (e.PropertyName == "AssetId")
_asset = new Lazy<Asset>(() => new Asset(AssetId));
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] String propertyName = null) {
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
The goal is for _asset to get reset whenever the related AssetId property changes. I need to keep the AssetId and Asset records synchronized. I have verified that the event is called after an object is created the first time.
After an application is created I am putting it in Session like this:
HttpContext.Current.Session["CurrentApplication"] = application;
On subsequent requests I am getting it from Session like this:
var application = HttpContext.Current.Session["CurrentApplication"] as Application;
This is being called from a helper class which is why I need to use the static objects to get the current request context.
Later in the same method I have a line that does this, if the user has asked to change the asset on the application:
application.AssetId = assetId;
After this assignment the event handler is not invoked. If I write it this way instead it works as intended:
application.RestoreEventHandlers(new StreamingContext());
application.AssetId = assetId;
Something is causing my event handlers to become unbound. Any ideas?
I may be way out here, but is the PropertyChanged event being invoked at all, other than in RestoreEventHandlers()? I had it in mind that you need to raise it manually inside the property - see example at http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged(v=vs.110).aspx
private string _assetId;
public string AssetId
{
get
{
return _assetId;
}
set
{
_assetId = value;
OnPropertyChanged();
}
}
Of course you could just reset the _asset inside the property:
private string _assetId;
public string AssetId
{
get
{
return _assetId;
}
set
{
_assetId = value;
_asset = new Lazy<Asset>(() => new Asset(AssetId));
}
}

How to propagate property change notifications of objects within collections

Lets say I have classes like this
public class R
{
protected string name;
protected List<S> listOfObjectS;
}
public class S
{
private string name, ID;
private A objectA;
}
public class A
{
private string name;
private int count;
}
If a user has two views open, one displaying instances of R and another allowing users to modify an instance of A, I need the view of R to change when the user changes any instance of A.
If the user changes a property of an instance of A, what is the best way to propagate that change (through instances of S) so that all instances of R display the new state of A?
EDIT: Overhauling this answer to be more specific to the question since the tags show you already knew about INotifyPropertyChanged.
You need to implement INotifyPropertyChanged in class A and in class S. Make it so objectA can only be set through a property that will raise the PropertyChanged event on S whenever a property is changed in A. Example:
public class A : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set { name = value; OnPropertyChanged("Name"); }
}
private int count;
public int Count
{
get { return count; }
set { count = value; OnPropertyChanged("Count"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
... and class S...
public class S : INotifyPropertyChanged
{
private string name, ID;
private A objectA;
public A ObjectA
{
get { return objectA; }
set
{
var old = objectA;
objectA = value;
// Remove the event subscription from the old instance.
if (old != null) old.PropertyChanged -= objectA_PropertyChanged;
// Add the event subscription to the new instance.
if (objectA != null) objectA.PropertyChanged += objectA_PropertyChanged;
OnPropertyChanged("ObjectA");
}
}
void objectA_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
// Propagate the change to any listeners. Prefix with ObjectA so listeners can tell the difference.
OnPropertyChanged("ObjectA." + e.PropertyName);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
For class R, use ObservableCollection<S> instead of List<S>, and subscribe to its CollectionChanged event, and monitor when objects are added or removed to listOfObjectS. When they are added, subscribe to S's PropertyChanged events. Then updated R's view. Example:
public class R
{
protected string name;
protected System.Collections.ObjectModel.ObservableCollection<S> ListOfObjectS { get; private set; }
public R()
{
// Use ObservableCollection instead.
ListOfObjectS = new ObservableCollection<S>();
// Subscribe to all changes to the collection.
ListOfObjectS.CollectionChanged += listOfObjectS_CollectionChanged;
}
void listOfObjectS_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Remove)
{
// When items are removed, unsubscribe from property change notifications.
var oldItems = (e.OldItems ?? new INotifyPropertyChanged[0]).OfType<INotifyPropertyChanged>();
foreach (var item in oldItems)
item.PropertyChanged -= item_PropertyChanged;
}
// When item(s) are added, subscribe to property notifications.
if (e.Action == NotifyCollectionChangedAction.Add)
{
var newItems = (e.NewItems ?? new INotifyPropertyChanged[0]).OfType<INotifyPropertyChanged>();
foreach (var item in newItems)
item.PropertyChanged += item_PropertyChanged;
}
// NOTE: I'm not handling NotifyCollectionChangedAction.Reset.
// You'll want to look into when this event is raised and handle it
// in a special fashion.
}
void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName.StartsWith("ObjectA."))
{
// Refresh any dependent views, forms, controls, whatever...
}
}
}
Let's say you have a form1 where you use an instance of class R to display a list of instances from class A. You than press edit and you send the instance of that same class A from the class R instance towards the new form.
This will than be a reference to the object contained in the instance of R and therefore be updated within form2. The only thing you than have to do is refresh the instance of class A in the list of form1.
To explain: when you are calling a form or method with the an object instance of a class, this will create a reference, not a clone and therefore can be updated from the second form2.

call converter in XAML if property of a object in the observable collection changes

I have a converter that accepts an ObservableCollection as a parameter, and I'd like to re-evaluate it whenever a specific property on any item in the collection changes
For example: lets say I have bound a label to a collection of Person objects with a converter. The job of the converter is to count the number of Persons in the list that are female, and return "valid" for 1 female or "accepted" for 2. I'd like the converter to get called again anytime the Gender property on any Person object gets changed.
How can I accomplish this?
That's a classic problem you end up having if you play around WPF long enough.
I've tried various solutions, but the one that works best is to use a BindingList like so:
public class WorldViewModel : INotifyPropertyChanged
{
private BindingList<Person> m_People;
public BindingList<Person> People
{
get { return m_People; }
set
{
if(value != m_People)
{
m_People = value;
if(m_People != null)
{
m_People.ListChanged += delegate(object sender, ListChangedEventArgs args)
{
OnPeopleListChanged(this);
};
}
RaisePropertyChanged("People");
}
}
}
private static void OnPeopleListChanged(WorldViewModel vm)
{
vm.RaisePropertyChanged("People");
}
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged(String prop)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(prop));
}
}
}
Then just bind to the People collection like you would do with an ObservableCollection, except bindings will be re-evaluated when any property in its items change.
Also, please note that OnPeopleListChanged is static, so no memory leaks.
And Person should implement INotifyPropertyChanged.
A CollectionChanged event is only thrown when an item is added or removed from the collection (not when an item in the collection is changed). So the converter is not called when an item is changed.
One option:
In the Gender Property Set include logic to evaluate the collection and set a string Property that you bind the label to.
Wrote generic version of the answer from Baboon
public class ObservalbeList<T>: INotifyPropertyChanged
{
private BindingList<T> ts = new BindingList<T>();
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged( String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public BindingList<T> Ts
{
get { return ts; }
set
{
if (value != ts)
{
Ts = value;
if (Ts != null)
{
ts.ListChanged += delegate(object sender, ListChangedEventArgs args)
{
OnListChanged(this);
};
}
NotifyPropertyChanged("Ts");
}
}
}
private static void OnListChanged(ObservalbeList<T> vm)
{
vm.NotifyPropertyChanged("Ts");
}
public ObservalbeList()
{
ts.ListChanged += delegate(object sender, ListChangedEventArgs args)
{
OnListChanged(this);
};
}
}

Categories