I'm trying to use Implicit Operators to make using my library, a bit easier.
I know I can get the value implicitly, but not sure how to set the value, if it can be done easily.
Here's the pseduo code I'm thinking about in my head ...
public class Int32Notified : INotifyPropertyChanged
{
private int _value;
public int Value
{
get { return _value; }
set
{
_value = value;
OnPropertyChanged("Value");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public static implicit operator int(Int32Notified value)
{
return value.Value;
}
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
and something I'm thinking about .. when using it....
public class Foo
{
// Yeah, i've not wired up the event... (yet)
public Int32Notified Age { get; set; }
}
...
// Creation and setting.
var foo = new Foo { Age = 10 };
// Getting
int age = foo.Age;
Is this possible?
Related
I have trouble with the ViewModel of my MVVM pattern-code.
I have a bunch of measurements and a bunch of rules to evaluate the measurements, stored in the classes Rule and Measurement. In my main class MyClass I store my Rules and Measurements then in ObservableCollections (OC) (and connected to a DataGrid).
For all n Rules I create n CollcetionOfEvaluators in one OC and pass the respective rule and all the measurements to each single one.
In each CollectionOfEvaulators I create for the one rule and the m Measurements m Evaluators in an OC.
The Evaluators take the one Rule and the one Measurement and gives back a bool if or if not the respective Measurement passes the respective Rule.
I then have a ListView that displays for each Rule a DataGrid that shows for every Measurement if it passed the Rule.
My problem is to make the Evaluator class to fire the OnPropertyChanged method, if I change the properties of one of the measurements in MyClass. How can I pass the info basically from one child to another child's child? When I play around with the DataGrid of the Evaluators, for example click on the header to rearrange it, it works. So I guess the problem is the c# code not the xaml. So I will leave it out for once. All the bindings are Mode=TwoWay (except the bool, since it has no setter) and UpdateSourceTrigger=PropertyChanged.
I tried to sketch the problem:
This is my code:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Text;
namespace demo
{
public class MyClass : INotifyPropertyChanged
{
public class Measurement : INotifyPropertyChanged
{
private double? myValue1;
public double? MyValue1
{
get { return myValue1; }
set
{
myValue1 = value;
OnPropertyChanged("MyValue1");
}
}
private double? myValue2;
public double? MyValue2
{
get { return myValue2; }
set
{
myValue2 = value;
OnPropertyChanged("MyValue2");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class EvaluationRule
{
public EvaluationRule(double Value1Min, double Value2Min)
{
this.Value1Min = Value1Min;
this.Value2Min = Value2Min;
}
public double Value1Min;
public double Value2Min;
}
public class Evaluator : INotifyPropertyChanged
{
public Evaluator(Measurement Measurement, EvaluationRule Rule)
{
this.Rule = Rule;
this.Measurement = Measurement;
}
public EvaluationRule Rule;
private Measurement measurement;
public Measurement Measurement
{
get { return measurement; }
set
{
measurement = value;
OnPropertyChanged("Measurement");
}
}
public bool IsApproved
{
get
{
if (measurement.MyValue1 > Rule.Value1Min
&& measurement.MyValue2 > Rule.Value2Min)
{
return true;
}
return false;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class CollectionOfEvaluators : INotifyPropertyChanged
{
public CollectionOfEvaluators(EvaluationRule Rule, ObservableCollection<Measurement> Measurements)
{
this.Rule = Rule;
this.Measurements = Measurements;
var Evaluators = new ObservableCollection<Evaluator>();
foreach (var _measurement in Measurements)
{
var _evaluator = new Evaluator(_measurement, this.Rule);
Evaluators.Add(_evaluator);
}
}
public EvaluationRule Rule;
private ObservableCollection<Measurement> measurements;
public ObservableCollection<Measurement> Measurements
{
get { return measurements; }
set
{
measurements = value;
OnPropertyChanged("Measurements");
}
}
private ObservableCollection<Evaluator> evaluators;
public ObservableCollection<Evaluator> Evaluators
{
get { return evaluators; }
set
{
evaluators = value;
OnPropertyChanged("Evaluators");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
private ObservableCollection<Measurement> measurements;
public ObservableCollection<Measurement> Measurements
{
get { return measurements; }
set
{
measurements = value;
OnPropertyChanged("Measurements");
}
}
private ObservableCollection<EvaluationRule> rules;
public ObservableCollection<EvaluationRule> Rules
{
get { return rules; }
set
{
rules = value;
GetCollection();
}
}
private ObservableCollection<CollectionOfEvaluators> collection;
public ObservableCollection<CollectionOfEvaluators> Collection
{
get { return collection; }
set
{
collection = value;
OnPropertyChanged("Collection");
}
}
public void GetCollection()
{
var Collection = new ObservableCollection<CollectionOfEvaluators>();
foreach (var _rule in rules)
{
var _collection = new CollectionOfEvaluators(_rule, Measurements);
Collection.Add(_collection);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
You must delegate the event. Evaluator should listen to the PropertyChanged event of its aggregated Measurement. The handler of this event can then raise the Evaluator.PropertyChanged event in response:
public class Evaluator : INotifyPropertyChanged
{
public Evaluator(Measurement measurement, EvaluationRule rule)
{
this.Rule = rule;
this.Measurement = measurement;
this.Measurement.PropertyChanged += OnMeasurementPropertyChanged;
}
public void OnMeasurementPropertyChanged(object sender, PropertyChangedEventAgrs e)
{
OnPropertyChanged(nameof(this.Measurement));
}
private Measurement measurement;
public Measurement Measurement
{
get => this.measurement
set
{
this.measurement = value;
OnPropertyChanged(nameof(this.Measurement));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Note that you got a spelling error when naming your class. It's Measurement - you missed an 'a'. Also parameter names should always be lowercase.
I have a custom class inheriting from ObservableCollection and INotifyPropertyChanged (i.e. the custom class also has properties) that serves as a Collection<T> where T also inherits from INotifyPropertyChanged:
public class CustomCollection<T> : ObservableCollection<T>, INotifyPropertyChanged where T: INotifyPropertyChanged {
private string _name;
public string Name {
get {
return _name;
}
set {
if (_name != value) {
_name = value;
NotifyPropertyChanged("Name");
}
}
}
private int _total;
public int Total {
get {
return _total;
}
set {
if (_total != value) {
_total = value;
NotifyPropertyChanged("Total");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName) {
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
And T item class:
public class DataItem : INotifyPropertyChanged {
private string _fname;
public string Fname {
get {
return _fname;
}
set {
if (value != _fname) {
_fname = value;
NotifyPropertyChanged("Fname");
}
}
}
private int_value;
public int Value {
get {
return _value;
}
set {
if (value != _value) {
_value = value;
NotifyPropertyChanged("Value");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName) {
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And the ViewModel:
public class ViewModel : ViewModelBase {
private readonly IService _dataService;
private bool _isLoading;
public bool IsLoading {
get {
return _isLoading;
}
private set {
_isLoading = value;
RaisePropertyChanged("IsLoading");
}
}
private CustomCollection<DataItem> _items;
public CustomCollection<DataItem> Items
{
get
{
return _items;
}
set
{
_items= value;
RaisePropertyChanged("Items");
}
}
public ViewModel(IService dataService) {
_dataService = dataService;
}
public void Refresh() {
if (!this.IsLoading) {
this.IsLoading = true;
_dataService.RefreshData(
this, (error) => {
if (error != null) {
return;
}
if (!IsInDesignMode)
this.IsLoading = false;
}
);
}
}
public void GetData() {
if (Games == null) {
Games = new CustomCollection<DataItem>();
} else {
Games.Clear();
}
if (!this.IsLoading) {
this.IsLoading = true;
_dataService.GetData(
this, (error) => {
if (error != null) {
return;
}
if (!IsInDesignMode)
this.IsLoading = false;
}
);
}
}
And I have bound the CustomCollection<T> to a control in my View (xaml). Everything works fine initially, upon navigating to the page, the ViewModel calls for a DataService to retrieve the data and populate the CustomCollection<T>. However, when refreshing the data, the View is not updated until all the data has been iterated over and refreshed/updated!
Here is the code for the refresh/updated (keep in mind, I'm retrieving the data via a web service, and for the purposes of testing have just manually updated the Value property in DataItem at each passover of the CustomCollection<T>):
public async RefreshData(ViewModel model, Action<Exception> callback) {
if (model.Items == null) return;
// ... retrieve data from web service here (omitted) ...
foreach (DataItem item in retrievedItems) { // loop for each item in retrieved items
DataItem newItem = new DataItem() { Fname = item.Fname, Value = item.Value };
if (model.Items.contains(newItem)) { // override for .Equals in CustomCollection<T> allows for comparison by just Fname property
model.Items[model.Items.IndexOf(newItem)].Value += 10; // manual update
} else {
model.Items.Add(newItem);
}
System.Threading.Thread.Sleep(1000); // 1 second pause to "see" each item updated sequentially...
}
callback(null);
}
So in summary, how can I make it so updating Value of my DataItem will instantly reflect in the View, given my current setup of CustomCollection<DateItem>? Something to do with async perhaps? I mean, when Sleep(1000) gets called, the UI does not hang, maybe this has something to do with it?
Any ideas on how to fix this? As you might have guessed, this issue is also present when first retrieving the data (but is barely noticeable as data is retrieved/processed during the navigation to the View).
Note: I'm using the MVVMLight Toolkit.
Thanks.
I have a simple POCO with a lot of Properties. To simplify things let´s assume the POCO looks like this:
public class Project
{
public int ProjectId {get; set;}
}
Now I want to create an Event that fires when the ProjectId is changed. What I have now is this:
public class Project
{
public int ProjectId {get; set;}
public event EventHandler ProjectChanged;
private void OnProjectChanged(EventArgs args)
{
if (ProjectChanged != null) ProjectChanged (this, args);
}
}
Now I have to extend the Property in order to call the Eventhandler:
public class Project
{
private int mProjectId;
public int ProjectId
{
get { return this.mProjectId;}
set
{
this.mProjectId = value;
this.OnProjectChanged(EventArgs.Empty);
}
}
public event EventHandler ProjectChanged;
private void OnProjectChanged(EventArgs args)
{
if (ProjectChanged != null) ProjectChanged(this, args);
}
}
I wonder if there is an easier way to attach the Eventhandler. Maybe some kind of annotation ? Like
public class Project
{
[OnChange("OnProjectChanged", EventArgs.Empty)]
public int ProjectId {get; set;}
public event EventHandler ProjectChanged;
private void OnProjectChanged(EventArgs args)
{
if (ProjectChanged != null) ProjectChanged (this, args);
}
}
Taking some ideas from the question I posted in the comments, you could implement an abstract base class which implements INotifyPropertyChanged. Each time you declare a property, you call SetField to trigger the PropertyChanged event.
This avoids two things:
1) Explicitly implementing INotifyProperty changed each time using the abstract class
2) Explicitly implementing a method to trigger the event each time, shortening the set code to one line.
abstract class ModelBase : INotifyPropertyChanged
{
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;
}
}
class Project : ModelBase
{
private string _name;
public string Name
{
get { return _name; }
set { SetField(ref _name, value, "Name"); }
}
}
class TestRunner
{
public TestRunner()
{
Project p = new Project();
p.PropertyChanged += (o, e) =>
{
// Changed
};
p.Name = "Test";
}
}
i'm trying to implement INotifyPropertyChanged within singelton class.
Here is my code:
public class plc : INotifyPropertyChanged
{
private static plc instance;
public plc()
{
}
public static plc Instance
{
get
{
if (instance == null)
{
instance = new plc();
}
return instance;
}
set
{
instance = value;
}
}
private static string _plcIp{get; set;}
public string plcIp
{
get
{
return _plcIp;
OnPropertyChanged();
}
set
{
_plcIp = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
I'm getting error unreachable code deleted and of course NotifyPropertyChange isn't working
It's because you are calling OnPropertyChanged(); after you return _plcIp;.
It should be called after you set the value. i.e.:
public string plcIp
{
get
{
return _plcIp;
}
set
{
if (value != _plcIp)
{
_plcIp = value;
OnPropertyChanged();
}
}
}
You should also check that the value is actually changing in the setter before raising the event.
There are several issues in your code:
if you are implementing singleton, then constructor of class should be private
use fields instead of private properties
properties should not be static (you are using singleton)
verify if property value really changed before raising OnPropertyChanged event
raise event before returning property value
use PascalNames for class name and properties names
raise event from setter instead of getter
Code:
public class Plc : INotifyPropertyChanged {
private static Plc _instance;
private Plc() { } // constructor should be private
public static Plc Instance
{
get
{
if (_instance == null)
_instance = new Plc();
return _instance;
} // you don't need setter
}
private string _plcIp; // instance field instead of static property
public string PlcIp
{
get { return _plcIp; }
set
{
if (_plcIp == value)
return; // check if value changed
_plcIp = value; // change value
OnPropertyChanged(); // raise event
}
}
// ...
}
This contains the error :
public string plcIp
{
get
{
return _plcIp;
OnPropertyChanged(); //This row..
}
set { _plcIp = value; }
}
It's in the Set method you want the update in the UI, not when you get the value.
Something like this :
public string plcIp
{
get { return _plcIp; }
set { _plcIp = value; OnPropertyChanged(); }
}
I can't seem to find a simple, concrete explanation of how to bind controls in a WinForms app to nested objects using data binding. For example:
class MyObject : INotifyPropertyChanged
{
private string _Name;
public string Name
{
get { return _Name; }
set
{
_Name = value;
OnPropertyChanged("Name");
}
}
private MyInner _Inner;
public MyInner Inner
{
get { return _Inner; }
set
{
_Inner = value;
OnPropertyChanged("Inner");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
class MyInner : INotifyPropertyChanged
{
private string _SomeValue;
public string SomeValue
{
get { return _SomeValue; }
set
{
_SomeValue = value;
OnPropertyChanged("SomeValue");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Now imagine a form with just two textboxes, the first for Name and the second for Inner.SomeValue. I'm easily able to get binding to work against Name, but Inner.SomeValue is flaky. If I populate the object and then set up the binding, it shows Inner.SomeValue in the textbox but I can't edit it. If I start from a fresh object without initializing Inner, I can't seem to get data to stick in Inner.SomeValue.
I've checked all over MSDN, all over StackOverflow, and dozens of searches with different keywords. Everyone wants to talk about binding to databases or DataGrids, and most examples are written in XAML.
Update: I've tried Marc's full test harness and have partial success. If I hit the "all change!" button, I seem to be able to write back to the inner object. However, starting with MyObject.Inner null, it doesn't know how to create an inner object. I think for now, I can work around it by just making sure my inner references are always set to a valid object. Still, I can't help feeling like I'm missing something :)
Hmm - an excellent question; I've done lots of data-binding to objects, and I would have sworn that what you are doing should work; but indeed it is very reluctant to notice the change to the inner object. I've managed to get it working by:
var outer = new BindingSource { DataSource = myObject };
var inner = new BindingSource(outer, "Inner");
txtName.DataBindings.Add("Text", outer, "Name");
txtSomeValue.DataBindings.Add("Text", inner, "SomeValue");
Not ideal, but it works. Btw; you might find the following utility methods useful:
public static class EventUtils {
public static void SafeInvoke(this EventHandler handler, object sender) {
if(handler != null) handler(sender, EventArgs.Empty);
}
public static void SafeInvoke(this PropertyChangedEventHandler handler,
object sender, string propertyName) {
if(handler != null) handler(sender,
new PropertyChangedEventArgs(propertyName));
}
}
Then you can have:
class MyObject : INotifyPropertyChanged
{
private string _Name;
public string Name { get { return _Name; } set {
_Name = value; PropertyChanged.SafeInvoke(this,"Name"); } }
private MyInner _Inner;
public MyInner Inner { get { return _Inner; } set {
_Inner = value; PropertyChanged.SafeInvoke(this,"Inner"); } }
public event PropertyChangedEventHandler PropertyChanged;
}
class MyInner : INotifyPropertyChanged
{
private string _SomeValue;
public string SomeValue { get { return _SomeValue; } set {
_SomeValue = value; PropertyChanged.SafeInvoke(this, "SomeValue"); } }
public event PropertyChangedEventHandler PropertyChanged;
}
And in the bargain it fixes the (slim) chance of a null-exception (race-condition).
Full test rig, to iron out kinks (from comments):
using System;
using System.ComponentModel;
using System.Windows.Forms;
public static class EventUtils {
public static void SafeInvoke(this PropertyChangedEventHandler handler, object sender, string propertyName) {
if(handler != null) handler(sender, new PropertyChangedEventArgs(propertyName));
}
}
class MyObject : INotifyPropertyChanged
{
private string _Name;
public string Name { get { return _Name; } set { _Name = value; PropertyChanged.SafeInvoke(this,"Name"); } }
private MyInner _Inner;
public MyInner Inner { get { return _Inner; } set { _Inner = value; PropertyChanged.SafeInvoke(this,"Inner"); } }
public event PropertyChangedEventHandler PropertyChanged;
}
class MyInner : INotifyPropertyChanged
{
private string _SomeValue;
public string SomeValue { get { return _SomeValue; } set { _SomeValue = value; PropertyChanged.SafeInvoke(this, "SomeValue"); } }
public event PropertyChangedEventHandler PropertyChanged;
}
static class Program
{
[STAThread]
public static void Main() {
var myObject = new MyObject();
myObject.Name = "old name";
// optionally start with a default
//myObject.Inner = new MyInner();
//myObject.Inner.SomeValue = "old inner value";
Application.EnableVisualStyles();
using (Form form = new Form())
using (TextBox txtName = new TextBox())
using (TextBox txtSomeValue = new TextBox())
using (Button btnInit = new Button())
{
var outer = new BindingSource { DataSource = myObject };
var inner = new BindingSource(outer, "Inner");
txtName.DataBindings.Add("Text", outer, "Name");
txtSomeValue.DataBindings.Add("Text", inner, "SomeValue");
btnInit.Text = "all change!";
btnInit.Click += delegate
{
myObject.Name = "new name";
var newInner = new MyInner();
newInner.SomeValue = "new inner value";
myObject.Inner = newInner;
};
txtName.Dock = txtSomeValue.Dock = btnInit.Dock = DockStyle.Top;
form.Controls.AddRange(new Control[] { btnInit, txtSomeValue, txtName });
Application.Run(form);
}
}
}