MVVM Detect property change of subclass - c#

I am using SimpleMVVM and have two separate classes (models), one using the second like this:
public class Database : ModelBase<Database>
{
public String ServerName //{ get; set; }
{
get { return _ServerName; }
set
{
if (_ServerName != value)
{
_ServerName = value;
NotifyPropertyChanged(m => m.ServerName);
}
}
}
private String _ServerName = "MyTestServer";
// other properties removed for brevity
}
public class MyConfiguration
{
/// <summary>
/// Database information
/// </summary>
public Database DatabaseInfo
{
get { return _DatabaseInfo; }
set
{
if (_DatabaseInfo != value)
{
_DatabaseInfo = value;
NotifyPropertyChanged(m => m.DatabaseInfo);
}
}
}
private Database _DatabaseInfo = new Database();
}
When 'ServerName' is changed, the NotifyPropertyChanged(m => m.ServerName); command executes but NOT NotifyPropertyChanged(m => m.DatabaseInfo);
How do I make the NotifyPropertyChanged(m => m.DatabaseInfo); fire whenever one of the properties of Database changes?

You can use the PropertyChanged event of the INotifyPropertyChanged interface to tell you when the child property changes.
In your MyConfiguration class:
public Database DatabaseInfo
{
get { return _DatabaseInfo; }
set
{
if (_DatabaseInfo != value)
{
_DatabaseInfo = value;
NotifyPropertyChanged(m => m.DatabaseInfo);
DatabaseInfo.PropertyChanged += DataBasePropertyChanged;
}
}
}
...
private void DataBasePropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyPropertyChanged(m => m.DatabaseInfo);
}
Please note that you will need to attach this listener each time that you change the DatabaseInfo property value. Also, note that if you just wanted to listen to one property, then you could have done this:
private void DataBasePropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "ServerName") NotifyPropertyChanged(m => m.DatabaseInfo);
}

private Database _DatabaseInfo = new Database();
public MyConfiguration()
{
this._DatabaseInfo.PropertyChanged += new PropertyChangedEventHandler(propChanged);
}
private void propChanged(object sender, PropertyChangedEventArgs e)
{
// Now you can update the _DatabaseInfo.DatabaseInfo property to force the property changed event to fire.
}
Refer to the documentation here
INotifyPropertyChanged.PropertyChanged event

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

Xamarin forms, working with a OnPropertyChanged with a INT value

I have one button on my MasterDetailPage changing the value on an INT (named App.value1) depending on what you click looking like this:
void click1 (object s, EventArgs a)
{
if (App.value1 == 0) {
App.value1 = App.value1 + 1;
} else {
App.value1 = 0;
}
}
And I want this click function to immediately change the value on my StartPage (another ContentPage). So I have created a viewmodel looking like this, where I try to work with the current value:
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged (string propertyName)
{
var changed = PropertyChanged;
if (changed != null) {
PropertyChanged (this, new PropertyChangedEventArgs (propertyName));
}
}
public int currentValue {
get {
return App.value1;
}
set {
if (App.value1 == 0) {
App.value1 = 0;
} else {
App.value1 = 1;
}
}
}
And this is the StartPage where I want the value of the INT to update immediately depending on what you clicked on at the MasterDetailView.
public StartPage ()
{
var ourView = new StartPageViewModel ();
ourCurrentValue = ourView.currentValue;
}
protected async override void OnAppearing()
{
LoadData();
}
private async Task<List<Pin>> LoadData() //I work with pins here (not showing that code though as it is irrelavant.
{
if (ourCurrentValue == 0) {
System.Diagnostics.Debug.WriteLine ("Value is 0");
}
else {
System.Diagnostics.Debug.WriteLine ("Value is 1");
}
}
Right now I only see "Value is 0" in my log. Nothing updates when I click on my button on the MasterDetailPage.
UPDATED CODE:
public class StartPageViewModel : INotifyPropertyChanged
{
private ICommand clickCommand;
private int currentValue;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged (string propertyName)
{
var changed = PropertyChanged;
if (changed != null)
{
PropertyChanged (this, new PropertyChangedEventArgs (propertyName));
}
}
public StartPageViewModel()
{
ClickCommand = new Command(() => CurrentValue = CurrentValue + 1);
}
public ICommand ClickCommand
{
get { return clickCommand; }
set
{
clickCommand = value;
OnPropertyChanged("ClickCommand");
}
}
public int CurrentValue
{
get { return currentValue; }
set
{
currentValue = value;
OnPropertyChanged("CurrentValue");
}
}
}
And StartPage:
public StartPage ()
{
App.PropertyChanged += (sender, args) => OnPropertyChanged("currentValue"); // ERROR: `An object reference is requiered to access non-static member 'Xamarin.Forms.BindableObject.PropertyChanged`
}
You can proceed with something like that:
Make following changes to your App class and value1 property inside that class:
public static event PropertyChangedEventHandler PropertyChanged;
private static void OnPropertyChanged (string propertyName)
{
var changed = PropertyChanged;
if (changed != null)
{
PropertyChanged (null, new PropertyChangedEventArgs (propertyName));
}
}
private static int _value1;
public static int value1
{
get { return _value1; }
set
{
_value1 = value;
OnPropertyChanged("value1");
}
}
Then add this line to your StartPageViewModel constructor:
App.PropertyChanged += (sender, args) => OnPropertyChanged("currentValue");
In that code you are just leveraging PropertyChanged for your own purposes (you can even create your own event for that).
I mean StartPageViewModel subscribes to PropertyChanged event in Appclass, so it will be notified when value1 change. And when it actually occurs, then it is invoking his own PropertyChanged to notify View about currentValue change.
However, I would say better solution is to share View Model between MasterDetailPage and StartPage, because using global state makes your solution hard to understand :
public class SharedViewModel : INotifyPropertyChanged
{
private ICommand clickCommand;
private int currentValue;
/* INotifyPropertyChanged implementation */
public SharedViewModel()
{
ClickCommand = new Command(() => CurrentValue = CurrentValue + 1);
}
public ICommand ClickCommand
{
get { return clickCommand; }
set
{
clickCommand = value;
OnPropertyChanged("ClickCommand");
}
}
public int CurrentValue
{
get { return currentValue; }
set
{
currentValue = value;
OnPropertyChanged("CurrentValue");
}
}
}
And you need to use the same instance of SharedViewModel in MasterDetailPage as well as StartPage

Binding to count on observable collection of subclass

I do have a WPF binding question here.
Following Setup:
I do have a class (ActionService) having a name and a ObservableCollection of subitems (also a class named Step). A Step has a flag that shows if the Step is allready done (IsDone).
I bind a form to the ActionService and display all kind of things.
Everything works as aspected and i have just the essential parts in my snippet.
Now I need one more thing that i can not get work. I want the ActionService to know by binding how many of its Steps are open (IsDone == false). I you open a childform with one of the steps and change the IsDone-State, the mother form should get the new count on the fly.
And I'm to dumb to get a correct solution on the way ;-)
Thanks for your help or a best practise.
public class ActionService : BaseObject
{
public ActionService()
{
}
private String name;
public String Name
{
get { return this.name; }
set
{
this.name = value;
raisePropertyChanged("Name");
}
}
public ObservableCollection<Step> actionsteps;
public ObservableCollection<Step> ActionSteps
{
get { return this.actionsteps; }
set
{
this.actionsteps = value;
raisePropertyChanged("ActionSteps");
}
}
}
public class Step : BaseObject
{
public Step()
{
}
private String description;
public String Description
{
get { return this.description; }
set
{
this.description = value;
raisePropertyChanged("Description");
}
}
private Boolean isdone;
public Boolean IsDone
{
get { return this.isdone; }
set
{
this.isdone = value;
raisePropertyChanged("IsDone");
}
}
}
public class BaseObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void raisePropertyChanged(String parPropertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(parPropertyName));
}
}
}
You can create a new property in your ActionService class:
public bool IsDone
{
get
{
return ActionSteps.Count(x => x.IsDone) == ActionSteps.Count;
}
}
If the count of Steps in the ActionSteps list where the IsDone property is true is equal to the number of Steps in the ActionSteps list, then return true, else, return false.
To subscribe to the Steps property changed event, when you add an item to the collection, you simply need to subscribe to the PropertyChanged event:
//Create the item and subscribe to propertychanged.
Step item = new Step();
item.PropertyChanged += item_PropertyChanged;
//Add the item to the list.
ActionSteps.Add(item);
And your method will look like this:
void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsDone")
raisePropertyChanged("IsDone");
}

Grid binded to EntityFramework Poco class via BindingSource is not automatically refreshing

This one is test project to show my question. (VS2012, WinForms, EntityFramework 5, XtraGrid 12.5)
Model created by EF PowerTools - Reverse Engineer CodeFirst tool.
In the timer1_tick event i'm changing mypoco.value property. I'm expecting that grid.cell shows this changes automatically but not. I also tried with textbox but the same.
if i uncomment BindingSource.ResetCurrentItem() in timer1_tick works expected but this is not my question. If i force to grid (or Textbox) to refresh everything is fine.
I expect that ef created proxy object notifies DbSet.Local (ObservableCollection) -> BindingList -> BindingSource -> Grid etc via interfaces,methots or inherit or i don't know... I'm asking about this notifying system and why not working? Or it is working but my expectation is wrong? (
Why this is not working as expected, Where i'm failing? Please also read notes in the code.
Thank you.
//FORM CODE
public partial class Form1 : Form
{
testContext context = new testContext();
MyPOCO mypoco;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
mypoco = context.MyPOCOes.Create();
// mypoco is created but not proxied currently. state = detached
// After adding it context proxy created and change tacking will be available
context.MyPOCOes.Add(mypoco);
// mypoco is in the memory but not saved to database. This is why using Local
myPOCOBindingSource.DataSource = context.MyPOCOes.Local.ToBindingList();
// Setup timer
timer1.Interval = 15 * 1000;
timer1.Start();
}
private void timer1_Tick(object sender, EventArgs e)
{
// Change the property and then warn user about this event occured
// At this point mypoco is proxied
mypoco.Value = 99;
this.Text = "Timer Tick";
//myPOCOBindingSource.ResetCurrentItem();
}
}
// some code from Form1.Designer file
private System.Windows.Forms.BindingSource myPOCOBindingSource;
private void InitializeComponent()
{
this.myPOCOBindingSource = new System.Windows.Forms.BindingSource();
....
this.myPOCOGridControl.DataSource = this.myPOCOBindingSource;
}
//MYPOCO
public partial class MyPOCO
{
public int ID { get; set; }
public Nullable<int> Value { get; set; }
}
//MAPPING
public class MyPOCOMap : EntityTypeConfiguration<MyPOCO>
{
public MyPOCOMap()
{
// Primary Key
this.HasKey(t => t.ID);
// Table & Column Mappings
this.ToTable("MyPOCO");
this.Property(t => t.ID).HasColumnName("ID");
this.Property(t => t.Value).HasColumnName("Value");
}
}
//CONTEXT
public partial class testContext : DbContext
{
static testContext()
{
Database.SetInitializer<testContext>(null);
}
public testContext()
: base("Name=testContext")
{
}
public DbSet<MyPOCO> MyPOCOes { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new MyPOCOMap());
}
}
The proxy code of MyPoco is here: Nothing related on bindings (of course) ...
public sealed class MyPOCO_F874E881B0FD3EF02199CD96C63396B451E275C5116C5DFBE892C68733857FDE : MyPOCO, IEntityWithChangeTracker, IEntityWithRelationships
{
[NonSerialized]
private IEntityChangeTracker _changeTracker;
private static Func<object, object, bool> _compareByteArrays;
[NonSerialized, IgnoreDataMember, XmlIgnore, ScriptIgnore]
public object _entityWrapper;
private System.Data.Objects.DataClasses.RelationshipManager _relationshipManager;
private static Action<object> _resetFKSetterFlag;
private void EntityMemberChanged(string text1)
{
if (this._changeTracker != null)
{
this._changeTracker.EntityMemberChanged(text1);
}
}
private void EntityMemberChanging(string text1)
{
if (this._changeTracker != null)
{
this._changeTracker.EntityMemberChanging(text1);
}
}
public void SetChangeTracker(IEntityChangeTracker tracker1)
{
this._changeTracker = tracker1;
}
public override int ID
{
get
{
return base.ID;
}
set
{
if (base.ID != value)
{
try
{
this.EntityMemberChanging("ID");
base.ID = value;
this.EntityMemberChanged("ID");
}
finally
{
_resetFKSetterFlag(this);
}
}
}
}
public System.Data.Objects.DataClasses.RelationshipManager RelationshipManager
{
get
{
if (this._relationshipManager == null)
{
this._relationshipManager = System.Data.Objects.DataClasses.RelationshipManager.Create(this);
}
return this._relationshipManager;
}
}
public override int? Value
{
get
{
return base.Value;
}
set
{
try
{
this.EntityMemberChanging("Value");
base.Value = value;
this.EntityMemberChanged("Value");
}
finally
{
_resetFKSetterFlag(this);
}
}
}
}

Categories

Resources