I am developing a WPF application in C# 4.5.2 in Visual Studio 2017. Within the application I have a custom object that gets rolled into an ObservableCollection<T> for later processing. I want to be able to handle this CollectionChanged event if/when necessary.
I added the appropriate decoration to the class (: INotifyPropertyChanged), added the event and handler, and attributed each property in my object with the Interface PropertyChanged:
public class OrderLineItem : INotifyPropertyChanged
{
private int _lineItemNumber;
public int LineItemNumber
{
get => _lineItemNumber;
set { _lineItemNumber = value; OnPropertyChanged(); }
}
private int _quantity;
public int Quantity
{
get => _quantity;
set { _quantity = value; OnPropertyChanged(); }
}
private string _partNumber;
public string PartNumber
{
get => _partNumber;
set { _partNumber = value; OnPropertyChanged(); }
}
private Hinge _hinged;
public Hinge Hinging
{
get => _hinged;
set { _hinged = value; OnPropertyChanged(); }
}
private Finish _finished;
public Finish Finished
{
get => _finished;
set { _finished = value; OnPropertyChanged(); }
}
private decimal _unitPrice;
public decimal UnitPrice
{
get => _unitPrice;
set { _unitPrice = value; OnPropertyChanged(); }
}
private decimal _modifyPrice;
public decimal ModifyPrice
{
get => _modifyPrice;
set { _modifyPrice = value; OnPropertyChanged(); }
}
private decimal _extendedPrice;
public decimal ExtendedPrice
{
get => _extendedPrice;
set { _extendedPrice = value; OnPropertyChanged(); }
}
private List<string> _modifications;
public List<string> Modifications
{
get => _modifications;
set { _modifications = value; OnPropertyChanged(); }
}
private CabinetType _type;
public CabinetType Type
{
get => _type;
set { _type = value; OnPropertyChanged(); }
}
private string _display;
public string Display
{
get => _display;
set { _display = value; OnPropertyChanged(); }
}
public enum Hinge { None = 0, L, R, BD }
public enum Finish { None = 0, L, R, B }
public OrderLineItem()
{
LineItemNumber = -1;
Quantity = -1;
PartNumber = string.Empty;
Hinging = Hinge.None;
Finished = Finish.None;
UnitPrice = 0.00m;
ModifyPrice = 0.00m;
ExtendedPrice = 0.00m;
Modifications = new List<string>();
Type = CabinetType.None;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
In my MainWindow.xaml.cs file I have added the .CollectionChanged handler to my ObservableCollection<T> and the .PropertyChanged handler to my object:
private void AddedItemsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (OrderLineItem newItem in e.NewItems)
{
_addedItems.Add(newItem);
newItem.PropertyChanged += OnItemPropertyChanged;
}
}
if (e.OldItems != null)
{
foreach (OrderLineItem oldItem in e.OldItems)
{
_addedItems.Add(oldItem);
oldItem.PropertyChanged -= OnItemPropertyChanged;
}
}
}
private void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
OrderLineItem item = sender as OrderLineItem;
if (item != null) _addedItems.Add(item);
}
Any time I add an item to the collection I add the PropertyChanged handler to it:
...
item.PropertyChanged += ItemOnPropertyChanged;
...
This collection gets displayed in a DataGrid via the .ItemsSource property.
The issue in this question's title only happens after implementing the INotifyPropertyChanged and its associated methods/interfaces etc.
The stack trace I was able to get does not provide me with any useful information (line number, file, method etc) to attempt to debug this. I did enable all of the debugging options to make sure I wasn't ignoring any potential exceptions. Here is a pastebin of the exception. The line number indicated is this:
AppDomain.CurrentDomain.UnhandledException +=
(sender, args) => throw new Exception("Unhandled exception: " + args.ExceptionObject);
If I do not have this line, or comment it out this is a pastebin of the exception I get instead.
Any idea of how I should proceed trying to solve this, besides not implementing the CollectionChanged event?
There seem to be a relation with INotifyPropertyChanged and Binding rules but I don't know which one.
I got into the same issue: implementing INotifyPropertyChanged on a PropertyGrid item triggered the Key cannot be null binding error. By the way, it was one of the most useless and random error logs I have seen.
Cannot save value from target back to source.
BindingExpression:[info on an unrelated parent binding...]
ArgumentNullException:'System.ArgumentNullException: Key cannot be null.
After some time, I found out about weird Binding behaviors in old wpf versions where adding Path= to the bindings could fix it, but that wasn't it.
I actually had a Binding in code-behind like so:
void GetBinding(PropertyItem propertyItem)
{
return new Binding($"({propertyItem.PropertyName})")
{
//[Source, Mode, etc...]
}
}
And the code worked for both properties and attached properties, but stopped working right after implementing the property notifier on this class. Maybe a more strict rule got triggered and flagged my property as not being an "attached" one, but it worked before without the interface.
So, I removed the parenthesis and it works now.
return new Binding($"{propertyItem.PropertyName}")
Looking at your exception, it seems like the problem is related to searching for a null key from your HybridDictionary.
I'm guessing that the INotifyPropertyChanged implementation is simply triggering this HybridDictionary[null] lookup, and is unrelated to the fundamental problem.
At a higher level, this seems like something the VS debugger should jump you straight to, so this seems odd, and I would recommend digging around your environment to make sure debugging is working properly.
Related
I do have a WPF binding question here.
Following Setup:
I do have a class (ActionService) having a name and a ObservableCollection of subitems (also a class named Step). A Step has a flag that shows if the Step is allready done (IsDone).
I bind a form to the ActionService and display all kind of things.
Everything works as aspected and i have just the essential parts in my snippet.
Now I need one more thing that i can not get work. I want the ActionService to know by binding how many of its Steps are open (IsDone == false). I you open a childform with one of the steps and change the IsDone-State, the mother form should get the new count on the fly.
And I'm to dumb to get a correct solution on the way ;-)
Thanks for your help or a best practise.
public class ActionService : BaseObject
{
public ActionService()
{
}
private String name;
public String Name
{
get { return this.name; }
set
{
this.name = value;
raisePropertyChanged("Name");
}
}
public ObservableCollection<Step> actionsteps;
public ObservableCollection<Step> ActionSteps
{
get { return this.actionsteps; }
set
{
this.actionsteps = value;
raisePropertyChanged("ActionSteps");
}
}
}
public class Step : BaseObject
{
public Step()
{
}
private String description;
public String Description
{
get { return this.description; }
set
{
this.description = value;
raisePropertyChanged("Description");
}
}
private Boolean isdone;
public Boolean IsDone
{
get { return this.isdone; }
set
{
this.isdone = value;
raisePropertyChanged("IsDone");
}
}
}
public class BaseObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void raisePropertyChanged(String parPropertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(parPropertyName));
}
}
}
You can create a new property in your ActionService class:
public bool IsDone
{
get
{
return ActionSteps.Count(x => x.IsDone) == ActionSteps.Count;
}
}
If the count of Steps in the ActionSteps list where the IsDone property is true is equal to the number of Steps in the ActionSteps list, then return true, else, return false.
To subscribe to the Steps property changed event, when you add an item to the collection, you simply need to subscribe to the PropertyChanged event:
//Create the item and subscribe to propertychanged.
Step item = new Step();
item.PropertyChanged += item_PropertyChanged;
//Add the item to the list.
ActionSteps.Add(item);
And your method will look like this:
void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsDone")
raisePropertyChanged("IsDone");
}
I don't understand why when I update a object, my bound controls do not update.
The data displays fine initially, but when I want to refresh the data displayed in the UI nothing happens when I update the object. The object updates fine. The ViewModel does use INotifyPropertyChanged on all fields.
However if I update individual items directly, I can update my UI. As commented below.
I guess I've made a school boy error somewhere here?
UPDATE: I've added the model to the question. While I understand the answers, I don't understand how to implement it. Attempted to implement a collection changed event without success. Can I have some pointers please?
public partial class CisArrivalsPanel : UserControl
{
private ApiDataArrivalsDepartures _theArrivalsDepartures;
public CisArrivalsPanel()
{
InitializeComponent();
_theArrivalsDepartures = new ApiDataArrivalsDepartures();
_theArrivalsDepartures = MakeQuery.LiveTrainArrivals("London Kings Cross");
this.DataContext = _theArrivalsDepartures;
ListBoxArr.ItemsSource = _theArrivalsDepartures.StationMovementList;
}
void Reload()
{
//This does not update the UI**
_theArrivalsDepartures = MakeQuery.LiveTrainArrivals("London Paddington");
//However this (when uncommented, and I comment out the above line) does update the UI**
//_theArrivalsDepartures.StationMovementList[0].OriginName = "test";
//_theArrivalsDepartures.StationMovementList[0].Platform = "0";
//_theArrivalsDepartures.StationMovementList[0].BestArrivalEstimateMins = "999";
//_theArrivalsDepartures.StationName = "test";
}
private void StationHeader_OnPreviewMouseDown(object sender, MouseButtonEventArgs e)
{
Reload();
Debug.WriteLine(_theArrivalsDepartures.StationName);
foreach (var a in _theArrivalsDepartures.StationMovementList)
{
Debug.WriteLine(a.OriginName);
Debug.WriteLine(a.BestArrivalEstimateMins);
}
}
}
EDIT : Added Model
public class ApiDataArrivalsDepartures : INotifyPropertyChanged
{
private string _stationName;
[JsonProperty(PropertyName = "station_name")]
public string StationName {
get
{
return _stationName;
}
set
{
_stationName = value;
NotifyPropertyChanged("StationName");
}
}
private List<StationListOfMovements> _stationMovementList;
public List<StationListOfMovements> StationMovementList
{
get
{
return _stationMovementList;
}
set
{
_stationMovementList = value;
NotifyPropertyChanged("StationMovementList");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
public class StationListOfMovements : INotifyPropertyChanged
{
private string _originName;
[JsonProperty(PropertyName = "origin_name")]
public string OriginName {
get
{
return _originName;
}
set
{
_originName = value;
NotifyPropertyChanged("OriginName");
}
}
[JsonProperty(PropertyName = "destination_name")]
public string DestinationName { get; set; }
private string _platform;
[JsonProperty(PropertyName = "Platform")]
public string Platform {
get
{
return _platform;
}
set
{
_platform = value;
NotifyPropertyChanged("Platform");
}
}
private string _bestArrivalEstimateMins;
[JsonProperty(PropertyName = "best_arrival_estimate_mins")]
public string BestArrivalEstimateMins {
get
{
return _bestArrivalEstimateMins;
}
set
{
_bestArrivalEstimateMins = value;
NotifyPropertyChanged("BestArrivalEstimateMins");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
There are two pieces here pertaining to your collection (technically three):
If you want a new collection to propagate, the collection property has to raise PropertyChanged (sounds like it does)
If you want add/remove on the collection to propagate, you need to use a collection that implements INotifyCollectionChanged. ObservableCollection is a good choice.
If you want changes to the items in the container to propagate, then those items need to implement INotifyPropertyChanged and raise the PropertyChanged event.
Make sure all those are covered, and the changes should appear on the UI as you expect.
You should update the DataContext and ItemsSource too.
void Reload()
{
//This does not update the UI**
_theArrivalsDepartures = MakeQuery.LiveTrainArrivals("London Paddington");
DataContext = theArrivalsDepartures;
ListBoxArr.ItemsSource = _theArrivalsDepartures.StationMovementList;
}
Use for the collection ObservableCollection , this class notify the ui when change to the collection occurred
your reload function works because the there is PropertyChanged on all the fields include this one
it notify the ui and reload the correct collection
I have an Item class with two properties "quantity" and "price" and implements INotifyPropertyChanged
public class Item:INotifyPropertyChanged
{
private event PropertyChangedEventHandler _propertyChanged;
public event PropertyChangedEventHandler PropertyChanged
{
add { _propertyChanged += value; }
remove { _propertyChanged -= value; }
}
void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (_propertyChanged != null)
{
_propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public int QuantityOnHand
{
get
{
return this._quantityOnHand;
}
set
{
if (value > 0)
{
this._quantityOnHand = value;
NotifyPropertyChanged();
}
else
{
throw new System.ArgumentException("Quantity must be a positive value!");
}
}
}
.....
}
And I have a collection class of items named "Inventory" with a property of TotalRetailPrice:
public class Inventory {
private List<Item> _inventoryList = new LinkedList<Item>();
public decimal TotalRetailPrice
{
get
{
decimal totalRetailPrice = 0M;
foreach (var item in _inventoryList)
{
totalRetailPrice += item.QuantityOnHand * item.RetailPrice;
}
return totalRetailPrice;
}
}
I am trying to find out a way to automatically update this property TotalRetailPrice, whenever I change either the quantity or the price of any item(s) in the list. How can I do that? Right now with my code, every time I tried to get this totalRetailPrice property, I will have to go through the list and recalculate it.
thanks!
Since the interface INotifyPropertyChanged exposes an event called PropertyChanged you can just subscribe to that in the 'inventory' class.
You will also want to listen for changed events in the list since you will need to know when items are added/removed so you can add/remove event handlers as necessary. I'd suggest using ObservableCollection<T> as this supports some 'collection changed' events. Is there any reason you are using LinkedList<T>?
e.g.
public class Inventory
{
private ObservableCollection<Item> _inventoryList = new ObservableCollection<Item>();
public decimal _total;
// You probably want INPC for the property here too so it can update any UI elements bound to it
public decimal Total { get { return _total; } set { _total = value; } }
// Constructor
public Inventory()
{
WireUpCollection();
}
private void WireUpCollection()
{
// Listen for change events
_inventoryList.CollectionChanged += CollectionChanged;
}
private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// Check what was added - I'll leave this to you, the e.NewItems are the items that
// have been added, e.OldItems are those that have been removed
// Here's a contrived example for when an item is added.
// Don't forget to also remove event handlers using inpc.PropertyChanged -= Collection_PropertyChanged;
var inpc = e.NewItems[0] as INotifyPropertyChanged;
if(inpc != null)
inpc.PropertyChanged += Collection_PropertyChanged;
}
private void Collection_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
RecalculateTotal();
}
private void RecalculateTotal()
{
// Your original code here which should feed the backing field
}
}
Check out the MSDN docs here:
http://msdn.microsoft.com/en-us/library/ms668604(v=vs.110).aspx
For info on ObservableCollection<T>. The events section is what you are after. Also note you can use anonymous functions to handle the events if you prefer the syntax or want to capture variables in a certain scope etc. It helps to understand them fully (not sure what's available for Java as I've not really touched it save a couple of Android mess-about projects) so it might be worth reading up as there are a small caveats to be aware of when capturing, but that's another story!
e.g.
_inventoryList.CollectionChanged += (o,e) =>
{
// Anonymous method body here
// o = the first param (object sender), e = args (NotifyCollectionChangedEventArgs e)
};
As it was told in another answer in order to "observe" collection items you have to use ObservableCollection which has a special event CollectionChanged. It is raised when the list is changed somehow (item added, removed, replaced). However, that event won't be raised (obviously) when some property of the existing item is changed, for example, inventory.InventoryList[0].QuantityOnHand = 8;.
So, to get the solution worked you need to observe both the collection changes (CollectionChanged event) and each collection item changes (PropertyChanged event). Though, implementing that logic correctly is not so easy. The Charlen answer is just a sketch of the solution but not the full solution.
It might be easier to use DependenciesTracking lib which solves the issue. See the example below:
public class Item : INotifyPropertyChanged
{
private int _quantityOnHand;
private decimal _retailPrice;
public event PropertyChangedEventHandler? PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string? propertyName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
public int QuantityOnHand
{
get => this._quantityOnHand;
set
{
if (_quantityOnHand == value) return;
if (value <= 0)
throw new ArgumentOutOfRangeException(nameof(value), value, "QuantityOnHand must be a positive value");
_quantityOnHand = value;
OnPropertyChanged();
}
}
public decimal RetailPrice
{
get => _retailPrice;
set
{
if (_retailPrice == value) return;
if (value <= 0)
throw new ArgumentOutOfRangeException(nameof(value), value, "RetailPrice must be a positive value");
_retailPrice = value;
OnPropertyChanged();
}
}
}
public class Inventory : INotifyPropertyChanged
{
private static readonly IDependenciesMap<Inventory> _dependenciesMap =
new DependenciesMap<Inventory>()
.AddDependency(i => i.TotalRetailPrice,
i => i.InventoryList?.Sum(item => item.QuantityOnHand * item.RetailPrice) ?? 0.0m,
i => i.InventoryList.EachElement().QuantityOnHand, i => i.InventoryList.EachElement().RetailPrice);
private ObservableCollection<Item>? _inventoryList;
private decimal _totalRetailPrice;
public event PropertyChangedEventHandler? PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string? propertyName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
public ObservableCollection<Item>? InventoryList
{
get => _inventoryList;
set
{
if (_inventoryList == value) return;
_inventoryList = value;
OnPropertyChanged();
}
}
public decimal TotalRetailPrice
{
get => _totalRetailPrice;
private set
{
if (value == _totalRetailPrice) return;
_totalRetailPrice = value;
OnPropertyChanged();
}
}
public Inventory()
{
_dependenciesMap.StartTracking(this);
}
}
public class Tests_SO_20767981
{
[SetUp]
public void Setup()
{
}
[Test]
public void Test_SO_20767981()
{
var inventory = new Inventory();
Assert.That(inventory.TotalRetailPrice, Is.EqualTo(0.0m));
inventory.InventoryList = new ObservableCollection<Item>();
Assert.That(inventory.TotalRetailPrice, Is.EqualTo(0.0m));
inventory.InventoryList.Add(new Item { QuantityOnHand = 3, RetailPrice = 5 });
Assert.That(inventory.TotalRetailPrice, Is.EqualTo(15));
inventory.InventoryList.Add(new Item { QuantityOnHand = 1, RetailPrice = 7 });
Assert.That(inventory.TotalRetailPrice, Is.EqualTo(22));
inventory.InventoryList[0].QuantityOnHand = 8;
Assert.That(inventory.TotalRetailPrice, Is.EqualTo(47));
inventory.InventoryList[0].RetailPrice = 12;
Assert.That(inventory.TotalRetailPrice, Is.EqualTo(103));
inventory.InventoryList.RemoveAt(1);
Assert.That(inventory.TotalRetailPrice, Is.EqualTo(96));
var newInventoryList = new ObservableCollection<Item>
{
new Item() { QuantityOnHand = 10, RetailPrice = 0.5m},
new Item() { QuantityOnHand = 6, RetailPrice = 1.5m}
};
inventory.InventoryList = newInventoryList;
Assert.That(inventory.TotalRetailPrice, Is.EqualTo(14m));
}
}
Lets say I have sales price, down payment amount, down payment percent and loan amount. When any of these properties are changed by the user the others need to be updated to reflect the new values. How do you deal with this type of infinite property change events?
When flow control is necessary across multiple attributes, I'll institute a flow control variable - a boolean - and in each property that's being changed, I'll add a test to see if I'm under flow control or not.
private bool controlledChange = false;
public property int MyVal1
{
set
{
_myVal1 = value;
if(!controlledChange)
{
controlledChange = true;
MyVal2 -= 1;
controlledChange = false;
}
}
}
public property int MyVal2
{
set
{
_myVal2 = value;
if(!controlledChange)
{
controlledChange = true;
MyVal1 += 1;
controlledChange = false;
}
}
}
This way whatever property is changed can initiate changes across the other properties, but when they get changed, they will no NOT to initiate their own set of changes in turn.
You should also look to make as many of those properties read only as possible, if they can have calculated results, so that you limit how the object can be changed.
THe easiest way is to only raise a change event if the property has really changed:
public decimal SalePrice {
get{
return salePrice;
}
set {
if (salePrice != value) {
salePrice = value; // putting as first statement prevents the setter
// to be entered again ...
RaiseSalePriceChange();
// Set other properties
}
}
}
I'm not sure I completely understand, since I don't know what you mean by 'infinite'
This may be a good use case for actually backing your properties with fields. That way, you can trigger events on Property sets, but internally set the fields one at a time without triggering N events.
class MyClass
{
private string m_Name;
private int m_SomeValue;
public string Name
{
get { return m_Name; }
set
{
if (value != m_Name)
{
m_Name = value;
m_SomeValue++;
// Raise Event
}
}
}
public int SomeValue
{
get { return m_SomeValue; }
set
{
if (m_SomeValue != value)
{
m_SomeValue = value;
// Raise Event
}
}
}
If INotifyPropertyChanged is really needed to notify external objects, so I would just centralise everything. Like this:
private double salesPrice;
private double downPaymentAmount;
private double downPaymentPercent;
private double loanAmount;
public double SalesPrice
{
get
{
return salesPrice;
}
set
{
if (salesPrice != value)
{
salesPrice = value;
// maybe you would rather use a RecalculateForSalePriceChanged() method
RecalculateDownPaymentAmount();
RecalculateDownPaymentPercent();
RecalculateLoanAmount();
propertiesChanged();
}
}
}
public double DownPaymentAmount
{
get
{
return downPaymentAmount;
}
set
{
if (downPaymentAmount != value)
{
downPaymentAmount = value;
// see above
RecalculateDownPaymentPercent();
RecalculateLoanAmount();
RecalculateSalesPrice();
propertiesChanged();
}
}
}
public double DownPaymentPercent
{
get
{
return downPaymentPercent;
}
set
{
if (downPaymentPercent != value)
{
downPaymentPercent = value;
// see above
RecalculateDownPaymentAmount();
RecalculateLoanAmount();
RecalculateSalesPrice();
propertiesChanged();
}
}
}
public double LoanAmount
{
get
{
return loanAmount;
}
set
{
if (loanAmount != value)
{
loanAmount = value;
// see above
RecalculateDownPaymentAmount();
RecalculateDownPaymentPercent();
RecalculateSalesPrice();
propertiesChanged();
}
}
}
private void propertiesChanged()
{
RaisePropertyChanged("SalesPrice", "DownPaymentAmount", "DownPaymentPercent", "LoanAmount");
}
Maybe you can concentrate the recalculations in less methods or even a single one, but I do not know how you calculate them. But certainly you have to keep a specific order when recalculating the values.
Since they only operate on fields and do not change the properties, there will be no PropertyChanged-feedback-loop.
Hope this helps and I did not misunderstood what you wanted.
What the OP wanted was something like following
class A : INotifyPropertyChanged
{
private int field1;
public int Property1
{
get { return field1; }
set
{
field1 = value;
field2++;
RaisePropertyChanged("Property1");
RaisePropertyChanged("Property2");
}
}
private int field2;
public int Property2
{
get { return field2; }
set
{
field2 = value;
field1++;
RaisePropertyChanged("Property1");
RaisePropertyChanged("Property2");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
What he might be doing was handling other properties in the setter of each property he mentioned thus leading to cyclic invocation of setters.
Vijay
My issue seems to be "scope", though I'm not certain that's the right terminology. I want to notify a read-only list to re-evaluate itself when a property within a custom object is set. I believe it is simply not aware of it's existence. Maybe there is an easy way around this I cannot think of, but I'm drawing a blank.
I find this hard to put into words, so here's simplified code with my comments on what I expect to happen.
Properties within object in which I am databinding to:
private CvarAspectRatios _aspectRatio = new CvarAspectRatios("none", GetRatio());
public CvarAspectRatios AspectRatio
{
get { return _aspectRatio; }
set
{ // This setter never gets hit since I bind to this
if (value != null) // object's 'Value' property now.
{
_aspectRatio = value;
NotifyPropertyChanged("AspectRatio");
NotifyPropertyChanged("ResolutionList"); // I want to inform ResolutionList
} // that it needs to repopulate based
} // on this property: AspectRatio
}
private ResolutionCollection _resolutionList = ResolutionCollection.GetResolutionCollection();
public ResolutionCollection ResolutionList
{
get
{
ResolutionCollection list = new ResolutionCollection();
if (AspectRatio != null && AspectRatio.Value != null)
{
foreach (Resolutions res in _resolutionList.Where(i => i.Compatibility == AspectRatio.Value.Compatibility))
{
list.Add(res);
}
return list;
}
return _resolutionList;
}
}
CvarAspectRatios Class:
public class CVarAspectRatios : INotifyPropertyChanged
{
private string _defaultValue;
public string DefaultValue
{
get { return _defaultValue; }
set { _defaultValue = value; NotifyPropertyChanged("DefaultValue"); }
}
private AspectRatios _value;
public AspectRatios Value
{
get { return _value; }
set
{
_value = value;
NotifyPropertyChanged("Value");
NotifyPropertyChanged("ResolutionList"); // This value gets set, and I'd like for ResolutionList to update
} // but it cannot find ResolutionList. No errors or anything. Just
} // no update.
public AspectRatios() { }
public AspectRatios(string defaultValue, AspectRatios val)
{
DefaultValue = defaultValue;
Value = val;
}
// Implementation of INotifyPropertyChanged snipped out here
}
What do you folks think? If you'd like a sample application I can whip one up.
Since CVarAspectRatios implements INotifyPropertyChanged, you can have the viewmodel class subscribe to the PropertyChanged event for the AspectRatio.
public class YourViewModel
{
public YourViewModel()
{
AspectRatio.PropertyChanged += AspectRatio_PropertyChanged;
}
void AspectRatio_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Value")
NotifyPropertyChanged("ResolutionList");
}
}
Just bear in mind that if you discard that AspectRatio object (if the object reference changes and not just the value property of that object), you should unsubscribe from the event on the discarded one.
To just transform your existing code into something which should work:
private CvarAspectRatios _aspectRatio; //No field initialization because that would not attach event handler, you could do it though and take care of the handler alone in the ctor
public CvarAspectRatios AspectRatio
{
get { return _aspectRatio; }
set
{
if (_aspectRatio != value) // WTH # "value != null"
{
_aspectRatio.PropertyChanged -= AspectRatio_PropertyChanged;
_aspectRatio = value;
_aspectRatio.PropertyChanged += new PropertyChangedEventHandler(AspectRatio_PropertyChanged);
NotifyPropertyChanged("AspectRatio");
}
}
}
void AspectRatio_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Value")
{
NotifyPropertyChanged("ResolutionList");
}
}
Why don't you factor out re-populating ResolutionList into a separate private method which gets called from the setter of AspectRatios?
If a list needs to update based on a changed property, the list (or a list manager object, for better encapsulation) would normally need to subscribe to the PropertyChanged event of the object hosting the property. If the list is itself a property of the same object, as in this case, it would be simpler and leaner for the property's setter to call a method that updates the list.