Updating several properties when another property changes? - c#

In my model class, I have several lists and other properties. One of these properties is called CurrentIteration and constantly gets updated. When this gets updated, I want other properties to update themselves to the element of a corresponding list that is the index of CurrentIteration. I thought all's I needed to include was an OnPropertyChanged event for the properties I want to update in the setter of CurrentIteration. However, they don't seem to be getting called.
public class VehicleModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private List<double> _nowTime = new List<double>();
public List<double> NowTime
{
get { return this._nowTime; }
set { this._nowTime = value; OnPropertyChanged("Nowtime"); }
}
private List<double> _VehLat = new List<double>();
public List<double> VehLat
{
get { return this._VehLat; }
set { this._VehLat = value; OnPropertyChanged("VehLat"); }
}
private List<double> _VehLong = new List<double>();
public List<double> VehLong
{
get { return _VehLong; }
set { _VehLong = value; OnPropertyChanged("VehLong"); }
}
//non-list properties
private int _currentIteration;
public int CurrentIteration //used to hold current index of the list of data fields
{
get { return _currentIteration; }
set
{
_currentIteration = value;
OnPropertyChanged("CurrentIteration");
OnPropertyChanged("CurrentVehLat");
OnPropertyChanged("CurrentVehLong");
}
}
private double _currentVehLat;
public double CurrentVehLat
{
get { return _currentVehLat; }
set { _currentVehLat = VehLat[CurrentIteration]; OnPropertyChanged("CurrentVehLat"); }
}
private double _currentVehLong;
public double CurrentVehLong
{
get { return _currentVehLong; }
set { _currentVehLong = VehLong[CurrentIteration]; OnPropertyChanged("CurrentVehLong"); }
}
public void SetData(int i)
{
CurrentIteration = i;
}
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
CurrentIteration DOES get updated properly, but the rest do not. The setters get skipped over completely. I'm almost positive it's something simple and my understanding of the setters in this case is wrong, but I'm not sure what it is exactly.
Edit: Here's an example of one of the bindings in XAML:
Text="{Binding Path=CurrentVehLong,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"

Raising the property change notification just says "These properties have changed, re-query their values". Meaning it calls the get accessor method.
It looks like you are expecting it to call the set method, which is not going to happen because you are not setting the value.
Try removing the backing property and just access the array value directly, like this :
public double CurrentVehLong
{
get { return VehLong[CurrentIteration];; }
set
{
VehLong[CurrentIteration] = value;
OnPropertyChanged("CurrentVehLong");
}
}
Now when you call OnPropertyChanged("CurrentVehLong"), it will re-query the get accessor for this property, and update the value based on the CurrentIteration. I also altered the set method so it would make more sense, and you'd be able to use it to set the value if that's what you want to do elsewhere.

Related

In C#, how to trigger the PropertyChanged event when an elements in a array gets modified

I have a array property, in which I want to notify whenever any elements of that array gets changed.
private double[] _OffsetAngles = new double[3];
public double[] OffsetAngles
{
get { return _OffsetAngles; }
set
{
_OffsetAngles = value;
NotifyPropertyChanged();
}
}
if any of the elements of OffsetAngles gets changed, I want to get the notification.
i.e. if I set OffsetAngles[1] = 20; //Trigger should happen.
if I set OffsetAngles[0] = 40; //Trigger should happen again.
Imagine that you are not using double but some class. And then that the filed of that class has changed. Should the array raise property changed? Surely not. So there are multiple solutions that you may consider:
use ObservableCollection and its SetItem method
use ObservableCollection and instead of assigning value remove and insert the value
instead of double use some class that implements INotifyPropertyChanged and when the dobule changes raise this event, it should be right approach if the purpose is data binding
recreate the Array each time (cumbersome and inefficient but still works)
So as others have mentioned, in your case you fire the NotifyPropertyChanged() when the array itself is changed, not any element of the array.
If you want the elements to be able to fire the event you would have to implement a class like:
public class NotifyingData<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private T _Data;
public T Data
{
get { return _Data; }
set { _Data = value; NotifyPropertyChanged(); }
}
}
and then populate your array with that class:
_OffsetAngles[0] = new NotifyingData<double> { Data = 10 };
I don't have access to VS right now, so there might be some errors, but this should be the right concept for you.
This example shows how to create and bind to a collection that derives from the ObservableCollection class, which is a collection class that provides notifications when items get added or removed.
public class NameList : ObservableCollection<PersonName>
{
public NameList() : base()
{
Add(new PersonName("Willa", "Cather"));
Add(new PersonName("Isak", "Dinesen"));
Add(new PersonName("Victor", "Hugo"));
Add(new PersonName("Jules", "Verne"));
}
}
public class PersonName
{
private string firstName;
private string lastName;
public PersonName(string first, string last)
{
this.firstName = first;
this.lastName = last;
}
public string FirstName
{
get { return firstName; }
set { firstName = value; }
}
public string LastName
{
get { return lastName; }
set { lastName = value; }
}
}
The objects in your collection must satisfy the requirements described in the Binding Sources Overview. In particular, if you are using OneWay or TwoWay (for example, you want your UI to update when the source properties change dynamically), you must implement a suitable property changed notification mechanism such as the INotifyPropertyChanged interface.
Ref: https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/how-to-create-and-bind-to-an-observablecollection
I had the same issue a while ago. I had to update a DataTable whenever the data changed and that's how I solved it on my program :
public ObservableCollection<KeyStroke> keyList = new ObservableCollection<KeyStroke>();
public class KeyStroke : INotifyPropertyChanged
{
// KeyStroke class storing data about each key and how many types it received
private int id;
private int numPress;
public KeyStroke(int id, int numPress)
{
Id = id;
NumPress = numPress;
}
public int Id
{
get => id;
set
{
id = value;
NotifyPropertyChanged("Id");
}
}
public int NumPress
{
get { return this.numPress; }
set
{
this.numPress = value;
NotifyPropertyChanged("NumPress");
}
}
public event PropertyChangedEventHandler PropertyChanged; //This handle the propertyChanged
private void NotifyPropertyChanged(String propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); //This is the WPF code for the DataGrid but you can replace it by whatever you need
}
}
This should help you. You can also put conditions inside the getter/setters of the properties but I think it's not really pretty

Access to ObservableCollection

I'm asking how we can acceed to an ObservableCollection.
Actually I'm working on a project and i have to collect the checked elements in an ObservableCollection in order to copy these elements to a PDF file.
public Class FianlElements
{
private int chapAr;
public int ChapAr
{
get { return chapAr; }
set
{
chapAr = value;
OnPropertyChanged("ChaprAr");
OnPropertyChanged("Article");
}
}
private string article;
public string Article
{
get { return article; }
set
{
article = value;
OnPropertyChanged("ChapAr");
OnPropertyChanged("Article");
}
}
private float somme;
public float Somme
{
get { return somme; }
set
{
somme = value;
OnPropertyChanged("Somme");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertySomme)
{
if (PropertyChanged != null)
PropertyChanged(this, new
PropertyChangedEventArgs(propertySomme));
}
}
Actually this is the class of the type of ObservableCollection
My declaration of the ObservableCollection is here
public ObservableCollection<FinalSelection> LesElem { get; set; }
I have another ObservableCollection
public ObservableCollection<ListBoxArticle> LesArticles { get; set; }
This one is Binded to a ListBox which contains CheckBox and TextBox
like this
so I want to copy only the checked elements to "LesElem"
So how can I get access to this ObservableCollection
Thanx
Let's imagine you are interested in LesArticle which are ticked.
AND
You have an IsChecked property on LesArticle.
AND
This is bound to the IsChecked property of the checkbox.
The list of checked ones would be
var checkedList = LesArticle.Where(x => x.IsChecked == true).ToList();
Observablecollection can take a List as it's constructor. So you can do:
FinalElem = new ObservableCollection<ListBoxArticle>(checkedList);

Can Indexers of a property work in xaml binding in windows phone 8?

something i do wrong? anyone gives some suggestions
according to msdn
Indexers of a property can be specified within square brackets following the property name where the indexer is applied. For instance, the clause Path=ShoppingCart[0] sets the binding to the index that corresponds to how your property's internal indexing handles the literal string "0". Multiple indexers are also supported.
i put Indexers of a property in my xaml
<Image Source="{Binding ImagePathList[0]}" Height="50" Width="50" Grid.Row="0" Grid.Column="0" VerticalAlignment="Top" Margin="0,7,7,0" Grid.RowSpan="2">
i do not give the viewmodel code because i am pretty sure ListImagePathList have data.
EDIT*
more detail: ImagePathList[0] is a web image url
EDIT FOR Brendan
model is Article
public class Article : INotifyPropertyChanged
{
private long _Id;
public long ID
{
get { return _Id; }
set
{
if (_Id != value)
{
_Id = value;
NotifyPropertyChanged();
}
}
}
private string _subject;
public string Subject
{
get
{
return _subject;
}
set
{
if (_subject != value)
{
_subject = value;
NotifyPropertyChanged();
}
}
}
private string _words;
public string Words
{
get
{
return _words;
}
set
{
if (_words != value)
{
_words = value;
NotifyPropertyChanged();
}
}
}
private DateTime _publishDate;
public DateTime PublishDate
{
get
{ return _publishDate; }
set
{
if (_publishDate != value)
{
_publishDate = value;
NotifyPropertyChanged();
}
}
}
public List<string> ImagePathList = new List<string>();
private string _firstImage;
public string FirstImage
{
get
{
return _firstImage;
}
set
{
if (_firstImage != value)
{
_firstImage = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
ArticleViewModel is in below; All data returned from network is correct!
public class ArticleListViewModel : INotifyPropertyChanged
{
public ArticleListViewModel()
{
this.ArticleCollection = new ObservableCollection<Article>();
}
public ObservableCollection<Article> ArticleCollection
{
get;
private set;
}
public void LoadPage(int pageNumber)
{
if (pageNumber == 1)
{
this.ArticleCollection.Clear();
}
IsLoading = true;
ReadArticleList(pageNumber);
}
private async void ReadArticleList(int pageNumber)
{
try
{
List<Article> articleList = new List<Article>();
articleList = await CollectionHttpClient.GetArticlesByPageAsync(pageNumber);
this.ArticleCollection.Add(item);
}
}
catch(Exception ex)
{
if (ex.HResult == -2146233088 && ex.Message.Equals("Response status code does not indicate success: 404 ()."))
{
MessageBox.Show("The network is not set right. Internet cannot be accessed.");
}
else
{
MessageBox.Show("sorry, no data.");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The XAML code you show is fine.
There may be a problem with the DataContext. Maybe the page DataContext has not been set? Or maybe the the DataContext has changed e.g. inside an ItemTemplate
Otherise the problem is probably to do with the bound property. Try the following
private ObservableCollection<string> _imagePathList = new ObservableCollection<string>();
public ObservableCollection<string> ImagePathList {
get { return this._imagePathList; }
set {
if (this._imagePathList != value)
{
this._imagePathList = value;
// I'm going to assume you have the NotifyPropertyChanged
// method defined on the view-model
this.NotifyPropertyChanged();
}
}
}
ObservableCollection is in System.Collections.ObjectModel and is like List but if elements are added/removed then the PropertyChanged event is fired. Also note that any bound property must have a get associated with it for it to work at all.
Another possibility is that ImagePathList was not assigned to or is empty - in which case, make sure you assign to it!
In case you have not yet implemented the NotifyPropertyChanged method, here it is ...
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
You will also need to add INotifyPropertyChanged interface to the containing class e.g.
public class MyViewModelClass : INoftifyPropertyChanged
{
...
}
I think the problem concerns the place where your images come from. As it is said at MSDN:
You can set the Source by specifying an absolute URL (e.g. http://contoso.com/myPicture.jpg) or specify a URL relative to the XAP file of your application.
You can set this property in XAML, but in this case you are setting the property as a URI. The XAML behavior relies on underlying type conversion that processes the string as a URI, and calls the BitmapImage(Uri) constructor. This in turn potentially requests a stream from that URI and returns the image source object.
Just for test - place some images in your project and then set ImagePathList to them. See if that works (it should as I think). Or see if you can get BitmapImage(ImagePathList);
It's hard for me to say now (as I don't know how your ImagePathList looks like) what could be a reason of the failure. But if I were you, I would test this.
I would advise to use a property (or you can use converter which will also do the job):
// a property in your class
public Uri FirstImage
{
get
{
return new Uri(ImagePathList.FirstOrDefault(), UriKind.Absolute);
}
}
On the other hand if you are downloading images to IsolatedStorage then you will have to use another property, that will load BitmapImage from IS:
public BitmapImage FirstImage // getter - BitmapImage
{
get
{
if (String.IsNullOrEmpty(ImagePath[0])) return null;
BitmapImage temp = new BitmapImage();
using (IsolatedStorageFile ISF = IsolatedStorageFile.GetUserStoreForApplication())
using (IsolatedStorageFileStream file = ISF.OpenFile(ImagePath[0], FileMode.Open, FileAccess.Read))
temp.SetSource(file);
return temp;
}
}
Hope this helps.

WPF binding with where clause possible?

If I had an Observable collection like so :
public ObservableCollection<SpecialPriceRow> SpecialPriceRows = new ObservableCollection<SpecialPriceRow>();
SpecialPriceRow class :
public class SpecialPriceRow : INotifyPropertyChanged
{
public enum ChangeStatus
{
Original,
Added,
ToDelete,
Edited
}
public ChangeStatus Status { get; set; }
public string PartNo { get; set; }
private decimal _price;
public decimal Price
{
get
{
return _price;
}
set
{
if (value != _price)
{
_price = value;
Status = ChangeStatus.Edited;
OnPropertyChanged("Status");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
Would it be possible for me to bind a Label in the XAML to the count of objects that are say ... Added? So I could get something like this :
Where green is the count of "Added" objects within the collection. How would I go about doing something like this?
I've written up a ViewModel which will perform the desired functionality you are looking for.
class VM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<SpecialPriceRow> _SpecialPriceRows = new ObservableCollection<SpecialPriceRow>();
private int _Original = 0;
private int _Added = 0;
private int _ToDelete = 0;
private int _Edited = 0;
public VM()
{
PropertyChanged = new PropertyChangedEventHandler(VM_PropertyChanged);
//The following lines in the constructor just initialize the SpecialPriceRows.
//The important thing to take away from this is setting the PropertyChangedEventHandler to point to the UpdateStatuses() function.
for (int i = 0; i < 12; i++)
{
SpecialPriceRow s = new SpecialPriceRow();
s.PropertyChanged += new PropertyChangedEventHandler(SpecialPriceRow_PropertyChanged);
SpecialPriceRows.Add(s);
}
for (int j = 0; j < 12; j+=2)
SpecialPriceRows[j].Price = 20;
}
private void VM_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
}
private void SpecialPriceRow_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Status")
UpdateStatuses();
}
public ObservableCollection<SpecialPriceRow> SpecialPriceRows
{
get
{
return _SpecialPriceRows;
}
}
private void UpdateStatuses()
{
int original = 0, added = 0, todelete = 0, edited = 0;
foreach (SpecialPriceRow SPR in SpecialPriceRows)
{
switch (SPR.Status)
{
case SpecialPriceRow.ChangeStatus.Original:
original++;
break;
case SpecialPriceRow.ChangeStatus.Added:
added++;
break;
case SpecialPriceRow.ChangeStatus.ToDelete:
todelete++;
break;
case SpecialPriceRow.ChangeStatus.Edited:
edited++;
break;
default:
break;
}
}
Original = original;
Added = added;
ToDelete = todelete;
Edited = edited;
}
public int Original
{
get
{
return _Original;
}
set
{
_Original = value;
PropertyChanged(this, new PropertyChangedEventArgs("Original"));
}
}
public int Added
{
get
{
return _Added;
}
set
{
_Added = value;
PropertyChanged(this, new PropertyChangedEventArgs("Added"));
}
}
public int ToDelete
{
get
{
return _ToDelete;
}
set
{
_ToDelete = value;
PropertyChanged(this, new PropertyChangedEventArgs("ToDelete"));
}
}
public int Edited
{
get
{
return _Edited;
}
set
{
_Edited = value;
PropertyChanged(this, new PropertyChangedEventArgs("Edited"));
}
}
}
Take note of the comments in the constructor. You need to point the PropertyChanged event of each SpecialPriceRow to the UpdateStatuses function in order for this to work properly.
Now all you need to do is bind your labels to the appropriate properties in the ViewModel.
If your SpecialPriceRows list becomes very large, you may want to consider calculating the status counts a bit differently. Currently, it is iterating through the entire list every time one instance is updated. For this to perform better, you may want to keep the old value of the status in the SpecialPriceRow class and every time an update occurs, increment the new status count and decrement the old one.
I'm not aware of any builtin functionality to do this. I would create a custom property in your data context class that does the counting and bind to this.
Something like this:
public int AddedCount
{
get
{
return SpecialPriceRows.Where(r => r.Status == ChangeStatus.Added).Count();
}
}
Then whenever an item is changed or added you need to explicitly raise the property changed for this:
public void AddItem()
{
...
OnPropertyChanged("AddedCount");
}
Then you only need to bind in your XAML code like:
<TextBlock Text="{Binding AddedCount}" />
You may need to subscribe to the change events in your collection to know when an item changes.
Alternative:
You can also create a ListCollectionView with a specific filter and bind to its Count property:
AddedItems = new ListCollectionView(TestItems);
AddedItems.Filter = r => ((SpecialPriceRow)r).Status == ChangeStatus.Added;
In your XAML you would then bind to the Count property of this:
<TextBlock Text="{Binding AddedItems.Count}" />
This has the advantage that it will automatically keep track of added and removed items in the collection and update itself. You have to refresh it manually though when the property of an item changes which affects the filter.

Winforms Databinding object containing a List<T>

I'm having trouble with a situation that I know must be pretty common, so I'm hoping the solution is simple. I have an object that contains a List<> of objects. It also has some properties that reflect aggregate data on the objects in the List<> (actually a BindingList<> so I can bind to it). On my form, I have a DataGridView for the List, and some other fields for the aggregate data. I can't figure out how to trigger a refresh of the aggregate data when values in the DataGridView get changed.
I have tried raising a PropertyChanged event when the properties of the objects in the List are changed, but that doesn't seem to refresh the display of the aggregate data. If I access an aggregate property (eg, display it in a messagebox), the textbox on the main form is refreshed.
Here's some simplified code to illustrate what I'm trying to do:
namespace WindowsFormsApplication1 {
public class Person {
public int Age {
get;
set;
}
public String Name {
get;
set;
}
}
public class Roster : INotifyPropertyChanged {
public BindingList<Person> People {
get;
set;
}
public Roster () {
People = new BindingList<Person>();
}
private int totalage;
public int TotalAge {
get {
calcAges();
return totalage;
}
set {
totalage = value;
NotifyPropertyChanged("TotalAge");
}
}
private void calcAges () {
int total = 0;
foreach ( Person p in People ) {
total += p.Age;
}
TotalAge = total;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged ( String info ) {
if ( PropertyChanged != null ) {
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
}
}
The calcAges method and the TotalAge property look very suspicious.
First, TotalAge should be read-only. If you allow it to be public and writable, what is the logic for changing the components that make up the age?
Second, every time you get the value, you are firing the PropertyChanged event, which is not good.
Your Roster class should look like this:
public class Roster : INotifyPropertyChanged {
public Roster ()
{
// Set the binding list, this triggers the appropriate
// event binding which would be gotten if the BindingList
// was set on assignment.
People = new BindingList<Person>();
}
// The list of people.
BindingList<Person> people = null;
public BindingList<Person> People
{
get
{
return people;
}
set
{
// If there is a list, then remove the delegate.
if (people != null)
{
// Remove the delegate.
people.ListChanged -= OnListChanged;
}
/* Perform error check here */
people = value;
// Bind to the ListChangedEvent.
// Use lambda syntax if LINQ is available.
people.ListChanged += OnListChanged;
// Technically, the People property changed, so that
// property changed event should be fired.
NotifyPropertyChanged("People");
// Calculate the total age now, since the
// whole list was reassigned.
CalculateTotalAge();
}
}
private void OnListChanged(object sender, ListChangedEventArgs e)
{
// Just calculate the total age.
CalculateTotalAge();
}
private void CalculateTotalAge()
{
// Store the old total age.
int oldTotalAge = totalage;
// If you can use LINQ, change this to:
// totalage = people.Sum(p => p.Age);
// Set the total age to 0.
totalage = 0;
// Sum.
foreach (Person p in People) {
totalage += p.Age;
}
// If the total age has changed, then fire the event.
if (totalage != oldTotalAge)
{
// Fire the property notify changed event.
NotifyPropertyChanged("TotalAge");
}
}
private int totalage = 0;
public int TotalAge
{
get
{
return totalage;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged ( String info ) {
if ( PropertyChanged != null ) {
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Now, when the properties in the list items are changed, the parent object will fire the property changed event, and anything bound to it should change as well.
I believe you may be looking for something like this
ITypedList
Also a Google Search of ITypedList leads you to a few nice blogs on how to implement.
When I use an ORM I typically have to do a few of these for nice datagrid binding and presentation.

Categories