INotifyPropertyChanged in c# structures - c#

Hello I have the following code:
public struct zoomInfo : INotifyPropertyChanged
{
public float zoom;
public float translateX;
public float translateY;
private int level;
public int zoomLevel
{
get { return level; }
set {
level = value;
OnPropertyChanged("zoomLevel");
}
}
//region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
//endregion
};
When I bind this to a control.. it is not working. PropertyChangedEventHandler is always null.. But when I change this to class instead of struct, PropertyChangedEventHandler is not null and binding works perfectly. So the question is, does INotifyPropertyChanged only works on classes?

You can implement INotifyPropertyChanged in a struct, but you should never do that, because semantics of structs do not play well (I'd say at all) with this interface, and events in general. Consider this:
struct EventStruct : INotifyPropertyChanged {
private string _property;
public string Property
{
get { return _property; }
set
{
_property = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
private void OnPropertyChanged([CallerMemberName] string propertyName = null) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Now we just subscribe to event and change a property:
class Program {
static void Main() {
var s = new EventStruct();
s.PropertyChanged += OnPropertyChanged;
s.Property = "test";
Console.ReadKey();
}
private static void OnPropertyChanged(object sender, PropertyChangedEventArgs e) {
Console.WriteLine(e.PropertyName + " changed");
}
}
Output is "Property changed". So you cannot say that structs and events do not work at all (or that INotifyPropertyChanged doesn't work with structs). It works, kind of, until you try to pass that struct anywhere:
class Program {
static void Main() {
var s = new EventStruct();
Bind(s);
s.Property = "test";
Console.ReadKey();
}
static void Bind(INotifyPropertyChanged item) {
// this is not the same instance of EventStruct,
// it's a copy, and event will never be fired on this copy
item.PropertyChanged += OnPropertyChanged;
}
private static void OnPropertyChanged(object sender, PropertyChangedEventArgs e) {
Console.WriteLine(e.PropertyName + " changed");
}
}
Passing struct to Bind method as INotifyPropertyChanged boxes that struct, which makes a copy, so you subscribe to PropertyChanged event using one instance of EventStruct (a copy), but event is fired on another instance of the struct. So event subscriber list is empty on that instance where event is actually fired. Same happens if you just pass struct and not interface (in this case struct is copied because passed by value):
static void Bind(EventStruct item) {
item.PropertyChanged += OnPropertyChanged;
}
If you will pass by reference, it will work again:
static void Bind(ref EventStruct item) {
item.PropertyChanged += OnPropertyChanged;
}
For that reason you should never implement events on structs, at least no use case comes to my mind where this can be useful and not lead into troubles.

Related

Why does raising the PropertyChanged event cause other controls to update

I have a view model that has several properties that are databound to several controls.
When I raise PropertyChanged on one of them, the controls unexpectedly all update. I would expect only the one I am raising the event on to update.
For my form, I have this:
public partial class MainForm : Form
{
AmountCalculatorVM amountCalculatorVM;
public MainForm()
{
InitializeComponent();
}
private void setBindings()
{
textBoxTotalAmount.DataBindings.Add("Text", amountCalculatorVM, "TotalAmount");
textBoxAverage.DataBindings.Add("Text", amountCalculatorVM, "Average",true, DataSourceUpdateMode.Never,null, "#.00");
textBoxCount.DataBindings.Add("Text", amountCalculatorVM, "Count");
listBoxLineAmounts.DataSource = amountCalculatorVM.Amounts;
}
private void MainForm_Load(object sender, EventArgs e)
{
amountCalculatorVM = new AmountCalculatorVM();
setBindings();
}
private void buttonAddAmount_Click(object sender, EventArgs e)
{
if (int.TryParse(textBoxLineAmount.Text.Replace(",", ""), out int amount))
{
amountCalculatorVM.Amounts.Add(amount);
textBoxLineAmount.Text = "";
textBoxLineAmount.Focus();
}
}
private void buttonClear_Click(object sender, EventArgs e)
{
textBoxLineAmount.Text = "";
amountCalculatorVM.Amounts.Clear();
textBoxLineAmount.Focus();
}
}
Then, for my view model, I have this:
class AmountCalculatorVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private readonly AmountList amounts;
public BindingSource Amounts { get; }
public int TotalAmount => amounts.TotalAmount;
public int Count => amounts.Count;
public decimal Average => amounts.Average;
public AmountCalculatorVM()
{
amounts = new AmountList();
Amounts = new BindingSource();
Amounts.DataSource = amounts;
Amounts.ListChanged += Amounts_ListChanged;
}
private void Amounts_ListChanged(object sender, ListChangedEventArgs e)
{
//Any one of these will cause all three textboxes to update in the form
//I would think that with Count and Average commented out, the Count and
//Average textboxes would not update.
OnPropertyChanged("TotalAmount");
//OnPropertyChanged("Count");
//OnPropertyChanged("Average");
//Using any other word will not
//OnPropertyChanged("SomeOtherRandomWord");
}
}
Here is the AmountList class for reference:
class AmountList : List<int>
{
public int TotalAmount
{
get
{
int total = 0;
foreach (int amount in this)
{
total += amount;
}
return total;
}
}
Now, unexpectedly, all three textboxes update if an item is added to the amounts list, which fires ListChanged, and then in turn, the PropertyChanged event.
It doesn't matter which of the three properties I fire PropertyChanged on, but it won't work if I use a different value - it needs to be either TotalAmount, Count, or Average.
I can't understand this behaviour. I would have expected only the text box bound to TotalAmount to be updated, and not the other two, since nothing seems to be notifying them that an update has occurred.
Any ideas?
Why don't you implement the propertychanged like this:
public class Data : INotifyPropertyChanged
{
// boiler-plate
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, string propertyName)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
// props
private string name;
public string Name
{
get { return name; }
set { SetField(ref name, value, "Name"); }
}
}
You can control now, in the setter, which property fires the event:
private string name;
public string Name
{
get { return name; }
set { SetField(ref name, value, "Name"); }
}
you know what I mean?

structs and INotifyPropertyChanged

I'm trying to add properties to my Model playing with my first MVVM app.
Now I want to add a place to save specific data in a clean way, so I used a struct.
But I am having issues to notify property changed, it does not have access to the method (An object reference is required for the non-static field)
Can someone explain to me why this happens and inform me on a strategy that fit my needs?
Thanks!
public ObservableCollection<UserControl> TimerBars
{
get { return _TimerBars; }
set
{
_TimerBars = value;
OnPropertyChanged("TimerBars");
}
}
public struct FBarWidth
{
private int _Stopped;
public int Stopped
{
get { return _Stopped; }
set
{
_Stopped = value;
OnPropertyChanged("Name"); //ERROR: An object reference is required for the non-static field
}
}
private int _Running;
//And more variables
}
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
OnPropertyChanged needs to be defined in the scope that you wish to update properties on.
For that to work you'll have to implement the interface INotifyPropertyChanged.
And finally you have to provide the correct argument to the OnPropertyChanged method. In this example "Stopped"
public struct FBarWidth : INotifyPropertyChanged
{
private int _Stopped;
public int Stopped
{
get { return _Stopped; }
set
{
_Stopped = value;
OnPropertyChanged("Stopped");
}
}
private int _Running;
//And more variables
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Edit: In your comment you mentioned that you've got a class sorounding the code you provided in your example.
That means you've nested a struct inside a class.
Just because you've nested your struct, doesn't mean it inherits properties and methods from the outer class. You still need to implement INotifyPropertyChanged inside your struct and define the OnPropertyChanged method inside it.

How to notify a bound element oh a value change when value derives from model?

I have the visibility of a progress bar bound to The following property within my viewmodel:
public string CalcProgVisibility
{
get
{
return Calculation.CalcProgVisibility;
}
set
{
}
}
Calculation is my model, which can change the value. When the value changes within the model, what do I need to do to make sure the view is aware of this change?
EDIT:
Here is the property within my model too. I am using onpropertychanged but its not making it to the view.
I am changing the value within the model, the view is bound to my viewmodel and the viewmodel si trying to return a value taken from the model. I am updating the value on the model, and cannot push the fact that it has updated the value all the way down to the view, I can only get the viewmodel to see it has changed...
I updated the entire code. I hope it's clear now.
Define your control BindingMode = TwoWay
<TextBox Visibility="{Binding Path=CalcProgVisibility, Mode=TwoWay}"...
and call the OnPropertyChanged method on the setter of the property in your view model and also in your model
//Model
public class Calculation : INotifyPropertyChanged
{
private string _calcProgVisibility;
public string CalcProgVisibility
{
get { return _calcProgVisibility; }
set
{
_calcProgVisibility = value;
OnPropertyChanged("CalcProgVisibility");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
RaisePropertyChanged(propertyName);
}
private void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler == null) return;
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
//ViewModel
public class ViewModel : INotifyPropertyChanged
{
public ViewModel(Calculation model)
{
this.CalcProgVisibility = model.CalcProgVisibility;
model.PropertyChanged += (s, e) => UpdateEntity(s as Calculation);
}
private void UpdateEntity(Calculation source)
{
CalcProgVisibility = source.CalcProgVisibility;
}
private string _calcProgVisibility;
public string CalcProgVisibility
{
get { return _calcProgVisibility; }
set
{
_calcProgVisibility = value;
OnPropertyChanged("CalcProgVisibility");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
RaisePropertyChanged(propertyName);
}
private void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler == null) return;
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Your Viewmodel has to implement the INotifyPropertyChanged Interface. To fire it in your case your viewmodel must also be aware of changes in your model object. So your model object could also implement INotifyPropertyChanged, or you use some form of the observer pattern.
If your model implements INotifyPropertyChanged, your viewmodel must manually register for this event and implement an handler. This could in turn trigger the PropertyChange event of the viewmodel then.
Another but in my opinion ugly way would be to scan (per timer or background thread) through your viemodel and check if a value changed since the last scan and then trigger a property changed event.
The first solution could look like this:
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace StackOverflow
{
[TestClass]
public class IntegrationTest
{
[TestMethod]
public void NotifyPropertyChangeShouldFireOnViewModelWhenModelChanges()
{
//Arrange
Model model = new Model();
ViewModel sut = new ViewModel(model);
bool notifyPropertyChangeOnViewModelWasCalled = false;
sut.PropertyChanged += (sender, e) => { notifyPropertyChangeOnViewModelWasCalled = true; };
//Act
model.CalcValue = 4711;
//Assert
Assert.IsTrue(notifyPropertyChangeOnViewModelWasCalled, "NotifyPropertyChange was not fired on ViewModel");
}
}
public class ObjectWithNotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName]string propertyName = "")
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class Model : ObjectWithNotifyPropertyChanged
{
private double calcValue;
public double CalcValue
{
get
{
return calcValue;
}
set
{
if (calcValue != value)
{
calcValue = value;
RaisePropertyChanged();
}
}
}
}
public class ViewModel : ObjectWithNotifyPropertyChanged
{
public ViewModel(Model model)
{
this.model = model;
model.PropertyChanged += model_PropertyChanged;
}
void model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "CalcValue":
RaisePropertyChanged("CalcValue");
break;
}
}
private Model model;
public double CalcValue
{
get
{
return model.CalcValue;
}
}
}
}

Property Change event is null even after I registered it

I use INotifyPropertyChanged to notify class when there is any change in a variable of a particular object within it.
Below is the class:
public class MyClass
{
public SecClass MyObj { get; set; }
//A few more variables
}
SecClass:
public class SecClass:INotifyPropertyChanged
{
private bool _noti= false;
public bool Noti
{
get { return _noti; }
set
{
_noti= value;
NotifyPropertyChanged("Noti");
}
}
//A few more variables
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
Here my function that makes the event registration:
public void Register()
{
MyObj.PropertyChanged += MyObj_PropertyChanged;
}
Function works and the registration is done, but when it comes to change it displays the Property Change as null (I guess that somewhere registration deleted, before happens change, how can I check this?)
I hooked this together with:
static class Program
{
static void Main()
{
var c = new MyClass();
c.MyObj = new SecClass();
c.Register();
c.MyObj.Noti = !c.MyObj.Noti;
}
}
adding (for illustration):
private void MyObj_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
Console.WriteLine(e.PropertyName);
}
to MyClass, and:
public event PropertyChangedEventHandler PropertyChanged;
to SecClass (to get them to compile), and it works fine - printing "Noti" at runtime. There is a theoretical thread-race, but it is very unlikely in any sane usage, but recommended usage is:
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
Also, for info: if you add [CallerMemberName] to that, you don't need to specify the property explicitly:
private void NotifyPropertyChanged([CallerMemberName] string name = null) {...}
with:
NotifyPropertyChanged(); // the compiler adds the "Noti" itself
But fundamentally: "cannot reproduce" - it works fine. I wonder if maybe it relates to your PropertyChanged implementation, since you don't actually show that. In particular, I wonder if you actually have two events: one explicitly implemented. That would mean that it is getting treated differently by your cast.

creating a ViewModel class

I would like to create a ViewModel Class to retrieve the values from the Database.
My goal is to retrieve the Values of Usage of RAM (Ram total & Ram available) from my DB Table and then display it on my View.
This is what I have done so far on my ViewModel Class
public class RamViewModel : INotifyPropertyChanged
{
float _ramTotal;
float _ramUsed;
public float RamTotal
{
get { return _ramTotal; }
set { _ramTotal = value; RaisePropertyChanged("RamTotal"); }
}
public float RamUsed
{
get { return _ramUsed; }
set { _ramUsed = value; RaisePropertyChanged("RamUsed"); }
}
private void RaisePropertyChanged(string p)
{
throw new NotImplementedException();
}
}
when I build the class, I got this error stating, " ViewModel.RamViewModel Does not implement interface member 'System.ComponentModel.INotifyPropertyChanged.PropertyChanged'"
How to overcome this error
INotifyPropertyChanged is an interface with one member that needs to be included in your class definition:
public event PropertyChangedEventHandler PropertyChanged;
You should also change the code in RaisePropertyChanged to not throw an exception, by implementing the actual functionality:
private void RaisePropertyChanged(string p)
{
if (null != PropertyChanged) PropertyChanged(this, new PropertyChangedEventArgs(p));
}
Your class does not expose the PropertyChanged event, which is necessary for classes that implement INotifyPropertyChanged (it's the only member of that interface).
So you should add:
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(propertyName);
}
}
ObservableCollection is unrelated to this.

Categories

Resources