DataBinding on Getter Only Property That Concats 2 variables - c#

I have the following classes
public abstract class Contact
{
public abstract string FullName { get; }
public abstract string FullName_LastNameFirst { get; }
}
public class PersonContact
{
private string firstName;
private string lastName;
public override string FullName => firstName + " " + lastName;
public override string FullName_LastNameFirst => lastName + ", " + firstName;
}
public class BusinessContact
{
private string name;
public override string FullName => name;
public override string FullName_LastNameFirst => name;
}
These classes extend INotifyPropertyChanged (not shown) and include public properties wrapping the private variables that trigger OnPropertyChanged.
The question is if I bind in WPF to the FullName or FullName_LastNameFirst properties how can I have them update when either of the properties that they are wrapping are changed.

When you change underlying private fields (firstName, lastName or name in BusinessContact) - call
OnPropertyChanged("FullName");
OnPropertyChanged("FullName_LastNameFirst");
WPF data binding will subscribe to PropertyChanged event of your object and will listen to change notifications of corresponding property. Since your property has no setter in which you can call OnPropertyChanged - you need to call it explicitly when any underlying data changes.

You surely have properties for your Person. If you have a ForeName property you can implement it like:
private string _ForeName;
public string ForeName
{
get
{ return _ForeName; }
set
{
if (_ForeName != value)
{
_ForeName = value;
OnPropertyChanged(nameof(this.ForeName));
OnPropertyChanged(nameof(this.FullName));
}
}
}
As you can see, a PropertyChanged event will be fired, if the value of ForeName is changed. You can make the LastName property similar.
An other solution is, if the person class listens to its own PropertyChanged:
public Person()
{
this.PropertyChanged += this.Person_PropertyChanged;
}
private void Person_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(LastName))
{
this.OnPropertyChanged(nameof(FullName));
}
if (e.PropertyName == nameof(ForeName))
{
this.OnPropertyChanged(nameof(FullName));
}
}
This second solution is useful if you have generated classes, and you cannot change the property setters. Then you can make a partial class, and handle the property changes.

The answers from Evk and lvoros are technically correct.
However, you can save yourself from having to write all this boilerplate code by using the Fody ProperyChanged library (available via Nuget).
A class written as
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string GivenNames { get; set; }
public string FamilyName { get; set; }
public string FullName => $"{GivenNames} {FamilyName}";
}
Will actually be compiled as
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
string givenNames;
public string GivenNames
{
get => givenNames;
set
{
if (value != givenNames)
{
givenNames = value;
OnPropertyChanged("GivenNames");
OnPropertyChanged("FullName");
}
}
}
string familyName;
public string FamilyName
{
get => familyName;
set
{
if (value != familyName)
{
familyName = value;
OnPropertyChanged("FamilyName");
OnPropertyChanged("FullName");
}
}
}
public string FullName => $"{GivenNames} {FamilyName}";
public virtual void OnPropertyChanged(string propertyName)
{
var propertyChanged = PropertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Fody will automatically generate OnPropertyChanged() calls for any dependent calculated properties in addition to the direct property wrappers.
I'm not normally a fan of programs working by "magic", but Fody is my one expception.

Related

Identifying when a property changed [duplicate]

This question already has answers here:
How to check if an object has changed?
(7 answers)
Closed 4 years ago.
Is it possible to know when an property is modified within the entity itself?
E.g.:
public class StudentEntity{
public string studentId { get; set; }
public string studentStatus { get; set; }
public string getStatusChangeDate{
get
{
//if studentStatus change then return date
}
}
}
The INotifyPropertyChanged interface is used to notify clients, typically binding clients, that a property value has changed.
For example, consider a Person object with a property called FirstName. To provide generic property-change notification, the Person type implements the INotifyPropertyChanged interface and raises a PropertyChanged event when FirstName is changed.
For change notification to occur in a binding between a bound client and a data source, your bound type should either:
Implement the INotifyPropertyChanged interface (preferred).
Provide a change event for each property of the bound type.
Rewrite your code:
public class StudentEntity : INotifyPropertyChanged
{
private string studentIdValue;
public string StudentId
{
get { return this.studentIdValue; }
set
{
if(value != this.studentIdValue)
{
this.studentIdValue = value;
this.OnPropertyChanged(nameof(this.StudentId));
}
}
}
private string studentStatusValue;
public string StudentStatus
{
get { return this.studentStatusValue; }
set
{
if(value != this.studentStatusValue)
{
this.studentStatusValue= value;
this.OnPropertyChanged(nameof(this.StudentStatus));
}
}
}
public string StatusChangeDate { get; set; }
public StudentEntity(string studentId, string studentStatus)
{
// Don't invoke property-setters from the ctor to avoid raising the event prematurely), instead set the backing fields directly:
this.studentIdValue = studentId;
this.studentStatusValue = studentStatus;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
class Program
{
static void Main(string[] args)
{
var person = new StudentEntity("101", "Accept");
person.PropertyChanged += Person_PropertyChanged;
person.StudentStatus = "Reject";
Console.ReadLine();
}
private static void Person_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
StudentEntity studentEntity = sender as StudentEntity;
if (e.PropertyName == "StudentStatus")
{
studentEntity.getStatusChangeDate = DateTime.Now.ToString();
}
}
}
You can use a method to set the value. This means each time EF loads the record, it won't get overwritten: but of course you have to remember to call the method and not set the property directly.
public class StudentEntity {
public string studentId { get; set; }
public string studentStatus { get; set; }
public DateTime studentStatusChanged { get; set; }
public void SetStudentStatus(string status) {
studentStatus = status;
studentStatusChanged = DateTime.Now;
}
}
Example: https://dotnetfiddle.net/kF8VZR

Notify change on Image source binded on custom class property

I'm having trouble with a simple image source binding.
I have a class that store the path to the image file (and other stuff) which look like this:
public class Ekta {
...
public string PATHMED { get; set; }
public string FICMED { get; set; }
public string FULLPATH { get { return PATHMED + FICMED; } }
...
}
I have the following property in my window:
public Ekta mainImg { get; set; }
And in the xaml, the binding is done like this:
<Image Source="{Binding Path=mainImg.FULLPATH}"/>
This work well when I set mainImg's value the first time (Before InitializeComponent() is called), but when I update it (mainImg = e; where e is an instance of Ekta) the UI doesn't change.
Am I missing something ? Is it the right way to bind an image source to a custom item ?
I suggest to make a base class named Notifier and use it for any class which needs INotifyPropertyChanged implementation
public class Notifier : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then
public class Ekta : Notifier
{
private string _PATHMED;
public string PATHMED
{
get { return _PATHMED; }
set
{
_PATHMED = value;
RaisePropertyChanged();
RaisePropertyChanged("FULLPATH");
}
}
private string _FICMED;
public string FICMED
{
get { return _FICMED; }
set
{
_FICMED = value;
RaisePropertyChanged();
RaisePropertyChanged("FULLPATH");
}
}
public string FULLPATH
{
get { return PATHMED + FICMED; }
}
}

React on object changed in ObservableCollection

I have an ObservableCollection<Person> in my viewmodel. This is bound as an ItemsSource to a DataGrid in the view. The class Person only has threeProperties:
public class Person : ViewModelBase
{
private Guid id;
public Guid Id
{
get { return this.id; }
set
{
this.id = value;
OnPropertyChanged("Id");
}
}
private string firstname;
public string Firstname
{
get { return this.firstname; }
set
{
this.firstname = value;
OnPropertyChanged("Firstname");
}
}
private string lastname;
public string Lastname
{
get { return this.lastname; }
set
{
this.lastname = value;
OnPropertyChanged("Lastname");
}
}
}
The class ViewModelBase implements INotifyPropertyChanged.
The items in the collection are updated perfect if I add or remove an entry in the dategrid. The item is then also removed in the collection.
My problem is that the content of an person-item is updated, but I don't know how I can react on this.
Do I have to add an event or something else to the person-class to get informed or is there another way to do this?
Implement INotifyPropertyChanged interface on your class Person so that any change in Person properties gets reflected back on UI.
Sample -
public class Person : INotifyPropertyChanged
{
private Guid id;
public Guid Id
{
get { return id; }
private set
{
if(id != value)
{
id = value;
NotifyPropertyChanged("Id");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}

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?

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.

Data binding to custom object

I have a simple custom class (Person), which I want to bind to a label as a whole (not to separate properties of this class). The label should just present whatever the Person.ToString() returns (in this case FirstName + LastName).
How do I properly bind it using the person as a Source.
How do I make sure that any change in one of the properties of the Person will be reflected in the label?
public class Person : INotifyPropertyChanged {
private string firstName;
public string FirstName {
get {
return firstName;
}
set {
firstName = value;
OnPropertyChanged("FirstName");
}
}
private string lastName;
public string LastName {
get {
return lastName;
}
set {
lastName = value;
OnPropertyChanged("LastName");
}
}
public override string ToString() {
return FirstName + " " + LastName;
}
private void OnPropertyChanged(string name) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
public Window1() {
myPerson = new Person() {
FirstName = "AAA",
LastName = "BBB"};
InitializeComponent();
}
public Person MyPerson {
get {
return myPerson;
}
set {
myPerson = value;
}
}
Label Content="{Binding Source=MyPerson}"
Create a new property FullName which returns the full name and raise PropertyChanged for FullName in the setters of FirstName and LastName as well. You should never bind to the object itself.

Categories