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

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

Related

Get notified when a variable changed

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

C# - Possible to detect when DataBound?

I would like to cache some UI-specific properties only at the time an object is DataBound, if it gets bound.
Say I have
public interface IAmA<T> ()
{
T Value {get;set;}
}
public class MyString : IAmA<string>, INotifyPropertyChanged {
private string _value = String.Empty;
public virtual string Value
{
get
{
return this._value;
}
set
{
this._value = value;
OnPropertyChanged();
}
}
public virtual event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
Is it possible to implement an action or event to fire at the time the object is databound?
event Action OnDataBound { ... }
So I can detect, say, when
MyString myStrObj = new MyString("text");
myStrObj.OnDataBound += CacheUIElements();
Do.OtherStuff();
otherObj.DataSource = myStrObj; //causes CacheUIElements() to fire
You can very well do it in the property setter and call the Delegate.
In above case it will be for the OtherObj.DataSource. Because you want the Action to get triggered when you are setting the datasource. So it should be in the setter of whichever property where you want the action to trigger.
class OtherClass
{
DataSource
{
get{return _dataSource}
set
{
_dataSource = value;
OnPropertyChanged("DataSource");
OnDataBound();
}
}
}
If not, assuming OtherClass Implements OnPropertyChanged and triggers for DataSource like this, If its a framework element for sure it will implement NotifyPropertyChanged for DataSource because only then UI will refresh with the changes.
class OtherClass: INotifyPropertyChanged
{
DataSource
{
get{return _dataSource}
set
{
_dataSource = value;
OnPropertyChanged("DataSource");
}
}
}
Then you can subscribe to this event and check against the property name and trigger the OnDataBound(); e.g:
otherClassObj.PropertyChanged += OtherClassPropertyChanged()
private void OtherClassPropertyChanged(Obj sender, PropertyChangedEventargs e)
{
if(e.PropertyName = "DataSource")
{
OnDataBound();
}
}

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();

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.

Can you display the first element of a ListBox rather than create a data template for xaml

The following Binding is on an IEnumerable called activity. The thing is, in my xaml... I only want to output the first element in the array. can you do something along these lines:
<Run Text="{Binding activity[0].created_by.name}"></Run>
Or do you have to link up another data template for the activity IEnumerable array?
Here is the property of the ActivityStream class (I now notice it's a List..hmm.)
...
[DataMember]
public List<object> activity { get; set; }
...
And here is where i do PropertyChanged yada yada stuff
public IEnumerable<JamesStream> activityStream;
...
private async void LoadActivityStreamSection()
{
activityStreamRepository = new JamesStreamRepository();
var tempActivityStream = await activityStreamRepository.GetAllBySpaceId(space.space_id);
activityStream = tempActivityStream;
ActivityStream = null;
//The Activitiy Stream has a property which is of type List<object> called activity
// In my xaml I just want to be outputting the 1st entry of the List<object> property .created_by_name
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler tempEvent = PropertyChanged;
if (tempEvent != null)
{
// property changed
tempEvent(this, new PropertyChangedEventArgs(propertyName));
}
}
public IEnumerable<JamesStream> ActivityStream
{
get
{
return activityStream;
}
set
{
if (activityStream != value)
{
OnPropertyChanged();
}
}
}
If your IEnumerable has a .First property, you may be able to attach to that?
your code works...normally :).
Can you show how do you fill your collection? I think it's maybe because you don't fire INotifyPropertyChanged (or INotify-like for collection) event

Categories