It's a closed network and I don't have a VS on the internet computer, so sorry in advance for how the code looks.
Here is part of my view model:
public class Elements : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(String p)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
private int _CardNumber;
private int _CardCode;
private Card _CurrentlySelectedRow;
public int CardNumber
{
get { return _CardNumber; }
set
{
if (value != _CardNumber)
{
_CardNumber = value;
OnPropertyChanged("CardNumber");
}
}
}
public int CardCode
{
get { return _CardCode; }
set
{
if (value != _CardCode)
{
_CardCode = value;
OnPropertyChanged("CardCode");
}
}
}
public Card CurrentlySelectedRow
{
get { return _CurrentlySelectedRow; }
set
{
if (value != _CurrentlySelectedRow)
{
_CurrentlySelectedRow= value;
OnPropertyChanged("CurrentlySelectedRow");
}
}
}
public class Card
{
public int CardNumber { get; set; }
public int CardCode { get; set; }
public Card() {}
public Card(int CardNumber_, int CardCode_)
{
CardNumber = CardNumber_;
CardCode = CardCode_;
}
}
private ObservableCollection<Card> _Cards { get; set; }
public ObservableCollection<Card> Cards
{
get
{
if (_Cards == null)
_Cards = new ObservableCollection<Card>();
return _Cards;
}
set
{
_Cards = value;
OnPropertyChanged("Cards");
}
}
public bool UpdateCard()
{
//selected row in gridcontrol is binded to CurrentlySelectedRow
CurrentlySelectedRow.CardNumber = CardNumber;
CurrentlySelectedRow.CardCode = CardCode ;
}
public bool AddCard()
{
Cards.Add(new Card(CardNumber, CardCode );
}
}
Since the grid is not editable, the row is updated in external form, in which controls are binded to cardNumber and cardCode, and when pressing OK - the UpdateCard() is called (if we in update), and the AddCard called if we in add.
The AddCard works - the grid updates.
The UpdateCard - updates the list, but the grid isn't updated...
Maybe you can see were is the problem?...
My intial thought on this is that the update to the CurrentlySelectedRow.CardNumber and CurrentlySelectedRow.CardCode wont call OnPropertyChanged().
With ObservableCollection the UI will be updated if any items are added or removed but will not if any changes are made to the properties in an item.
Because no call is made, the UI doesn't know that these properties have changed.
You would have to make your Card class implement INotifyPropertyChanged similarly to Elements.
What I would normally do is create a BaseObject class that simply implements INotifyPropertyChanged. This class is then inherited by all of my other classes:
public abstract BaseObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string p)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
}
Then just change the properties in your Card class to call OnPropertyChanged.
Edit following on from comments:
Now that you've got INotifyPropertyChanged implemented you could try rebinding whatever is bound to Elements.CardNumber and Elements.CardCode to CurrentlySelectedCard.CardNumber and CurrentlySelectedCard.CardCode respectively. This would potentially allow you to delete the properties from Elements.
This will, however depend on the rest of your code.
Related
I have two buttons and bind their property to two properties of a data object.
But every property is updated when I call PropertyChanged of the data object.
public partial class Form1 : Form
{
private DataClass data = new DataClass();
public Form1()
{
InitializeComponent();
ButtonA.DataBindings.Add("Text", data, "DataA");
ButtonB.DataBindings.Add("Text", data, "DataB");
ButtonB.Click += new EventHandler(OnButtonBClicked);
}
private void OnButtonBClicked(object sender, EventArgs e)
{
data.DataA += "1";
data.DataB += "1";
data.Notify("DataB");
}
}
public class DataClass : INotifyPropertyChanged
{
public string DataA { get; set; }
public string DataB { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public DataClass() {}
public void Notify(string property_name)
{
PropertyChanged(this, new PropertyChangedEventArgs(property_name));
}
}
When I press ButtonB (which means I call PropertyChanged(this, new PropertyChangedEventArgs("DataB"))), both ButtonA and ButtonB show new text.
If I call PropertyChanged(this, new PropertyChangedEventArgs("DataA")), both buttons are updated.
If I don't change value of DataA / DataB and just call PropertyChanged(this, new PropertyChangedEventArgs("DataB")), still both buttons are updated (can be noticed by breakpoint debugging).
If I call PropertyChanged(this, new PropertyChangedEventArgs("QQQ")), then no button is updated.
PropertyChangedEventArgs has a property named propertyName, I thought it's used to specify one property to notify but it doesn't.
In my real code, DataB changes much more frequently than DataA. I don't want to update ButtonA each time DataB is changed, it takes too much time.
Question: why would this happen? When a data source property is changed, how can I only update properties really connected to it?
(All code is .Net Framework 4.7.1 on Windows.)
#Jimi's method works.Simple and effective.I put each property in a shell class and bind data to the shell:
public class MyProperty<T>: INotifyPropertyChanged
{
public T Content { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public MyProperty(T _content)
{
Content = _content;
}
public void Notify()
{
PropertyChanged(this, new PropertyChangedEventArgs("Content"));
}
}
public class DataClass
{
public MyProperty<string> DataA = new MyProperty<string>("");
public MyProperty<string> DataB = new MyProperty<string>("");
public DataClass() {}
}
But in this way I must use DataA.Content+="1" instead of DataA+="1" every where.
I decide to use a base class to create all shells.But my real DataClass must inherit from other class and C# don't support multi-inherit.So I have to use a extension class.
public class BindHandle<T> : INotifyPropertyChanged
{
public T Content { get { return (T)parent.GetType().GetProperty(prop_name).GetValue(parent); } }
private object parent;
private string prop_name;
public event PropertyChangedEventHandler PropertyChanged;
public BindHandle(object _parent, string _prop_name)
{
parent = _parent;
prop_name = _prop_name;
}
public void NotifyChange()
{
PropertyChanged(this, new PropertyChangedEventArgs("Content"));
}
}
public interface IBindHandleProvider
{
BindHandleProvider provider { get; set; }
}
public class BindHandleProvider
{
private Dictionary<string, object> handle_map = new Dictionary<string, object>();
public BindHandle<T> GetBindHandle<T>(object obj,string property_name)
{
if (!handle_map.ContainsKey(property_name))
handle_map.Add(property_name, new BindHandle<T>(obj, property_name));
return (BindHandle<T>)handle_map[property_name];
}
public void NotifyChange<T>(string property_name)
{
if (handle_map.ContainsKey(property_name))
((BindHandle<T>)handle_map[property_name]).NotifyChange();
}
}
public static class BindHandleProviderExtension
{
public static void NotifyChange<T>(this IBindHandleProvider obj, string property_name)
{
obj.provider.NotifyChange<T>(property_name);
}
public static BindHandle<T> GetBindHandle<T>(this IBindHandleProvider obj, string property_name)
{
return obj.provider.GetBindHandle<T>(obj,property_name);
}
}
public class DataClass:IBindHandleProvider
{
public BindHandleProvider provider { get; set; } = new BindHandleProvider();
public string DataA { get; set; } = "";
public string DataB { get; set; } = "";
public DataClass(){ }
}
Then bind it like
ButtonA.DataBindings.Add("Text", data.GetBindHandle<string>("DataA"), "Content");
And notify like
data.NotifyChange<string>("DataB");
It's kinda complex but works well.
I have an observable collection that is shared between different viewmodels.
public class UserInput1ViewModel: INotifyPropertyChanged
{
public ObservableCollection<ParamClass> ParamColl { get; set; }
public UserInput1ViewModel(<ParamClass> paramColl)
{
this.ParamColl = paramColl;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
private void UpdateCollection()
{
this.ParamList = PerformCalculations();
}
}
public class ParamClass
{
public double Property1 { get; set; }
public double Property2 { get; set; }
public double Property3 { get; set; }
... ...
... ...
public double Property19 { get; set; }
}
The function PerformCalculations() will execute, but it will not update the all the properties inside the observable collection. I have learned that you cannot do that with observable collection https://stackoverflow.com/a/9984424/4387406.
So, this is what I am currently doing.
private void UpdateCollection()
{
var output = PerformCalculations();
for(int i = 0; i < output.Count(); i++)
{
this.ParamColl[i].Property1 = output[i].Property1;
this.ParamColl[i].Property2 = output[i].Property2;
... ...
... ...
this.ParamColl[i].Property19 = output[i].Property19;
}
}
My question is: is there a better way of sharing observable collection?
Many thanks in advance.
If you want the GUI to update whenever a property of an instance in a list changes, you should implement the INotifyPropertyChanged in the instance class, just as you have done in your ViewModel.
You haven't shown what your ParamClass looks like so I'm using a Person class in place. You could do the same thing as you've done in your ViewModel.
public class Person : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set { name = value; OnPropertyChanged("Name"); }
}
private int age;
public int Age
{
get { return age; }
set { age = value; OnPropertyChanged("Age"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
Now, if even a single property in any of the instances is changed it will be reflected on your GUI.
Since you're using WPF, there's quite a few good MVVM tool-kits out there that will do a lot of this for you. For instance, MVVM Light Toolkit is one such examples. There's many others out there.
Is there a way to trigger some kind of OnPropertyChanged event for a computed property that uses a property of a "child entity" in a collection?
A small example:
I have a simple WPF application with a DataGrid showing Customer properties.
I am using Entity Framework 5, CodeFirst approach, so I wrote my classes manually with my own INotifyPropertyChanged implementation.
public partial class Customer : INotifyPropertyChanged
{
private string _firstName;
public virtual string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
OnPropertyChanged("FirstName");
OnPropertyChanged("FullName");
}
}
private string _lastName;
public virtual string LastName
{
get { return _lastName; }
set
{
_lastName = value;
OnPropertyChanged("LastName");
OnPropertyChanged("FullName");
}
}
public virtual ICollection<Car> Cars { get; set; }
public virtual ICollection<Invoice> Invoices { get; set; }
...
}
Now in that same class I created 3 computed properties:
public string FullName
{
get { return (FirstName + " " + LastName).Trim(); }
}
public int TotalCars
{
get
{
return Cars.Count();
}
}
public int TotalOpenInvoices
{
get
{
if (Invoices != null)
return (from i in Invoices
where i.PayedInFull == false
select i).Count();
else return 0;
}
}
The FullName is automatically updated in the DataGrid because I'm calling OnPropertyChanged("FullName");
I found an example of the INotifyCollectionChanged implementation that I can probably use to auto update the TotalCars when something is added to or removed from the ICollection:
http://www.dotnetfunda.com/articles/article886-change-notification-for-objects-and-collections.aspx
But what is the best approach to trigger the OnPropertyChange("TotalOpenInvoices") when a property (PayedInFull) inside the ICollection (Invoices) changes?
Doing something like OnPropertyChanged("Customer.TotalOpenInvoices"); in the Invoice class doesn't seem to do the trick... :)
Do you have control of what goes inside your collections? If you do, you can create a Parent property on your Invoice object and when it is added to the collection, set the parent to your Customer. Then when PaidInFull gets set, run your Customer.OnPropertyChanged("TotalOpenInvoices") or call a method on the Customer object to recalculate your invoices.
Enforcing parent-child relationship in C# and .Net
This assumes that Invoice and Customer both implement IPropertyChanged. Simply change your collection to an ObservableCollection, and watch the CollectionChanged property.
When new Invoices are added, hook up an event handler to the PropertyChanged event of that Invoice. When an item is removed from the collection, remove that event handler.
Then, just call your NotifyPropertyChanged function on your TotalOpenInvoices property.
For example (not completely tested, but it should be close):
Invoice
public class Invoice : INotifyPropertyChanged
{
private bool payedInFull = false;
public bool PayedInFull
{
get { return payedInFull; }
set
{
payedInFull = value;
NotifyPropertyChanged("PayedInFull");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
Customer
public class Customer : INotifyPropertyChanged
{
public Customer()
{
this.Invoices.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Invoices_CollectionChanged);
}
ObservableCollection<Invoice> Invoices
{
get;
set;
}
public int TotalOpenInvoices
{
get
{
if (Invoices != null)
return (from i in Invoices
where i.PayedInFull == false
select i).Count();
else return 0;
}
}
void Invoices_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
foreach (Invoice invoice in e.NewItems)
{
invoice.PropertyChanged += new PropertyChangedEventHandler(invoice_PropertyChanged);
NotifyPropertyChanged("TotalOpenInvoices");
}
foreach (Invoice invoice in e.OldItems)
{
invoice.PropertyChanged -= new PropertyChangedEventHandler(invoice_PropertyChanged);
NotifyPropertyChanged("TotalOpenInvoices");
}
}
void invoice_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "PayedInFull")
NotifyPropertyChanged("TotalOpenInvoices");
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
Note that each class could be abstracted to a single class that implements INotifyPropertyChanged, such as a ViewModelBase class, but that is not necessarily required for this example.
I am working in vs2010.
I have created a DataGrid which is bounded to
ObservableCollection List;
the Class_CMD looks like this :
public class Class_RetrieveCommand
{
public string CMD { get; set; }
public bool C_R_CMD { get; set; }
public bool S_CMD { get; set; }
public bool C_S_CMD { get; set; }
}
i have 4 delegates which i pass to another window, and this window needs to update the list during runtime. During the runtime i can see the string column of the grid updated all the time but the DataGridCheckBoxColumns are never updated.
the DataGrid -
<DataGrid Background="Transparent" x:Name="DataGrid_CMD" Width="450" MaxHeight="450" Height="Auto" ItemsSource="{Binding}" AutoGenerateColumns="True">
one of the delegates which updates the bool is -
public void UpdateC_S_CMD(string Msg)
{
foreach (Class_CMD c in List.ToArray())
{
if (c.CMD.Equals(Msg))
c.C_S_CMD = true;
}
}
I don't understand why the bool columns are not updated....
can anyone help please?
thanks.
Your class Class_RetrieveCommand needs to implement the INotifyPropertyChanged interface. Otherwise the individual rows databound to the instances of the class don't know that the underlying properties have changed. If you change it to something like this, you should see the changes reflected in your grid:
public class Class_RetrieveCommand : INotifyPropertyChanged
{
private bool _cRCmd;
private bool _cSCmd;
private string _cmd;
private bool _sCmd;
public string CMD
{
get { return _cmd; }
set
{
_cmd = value;
InvokePropertyChanged(new PropertyChangedEventArgs("CMD"));
}
}
public bool C_R_CMD
{
get { return _cRCmd; }
set
{
_cRCmd = value;
InvokePropertyChanged(new PropertyChangedEventArgs("C_R_CMD"));
}
}
public bool S_CMD
{
get { return _sCmd; }
set
{
_sCmd = value;
InvokePropertyChanged(new PropertyChangedEventArgs("S_CMD"));
}
}
public bool C_S_CMD
{
get { return _cSCmd; }
set
{
_cSCmd = value;
InvokePropertyChanged(new PropertyChangedEventArgs("C_S_CMD"));
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
public void InvokePropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, e);
}
}
}
You should implement INotifyPropertyChanged in the Class_RetrieveCommand like this:
public class Class_RetrieveCommand : INotifyPropertyChanged
{
private string _CMD;
public string CMD
{
get { return _CMD; }
set { _CMD = value; OnPropertyChanged("CMD"); }
}
... similar for the other properties
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Unfortunately you can't use auto properties anymore then (except you resort to proxygenerators).
How do I change the value of TotalPublicationsRead, when the Read property of a Publication has changed?
public class Report
{
public ObservableCollection<Publication> Publications { get; set; }
public int TotalPublicationsRead { get; set; }
}
public class Publication : INotifyPropertyChanged
{
private bool read;
public bool Read
{
get { return this.read; }
set
{
if (this.read!= value)
{
this.publications = value;
OnPropertyChanged("Read");
}
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
private void OnPropertyChanged(string property)
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
Thanks in advance.
If you're trying to do what I think you are, then I'd change the TotalPublicationsRead property and forget about the events. In the code below I just count the items in the list where the Publication has been Read.
The way you were trying to do it you would have to have an event handler for when the ObserableCollection changed. Then you would have to attach an event handler to the PropertyChanged event which would increment or decrement the TotalPublicationsRead property. I'm sure it would work, but it would be a lot more complicated.
public class Report
{
public List<Publication> Publications { get; set; }
public int TotalPublicationsRead
{
get
{
return this.Publications.Count(p => p.Read);
}
}
}
public class Publication : INotifyPropertyChanged
{
private bool read;
public bool Read
{
get { return this.read; }
set { this.read = value; }
}
}
You can use Dependency Property.
Please check details at:
http://www.wpftutorial.net/dependencyproperties.html
http://msdn.microsoft.com/en-us/library/ms745795(v=vs.110).aspx