Why is my property notification not triggering? - c#

I have an hierarchy of classes. The pertinent bits are included below. I was expecting that when FilterCritetionInteger.CriterionValue is changed, via WPF binding, that I would get a notification in my FilterCriterionCollection. But, while the FilterCriterionInteger.CriterionValue setter is called, the FilterCriterionCollection.Criterion_PropertyChanged method is never called.
Apparently I am misunderstanding something, or have something connected incorrectly. I'm just not finding it. Why is my notification not happening? (Please let me know if there is more context needed, anywhere.)
ViewModelBase
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler pcHandler = this.PropertyChanged;
if (pcHandler != null)
{
PropertyChangedEventArgs args = new PropertyChangedEventArgs(propertyName);
pcHandler(this, args);
}
}
}
FilterCriterionBase
public abstract class FilterCriterionBase : ViewModelBase
{
public abstract bool IsValid { get; }
}
FilterCriterionInteger
public class FilterCriterionInteger : FilterCriterionBase
{
private string _criterionValue = "0";
public string CriterionValue
{
get { return _criterionValue; }
set
{
_criterionValue = value;
OnPropertyChanged("IsValid");
}
}
public int? MaxValue { get; private set; } = null;
public override bool IsValid
{
get
{
if (int.TryParse(_criterionValue, out int i))
{
if (MaxValue.HasValue && (i > MaxValue)) return false;
}
else
{
return false;
}
return true;
}
}
}
FilterCriterionCollection
public class FilterCriteriaCollection : ViewModelBase
{
public FilterCriteriaCollection()
{
Criteria.CollectionChanged += Criteria_CollectionChanged;
}
~FilterCriteriaCollection()
{
Criteria.CollectionChanged -= Criteria_CollectionChanged;
}
public ObservableCollection<FilterCriterionBase> Criteria { get; private set; } = new ObservableCollection<FilterCriterionBase>();
private void Criteria_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (FilterCriterionBase fb in e.NewItems)
{
fb.PropertyChanged += Criterion_PropertyChanged;
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (FilterCriterionBase fb in e.NewItems)
{
fb.PropertyChanged -= Criterion_PropertyChanged;
}
break;
}
}
private bool _isValid;
public bool IsValid
{
get { return _isValid; }
private set { _isValid = value; OnPropertyChanged("IsValid"); }
}
private void Criterion_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
using (FilterCriterionBase criterion = sender as FilterCriterionBase)
{
switch (e.PropertyName)
{
case "IsValid":
{
bool isvalid = true;
foreach(FilterCriterionBase fcb in Criteria)
{
isvalid = fcb.IsValid;
if (!isvalid) break;
}
IsValid = isvalid;
break;
}
}
}
}
}

So, as it turns out, I am a moron. The code, above, does need a little tweaking but it is, in general, sound. My primary issue was that I had some leftover code, in my testing sandbox, from testing some JSON serialization/deserialization. I realized, after much head banging, that I was sending deserialized objects that had lost their event notification context and, therefore, were no longer in the loop.
Would it be proper protocol to just delete this post, since it doesn't really require an answer beyond double checking your inputs...?

Related

How to stop INotify from updating twice?

I am updating a Datagrid and when a user inputs a number that already exists I want notify the user they the number already exists and then clear the value from the datagrid.
I know why this is happening, but I can't figure out how to stop this or how to make a work around.
This is very simplified code: Using EF code first with MVVM model.
public partial class StaffMasterData
{
public System.Guid Id { get; set; } // ID (Primary key)
public int? StaffNo { get; set; } // StaffNo
public StaffMasterData()
{
InitializePartial();
}
partial void InitializePartial();
}
Entity extension class for StaffMasterData :
public partial class StaffMasterData : INotifyPropertyChanged
{
partial void InitializePartial()
{
Id = Guid.NewGuid();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
And the method to save the data:
public void SaveMasterData(StaffMasterData nwRowData)
{
using (var db = CreateDbContext())
{
//MasterDataBinding is the observableCollection
//the datagrid is being bound to.
var staffNoExists = MasterDataBinding.Any(p => p.StaffNo == nwRowData.StaffNo);
if (!staffNoExists)
{
db.StaffMasterDatas.AddOrUpdate(nwRowData);
db.SaveChanges();
}
else
{
Alerts.Error("Staff Number exists");
nwRowData.StaffNo = null;
}
}
}
And the assinging of the collection changed event:
public class ShiftManagerViewModel : INotifyPropertyChanged
{
private ObservableCollection<StaffMasterData> _mMasterDataBinding = new ObservableCollection<StaffMasterData>();
public ObservableCollection<StaffMasterData> MasterDataBinding
{
get { return _mMasterDataBinding; }
set
{
if (value != _mMasterDataBinding)
{
_mMasterDataBinding = value;
OnPropertyChanged();
}
}
}
public ShiftManagerViewModel()
{
_mMasterDataBinding.CollectionChanged += collectionChanged_Event;
}
private void collectionChanged_Event(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null && e.NewItems.Count > 0)
{
foreach (INotifyPropertyChanged item in e.NewItems.OfType<INotifyPropertyChanged>())
{
item.PropertyChanged += propertyChanged_Event;
}
}
if (e.OldItems != null && e.OldItems.Count > 0)
{
foreach (INotifyPropertyChanged item in e.OldItems.OfType<INotifyPropertyChanged>())
{
item.PropertyChanged -= propertyChanged_Event;
}
}
}
public void propertyChanged_Event(object sender, PropertyChangedEventArgs e)
{
if (sender is StaffMasterData)
{
SaveMasterData((StaffMasterData)sender);
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
As it is probably very clear, when running through this line of code nwRowData.StaffNo = null; , it fires the event again as the collection has been modified which then in turn runs through the messageBox code and it pops up twice.
Honestly I have hit a brick wall with this and any point in the right direction would be appreciated.
You could use a flag that determines whether to actually call the SaveMasterData method. Set this flag to false just before you set the StaffNo property to null and then set it back to true immediately afterwards:
private bool _handle = true;
public void SaveMasterData(StaffMasterData nwRowData)
{
using (var db = CreateDbContext())
{
//MasterDataBinding is the observableCollection
//the datagrid is being bound to.
var staffNoExists = MasterDataBinding.Any(p => p.StaffNo == nwRowData.StaffNo);
if (!staffNoExists)
{
db.StaffMasterDatas.AddOrUpdate(nwRowData);
db.SaveChanges();
}
else
{
Alerts.Error("Staff Number exists");
_handle = false;
nwRowData.StaffNo = null;
_handle = true;
}
}
}
public void propertyChanged_Event(object sender, PropertyChangedEventArgs e)
{
if (!_handle && sender is StaffMasterData)
{
SaveMasterData((StaffMasterData)sender);
}
}

C# Trigger RaisePropertyChanged of Parent from Child?

I have this code where I have my ViewModel and the ViewModel has a property where it gets all of its properties.
This is rough pseudo-code:
public class MyClassViewModel : INotifyPropertyChanged
{
public MyClassViewModel ()
{
}
public BaseClass myClassBase { get ; set; }
public string Title
{
get
{
return myClassBase.Title;
}
set
{
myClassBase.Title = value;
RaisePropertyChanged("Title");
}
}
public string Description
{
get
{
return myClassBase.Description;
}
set
{
myClassBase.Description = value;
RaisePropertyChanged("Description");
}
}
}
And this is the BaseClass:
public class BaseClass
{
public BaseClass()
{
}
public string Title {get;set;}
public string Description {get;set;}
}
CheckItemViewModel is the one binded to UI. So if I do something like MyClassViewModel .Title = "Test"; it properly refreshes the UI.
However, I need to do something like MyClassViewModel.myClassBase.Title = "Test" for specific reasons (Javascript - Chakra interface). The problem with this then is that the UI does not Refresh anymore since it doesn't have RaisePropertyChanged.
Even when I implemented RaisePropertyChanged inside the BaseClass itself, it still doesn't work. It doesn't work because PropertyChanged in BaseClass is always null.
I suspect it's because MyClassViewModel is the one binded to UI. So PropertyChanged in BaseClass is never binded.
Is there a way to trigger the Parent's RaisePropertyChanged?
Thank you
I would suggest implementing INotifyPropertyChanged on both classes, then have MyClassViewModel subscribe to the event in BaseClass and forward it to the UI:
public class MyClassViewModel : INotifyPropertyChanged, IDisposable
{
private BaseClass myClassBase;
public void Dispose()
{
if (myClassBase != null) myClassBase.PropertyChanged -= OnBaseClassPropertyChanged;
}
public BaseClass MyClassBase {
get {
return myClassBase;
}
set {
if (myClassBase != null) myClassBase.PropertyChanged -= OnBaseClassPropertyChanged;
myClassBase = value;
myClassBase.PropertyChanged += OnBaseClassPropertyChanged;
}
}
private void OnBaseClassPropertyChanged(object sender, PropertyChangedEventArgs args) {
RaisePropertyChanged(args.PropertyName);
}
// forwarded properties (Title and Description) go here
}
First of all, you can simplify the RaisePropertyChanged this way:
public void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
So you don't need to write RaisePropertyChanged("Description"), but only: RaisePropertyChanged(), and the propertyName is automatically injected. That's awesome if you refactor frequently: you don't have to deal with the nightmare of remembering all the "Title" and "Description" strings in the whole solution :)
Second, if the BaseClass has the PropertyChangedEvent, you can listen to it in the MyClassViewModel.
myClassBase.PropertyChanged += (s, e) => { RaisePropertyChanged(e.PropertyName); };
But, if you don't inject myClassBase immediately in the constructor of MyClassViewModel, or if the myClassBase can change sometime, things get a bit more complicated.
You have to make MyClassViewModel also to implement INotifyPropertyChanging:
public event PropertyChangingEventHandler PropertyChanging;
public void RaisePropertyChanging([CallerMemberName] string propertyName = null)
{
PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName));
}
You have to raise notifications also for the myClassBase:
public BaseClass myClassBase
{
get { return _myClassBase; }
set
{
RaisePropertyChanging();
_myClassBase = value;
RaisePropertyChanged();
}
}
private BaseClass _myClassBase;
Then, all you need is this code:
public MyClassViewModel()
{
PropertyChanging += OnPropertyChanging;
PropertyChanged += OnPropertyChanged;
}
private void OnPropertyChanging(object sender, PropertyChangingEventArgs e)
{
if (e.PropertyName != nameof(MyClassViewModel.myClassBase))
return; //or do something with the other properties
if (myClassBase == null)
return;
myClassBase.PropertyChanged -= OnMyBaseClassPropertyChanged;
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName != nameof(MyClassViewModel.myClassBase))
return; //or do something with the other properties
if (myClassBase == null)
return;
myClassBase.PropertyChanged += OnMyBaseClassPropertyChanged;
}
private void OnMyBaseClassPropertyChanged(object sender, PropertyChangedEventArgs e)
{
RaisePropertyChanged(e.PropertyName);
}
NB: I use the C#-6.0 nameof() operator, I hope you can use it, it's simply awesome!
EDIT:
Here you have a simple test method that demonstrates the correct functionality:
[TestMethod]
public void ChildClassPropertyChanged()
{
var bc = new BaseClass();
var c = new MyClassViewModel();
bc.Title = "t1";
c.myClassBase = bc;
Assert.AreEqual("t1", c.Title);
c.Title = "t2";
Assert.AreEqual("t2", c.Title);
c.myClassBase.Title = "t3";
Assert.AreEqual("t3", c.Title);
c.myClassBase = new BaseClass();
bc.Title = "t4";
Assert.AreEqual(null, c.Title);
c.myClassBase.Title = "t5";
Assert.AreEqual("t5", c.Title);
}
Keep in mind that if you set a null myClassBase, inside your properties' getters and setters the code throws a NullReferenceException. Maybe you should modify it this way:
public string Title
{
get
{
return myClassBase?.Title;
}
set
{
if (myClassBase != null)
myClassBase.Title = value;
RaisePropertyChanged();
}
}

Change data in Class when List<sub-class> data is changed

I have a class STimer that has a List in it. The serviceDetail is monitored on a timer very often, and rarely changes, but when it does change I want to get the fact that the data changed without looping through the list due to processing power. Maybe this is a duplicate question that I just don't know how to search for it, but I have been trying. Here is a code Sample:
class STimers
{
public class ServiceDetail
{
private int _serviceKey;
private bool _isRunning = true;
private bool _runningStateChanged = false;
public bool isRunning
{
get { return _isRunning; }
set
{
//Check to see if the data is the same, if so, don't change, if not, change and flag as changed
if(_isRunning = value) { return; }
else
{
_isRunning = value;
_runningStateChanged = true;
<-- Update STimers._dataChanged to true -->
}
}
}
}
public List<ServiceDetail> _serviceMonitors = new List<ServiceDetail>();
public bool _dataChanged = false;
}
I could do a .Find on the list to return all of the _serviceMonitors._runningStateChanged=true, but that seems like a lot of work parsing the List every time the timer fires, when likely only 1 out of 1,000 loops will actually have a change.
Is this even possible, or do I need to move the check for changes out of the class?
You could get this by adding an event to your ServiceDetail class
public class ServiceDetail
{
public event EventHandler<ListChangedEventArgs> ListChanged;
private int _serviceKey;
private bool _isRunning = true;
private bool _runningStateChanged = false;
private void OnListChanged(ListChangedEventArgs e){
if (ListChanged != null) ListChanged(this, e);
}
public bool isRunning
{
get { return _isRunning; }
set
{
//Check to see if the data is the same, if so, don't change, if not, change and flag as changed
if(_isRunning = value) { return; }
else
{
_isRunning = value;
_runningStateChanged = true;
OnListChanged(new ListChangedEventArgs(this));
<-- Update STimers._dataChanged to true -->
}
}
}
}
And define your ListChangedEventArgs class like this
public class ListChangedEventArgs:EventArgs
{
public ServiceDetail serviceDetail { get; set; }
public ListChangedEventArgs(ServiceDetail s)
{
serviceDetail = s;
}
}
And then register to the event for each servicedetail added to the list
s.ListChanged += (sender, args) => YourFunction();
Hope it helps

WPF PropertyChanged code error: cannot implement 'System.ComponentModel.INotifyPropertyChanged

i have this code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.ComponentModel;
using System.Collections.Specialized;
using System.Collections.ObjectModel;
namespace Bix
{
public class SettingsDataObject
{
private int id;
public int Id
{
get { return id; }
set { id = value == 0 ? Db.GetNextSettingsId() : value; }
}
private string adminEmail; public string AdminEmail {
get { return adminEmail; }
set { adminEmail = value; }
}
private int state; public int State { get { return state; } set { state = value == 0 ? 1 : value; } }
public object[] GetArray()
{
return new object[] { id, adminEmail, state };
}
public SettingsDataObject()
{
}
}
public class SettingsUIObjects : ObservableCollection<SettingsUIObject>,INotifyPropertyChanged
{
protected override void InsertItem(int index, SettingsUIObject item)
{
base.InsertItem(index, item);
// handle any EndEdit events relating to this item
item.ItemEndEdit += new SettingsUIObject.ItemEndEditEventHandler(ItemEndEditHandler);
item.PropertyChanged += new SettingsUIObject.PropertyChangedEventHandler(PropertyChanged);
}
public void ItemEndEditHandler(IEditableObject sender)
{
// simply forward any EndEdit events
if (ItemEndEdit != null)
{
ItemEndEdit(sender);
}
}
public event SettingsUIObject.ItemEndEditEventHandler ItemEndEdit;
public event SettingsUIObject.PropertyChangedEventHandler PropertyChanged;
}
public class SettingsDataProvider
{
private DataAccessLayer dl;
public SettingsDataProvider()
{
dl = new DataAccessLayer();
}
public SettingsUIObjects GetSettings()
{
try
{
SettingsUIObjects objs = new SettingsUIObjects();
List<SettingsDataObject> objDataObjects = dl.GetSettings();
foreach (SettingsDataObject obj in objDataObjects)
{
objs.Add(new SettingsUIObject(obj));
}
objs.ItemEndEdit += new SettingsUIObject.ItemEndEditEventHandler(SettingsItemEndEdit);
objs.CollectionChanged += new
NotifyCollectionChangedEventHandler(SettingsCollectionChanged);
objs.PropertyChanged += new SettingsUIObject.PropertyChangedEventHandler(SettingsPropertyChanged);
return objs;
}
catch (Exception) { return new SettingsUIObjects(); }
}
void SettingsItemEndEdit(IEditableObject sender)
{
SettingsUIObject obj = sender as SettingsUIObject;
// use the data access layer to update the wrapped data object
dl.UpdateSettings(obj.GetDataObject());
}
void SettingsPropertyChanged(INotifyPropertyChanged sender, PropertyChangedEventArgs e)
{
SettingsUIObject obj = sender as SettingsUIObject;
// use the data access layer to update the wrapped data object
dl.UpdateSettings(obj.GetDataObject());
}
void SettingsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach (object item in e.OldItems)
{
SettingsUIObject obj = item as SettingsUIObject;
// use the data access layer to delete the wrapped data object
dl.DeleteSettings(obj.GetDataObject());
}
}
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (object item in e.NewItems)
{
SettingsUIObject obj = item as SettingsUIObject;
// use the data access layer to delete the wrapped data object
dl.UpdateSettings(obj.GetDataObject());
}
}
}
}
public class SettingsUIObject : IEditableObject, INotifyPropertyChanged
{
private SettingsDataObject obj;
public SettingsUIObject(SettingsDataObject o)
{
obj = o;
}
public SettingsDataObject GetDataObject()
{
return obj;
}
public int Id { get { return obj.Id; } set { obj.Id = value; } }
public string AdminEmail {
get { return obj.AdminEmail; }
set { obj.AdminEmail = value; }
}
public delegate void ItemEndEditEventHandler(IEditableObject sender);
public event ItemEndEditEventHandler ItemEndEdit;
#region IEditableObject Members
public void BeginEdit() { }
public void CancelEdit() { }
public void EndEdit()
{
if (ItemEndEdit != null)
{
ItemEndEdit(this);
}
}
#endregion
public delegate void PropertyChangedEventHandler(INotifyPropertyChanged sender, PropertyChangedEventArgs e);
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
}
and i keep getting the compile error:
'Bix.SettingsUIObject' does not implement interface member 'System.ComponentModel.INotifyPropertyChanged.PropertyChanged'. 'Bix.SettingsUIObject.PropertyChanged' cannot implement 'System.ComponentModel.INotifyPropertyChanged.PropertyChanged' because it does not have the matching return type of 'System.ComponentModel.PropertyChangedEventHandler'
can anyone tell me why?
thanks
Orson
public delegate void PropertyChangedEventHandler(INotifyPropertyChanged sender, PropertyChangedEventArgs e);
public event PropertyChangedEventHandler PropertyChanged;
Your code redeclares a PropertyChangedEventHandler delegate, which hides the one declared in System.ComponentModel. So your event is of type SettingsUIObject.PropertyChangedEventHandler, not System.ComponentModel.PropertyChangedEventHandler. Since the type doesn't match the one declared in INotifyPropertyChanged, your PropertyChanged event doesn't a valid implementation of the interface.
Just remove your PropertyChangedEventHandler delegate and it should work fine.

Extending DataList to accept a new InsertCommand from LinkButtons

I'm trying to subclass DataList to accept a new Command from embedded LinkButtons. Here's my abstract class:
public abstract class BaseFieldGroup : DataList
{
public const string InsertCommandName = "Insert";
public event DataListCommandEventHandler InsertCommand
{
add
{
base.Events.AddHandler(EventInsertCommand, value);
}
remove
{
base.Events.RemoveHandler(EventInsertCommand, value);
}
}
private static readonly object EventInsertCommand;
static BaseFieldGroup()
{
EventInsertCommand = new object();
}
protected virtual void OnInsertCommand(DataListCommandEventArgs e)
{
DataListCommandEventHandler handler = (DataListCommandEventHandler)base.Events[EventInsertCommand];
if (handler != null)
{
handler(this, e);
}
}
}
it seems right, but the Event isn't being caught; I'm not sure why. However, I also noticed that LinkButton sends the even up with a RaiseBubble, I don't know if that's an issue or not. Anyone have thoughts?
Oh, this is 2.0
Aha! Found and fixed; I have to override the OnBubbleEvent of the DataList to accomodate (call) the new command. See below:
public abstract class BaseFieldGroup : DataList
{
...
protected override bool OnBubbleEvent(object source, EventArgs e)
{
bool flag = false;
if (e is DataListCommandEventArgs)
{
DataListCommandEventArgs args = (DataListCommandEventArgs)e;
this.OnItemCommand(args);
flag = true;
switch (args.CommandName)
{
case SelectCommandName:
this.SelectedIndex = args.Item.ItemIndex;
this.OnSelectedIndexChanged(EventArgs.Empty);
return flag;
case EditCommandName:
this.OnEditCommand(args);
return flag;
case DeleteCommandName:
this.OnDeleteCommand(args);
return flag;
case UpdateCommandName:
this.OnUpdateCommand(args);
return flag;
case CancelCommandName:
this.OnCancelCommand(args);
return flag; //??
case InsertCommandName:
this.OnInsertCommand(args);
return flag;
}
}
return flag;
}

Categories

Resources