WPF Combo box not calling setter method when item changed - c#

I have a combo box that is bound to an object from a model that is instantiated inside of my view model. OnPropertyChange is handled inside of the Notifier class that inherits from INotifyPropertyChange. The view model polls executes a method from a data access layer and returns an observablelist to the view model. This is then passed into a constructor that builds the object i want to bind to the combo box. The object has two properties. 1) An observable list of possible selections and 2) a string that represents the current selected item.
Here is the problem. The combo box is successfully bound and populated by the list. It does not however appear to call the setter method when an item is changed. I need this functionality so I can continue with application logic once the item is elected. Debugging confirms that no setter is called, only the get. The setter is in fact called on the model however which make sense. Im certain I am missing something here and am open to suggestions on a better way to do this.
Model
public class WellListGroup : Notifier
{
private ObservableCollection<string> _headers;
public ObservableCollection<string> headers
{
get { return _headers; }
set { _headers = value; OnPropertyChanged("headers"); }
}
private string _selected;
public string selected
{
get { return this._selected;}
set { this._selected = value; OnPropertyChanged("selected");}
}
}
Notifier
public class Notifier : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
View Model
public class MainViewModel : Notifier
{
//data access layer
public static getWells gw = new getWells();
//set combo box
public static ObservableCollection<string> headers = gw.getHeaders();
private WellListGroup _wlg = new WellListGroup {headers = headers, selected = headers[0]};
public WellListGroup wlg
{
get {
return _wlg;
}
set {
_wlg = value;
OnPropertyChanged("wlg");
OnChange()// do stuff!!!
}
}
View
<ComboBox x:Name="groupComboBox"
DockPanel.Dock="Top"
ItemsSource = "{Binding Path = wlg.headers}"
SelectedItem="{Binding Path = wlg.selected, Mode=TwoWay}">
</ComboBox>
EDIT - Reworked ViewModel to Subscribe to event on the object
public class MainViewModel : Notifier
{
//data access layer
public static getWells gw = new getWells();
//set combo box
public static List<string> headers = gw.getHeaders();
private WellListGroup _wlg = new WellListGroup {headers = headers, selected = headers[0]};
public WellListGroup wlg
{
get {
return _wlg;
}
set {
_wlg = value;
OnPropertyChanged("wlg");
OnChange(_wlg.selected);// do stuff!!!
}
}
public MainViewModel()
{
// Move this into the constructor to avoid any race conditions
_wlg = new WellListGroup {headers = headers, selected = headers[0]};
// Subscribe to the property change even for WLG
_wlg.PropertyChanged += (sender, args) =>
{
if (args.PropertyName == "selected") {
}
OnChange(_wlg.selected);// do stuff!!!
};
}

The situation is that the reflected property setter is within the _wlg class and not the setter of the _wlg class itself on the VM. The bounded item is not going to the top level but the lower property as mentioned.
Either put in a commanding system to kick off the OnChange()// do stuff!!! code or subscribe to the _wlg class instance INotifyProptertyChanged event and call the method you mentioned.
Is there anyway to handle either of those from within the view model?
Yes, subscribe to the instance of the class WellListGroup property changed event and look for selected or others to report a change.
public MainViewModel()
{
// Move this into the constructor to avoid any race conditions
_wlg = new WellListGroup {headers = headers, selected = headers[0]};
// Subscribe to the property change even for WLG
_wlg.PropertyChanged += (sender, args) =>
{
if (args.PropertyName == 'selected')
OnChange()// do stuff!!!
};
}
Of note, it is unclear if you really need to hold the strings in an ObservableCollection. That collection has its own implementation of notify events for adding and deleting of items within the collection.
If the VM needs that specific change info, then you will need to subscribe to the ObservableCollection's event(s) for such operations instead of/as well as the aforementioned above example.
If one does not need those notifications, holding the strings in an ObservableCollection is not needed and you can change it to a List<string> instead.

Related

Invoking PropertyChanged from a Method to update properties

I am trying to figure out how to update my bool properties inside a ViewModel using
INotifyPropertyChanged?
Basically in my ViewModel I pass in a List of string. Each boolean properties check the list to see if a
string value exists.
Now in my software lifecycle the list will get updated and inturn I would like to update each properties
using INotifyPropertyChanged.
My question is how do I invoke the INotifyPropertyChanged from a AddToList method? Is using a method for this the
correct direction?
public class ViewModel : INotifyPropertyChanged
{
private List<string> _listOfStrings;
public ViewModel(List<string> ListOfStrings)
{
_listOfStrings = ListOfStrings;
}
public bool EnableProperty1 => _listOfStrings.Any(x => x == "Test1");
public bool EnableProperty2 => _listOfStrings.Any(x => x == "Test2");
public bool EnableProperty3 => _listOfStrings.Any(x => x == "Test3");
public bool EnableProperty4 => _listOfStrings.Any(x => x == "Test4");
public void AddToList(string value)
{
_listOfStrings.Add(financialProductType);
// Should I call the OnPropertyChanged here
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The easiest thing to do here would be to manually call OnPropertyChanged in the AddString method.
public void AddToList(string value)
{
_listOfStrings.Add(financialProductType);
OnPropertyChanged("EnableProperty1");
OnPropertyChanged("EnableProperty2");
// etc
}
This is fine if you're not likely to change the class much. If you add another property that's calculated from _listOfStrings you'll need to add a OnPropertyChanged call here.
Using an ObservableCollection doesn't really help because you already know when the list changes (AddToList) and you'll still have to trigger all the OnPropertyChanged methods anyway.
As far as I can see, there are 2 things you are missing in your implementation:
You should use ObservableCollection instead of List. As the name suggest, the former one can be observed (notify about its changing) by the view.
You need to bind a control to the public ObservableCollection and call OnPropertyChanged every time you assign/change value of the collection. something like this:
private ObservableCollection<string> _myList;
// your control should bind to this property
public ObservableCollection<string> MyList
{
get => return _myList;
set
{
// assign a new value to the list
_myList = value;
// notify view about the change
OnPropertiyChanged(nameof(MyList));
}
}
// some logic in your view model
string newValue = "newValue";
_myList.Add(newValue );
OnPropertyCHanged(nameof(MyList));
Hope this helps?

Using different instances of a ViewModel class can cause problems updating an ObservableCollection?

I have a BookViewModel class with some properties, one of then an ObservableCollection. But I'm having problems updating its value. This is my case:
public class BookViewModel : INotifyPropertyChanged
{
private IEnumerable<Book> booksList;
private ObservableCollection<Chapter> selectedChapters = new ObservableCollection<Chapter>();
public BookViewModel()
{
}
public BookViewModel(List<Book> booksList)
{
this.BooksList = booksList;
}
// ...
public ObservableCollection<Book> SelectedChapters
{
get
{
return this.selectedChapters;
}
set
{
this.selectedChapters = value;
this.OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
In one UserControl of my application I do:
private TrainViewModel booksViewModel;
// ...
booksViewModel = new BookViewModel(booksList); // booksList comes from other site
this.DataContext = this.booksViewModel;
And in another UserControl, which is created dynamically as a child of the previous UserControl, I do:
private TrainViewModel booksViewModel;
// ...
booksViewModel = new BookViewModel();
this.DataContext = this.booksViewModel; // Different constructor
In this latter page, I have some checkboxes which modify my selectedChapters property by adding or removing elements from it:
// When some checkbox is checked
this.booksViewModel.SelectedChapters.Add(selectedChapter);
// When some checkbox is unchecked
this.booksViewModel.SelectedChapters.Remove(selectedChapter);
If each time a checkbox is checked or unchecked I do:
Debug.Print(this.booksViewModel.SelectedChapters.Count()); // Always print out 1!!
I'm wondering if using the same ViewModel, but with different instances in each view (the new thing), is causing the problem.
Ok, I could solve it. Not sure if I'm expressing myself well, but it was like I was modifying different sources of data (i.e. data contexts). So, what I did was try to cast the data context of the child UserControl to a BookViewModel (which is the data context of its parent) and work from that:
// Inside the event handler for check and uncheck
BookViewModel bookViewModel = this.DataContext as BookViewModel;
// When some checkbox is checked
if (bookViewModel != null){
this.booksViewModel.SelectedChapters.Add(selectedChapter);
}
// When some checkbox is unchecked
if (bookViewModel != null){
this.booksViewModel.SelectedChapters.Remove(selectedChapter);
}
And that's all. Update perfectly. I don't do anything with related to data dontext or view model in any part of the code (even in the constructor). Now, it's like I'm modifying the data in the same data context of the parent (sorry if my explanation isn't precise, I'm still getting used to WPF concepts).

Accessing ItemsSource from GridView

I have a button, When it's clicked it populates my Datagrid. The code is written within the .xaml.cs file, which I believe breaks the MVVM rule but it's just a temporary situation. I know it's not ideal for MVVM.
Calculate.xaml.cs
public void PopulateGrid(object sender, RoutedEventArgs e)
{
BindableCollection<Payments> PaymentCollection = new BindableCollection<Payments>
....
Datagrid.ItemsSource = PaymentCollection
....
}
My question is if there's a way to read the Datagrids ItemsSource From the ViewModel.
What I've Tried
LoansViewModel
public BindableCollection<Payments> paymentCollection {get; set;}
Calculate.xaml
<telerik:RadGridView ItemsSource="{Binding paymentCollection, Mode=TwoWay}" ... />
The collection paymentCollection Doesn't Update after calculate is clicked.
Just do this the correct MVVM way. Get rid of your PopulateGrid method in the .xaml.cs file and eliminate setting the Click property in your xaml. Instead bind the command property of the button to an ICommand property in your ViewModel the same way you are binding the ItemsSource of the RadGridView. You will need an implementation of ICommand to use and MVVM Lights RelayCommand is one option for that.
Here is the code for the ICommand:
private ICommand _populateGridCommand;
public ICommand PopulateGridCommand
{
get
{
if (_populateGridCommand == null)
{
_populateGridCommand = new RelayCommand(() => PopulateGrid());
}
return _populateGridCommand;
}
}
public void PopulateGrid()
{
PaymentCollection.Clear();
//load data and then add to the collection
}
UPDATE
To do this in code behind, you'll need to access the ViewModel and work on the collection from it. I don't like this but it should work.
public void PopulateGrid(object sender, RoutedEventArgs e)
{
var loansVM = DataGrid.DataContext as LoansViewModel;
loansVM.paymentsCollection.Clear();
var newData = //load data
foreach (var data in newData)
loansVM.paymentsCollection.Add(data);
}
Your xaml code looks like it should work provided the DataContext of your grid is set to your ViewModel instance where your paymentCollection property is declared.
Once your binding is set, it calls the get on the paymentCollection property. If your collection property object is not reassigned any further, and you add and remove elements from it, and it notifies on those changes via INotifyCollectionChanged, it will work. This is how ObservableCollection works and used most commonly for such scenarios.
However, if when you calculate, you re-assign your paymentCollection property with a new instance, your grid will not update, because you now have an entirely different collection. In that case you will need to notify the view that the paymentCollection property itself has changed. In which case you should implement it as a notification property:
private BindableCollection<Payments>_paymentCollection;
public BindableCollection<Payments> paymentCollection {
get { return _paymentCollection; }
set {
_paymentCollection = value;
OnPropertyChanged("paymentCollection");
}
}
protected void OnPropertyChanged(string name) {
PropertyChangedEventHandler handler = PropertyChanged;
if(handler != null) {
handler(this, new PropertyChangedEventArgs(name));
}
}

Binding properties not working

Well the title may not help to much, but here is my problem. I have problems binding properties between controls in SILVERLIGHT. Here is the structure:
There is a control called "ProjectItemList", this control will recieve a string and display it in a very simple way.
There is a control called "ProjectList", this control will recive an array of string and use the control "ProjectItemList" to represent each of the strings in the array.
There is a window called "ProjectWindow", this window contains a "ProjectList" control, and a dependency property called "Nombres".
The context: The "ProjectWindow" window include a "ProjectList" control, there is a binding between ProjectWindow's dependency property "Nombres" and a dependency property "ListOfNames", here is the code of the "ListOfNames" dependency property:
public static DependencyProperty ListOfNamesProperty =
DependencyProperty.Register("ListOfNames", typeof(string[]), typeof(ProjectList), new PropertyMetadata(null));
public string[] ListOfNames
{
get
{
return (string[])GetValue(ListOfNamesProperty);
}
set
{
SetValue(ListOfNamesProperty, value);
List<ProjectItemList> auxList = new List<ProjectItemList>();
foreach (string s in value)
{
ProjectItemList il = new ProjectItemList();
il.Nombre = s;
this.lb_projects.Items.Add(il);
}
}
}
The problem is that, although the list control in "ProjectList" represent the "Nombres" property, the "Set" of "ListOfNames" is never called, so I can't create each of the "ProjectItemList" objects and represent the data.
I'm fairly new into XAML and Silverlight, so i may not considering something simple.
Thanks!
When you use dependency properties, the set and get are not called through ui binding, this is your problem.
WPF ui controls tald directrly with the dependeny property without going through your getter and setter.
You need to specify a callback to monitor property changes:
public static DependencyProperty ListOfNamesProperty =
DependencyProperty.Register("ListOfNames", typeof(string[]), typeof(ProjectList),
new PropertyMetadata(ListOfNamesChaned));
private static void ListOfNamesChaned(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
List<ProjectItemList> auxList = new List<ProjectItemList>();
foreach (string s in value)
{
ProjectItemList il = new ProjectItemList();
il.Nombre = s;
this.lb_projects.Items.Add(il);
}
}
public string[] ListOfNames
{
get
{
return (string[])GetValue(ListOfNamesProperty);
}
set
{
SetValue(ListOfNamesProperty, value);
}
}
See Chen Kinnrot's answer to know why.
For this to achieve, Raise Property Change event can be used i.e with the propertychanged event handler,Code snippet is like:
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
use OnPropertyChanged to bind the property and if any change in that property.it will notify and your binding properties automatically will be changed and ui will be updated which are bound to these properties.

RaisePropertyChanged doesn't work for collections

I'm using mvvm-light and I noticed this strange behavior about the RaisePropertyChanged.
xaml:
<ListBox ItemsSource="{Binding Collection}"/>
<TextBlock Text="{Binding Text}"/>
Observable class:
public class A : ObservableObject
{
private string _b;
public string B
{
get { return this._b; }
set
{
this._b = value;
this.RaisePropertyChanged("B");
}
}
}
vm:
public MainViewModel(IDataService dataService) { this.Collection = new List<A>(...); }
public RelayCommand Command1
{
get
{
return this._command1 ?? (this._command1= new RelayCommand(() =>
{
this.Collection.Add(new A());
this.Collection[2].B = "updated";
this.RaisePropertyChanged("Collection");
this.RaisePropertyChanged("Text");
}));
}
}
public RelayCommand Command2
{
get { return this._command2?? (this._command2 = new RelayCommand(() => { this.Text++; })); }
}
public List<A> Collection { get; set; }
public int Text { get; set; }
So, RaisePropertyChanged("Collection") doesn't update the binding while RaisePropertyChanged("Text") do. I can see it by executing the Command2 several times and the Command1 after that. If the Collection is an ObservableCollection then new element shows in a view, but updated item isn't, which means an internal mechanism of an ObservableCollection works, but not the RaisePropertyChanged.
First, an explanation of the issue:
On Windows Phone, when setting a value for a dependency property, the framework internally check if the new value is different from the old one (for optimization purpose maybe). When you raise the PropertyChanged event or directly re-assign your collection to the ItemsSource property (which is just a wrapper around the ItemsControl.ItemsSourceProperty dependency property), the framework detects that the value actually didn't change and doesn't update the property. Therefore, the ListBox is never notified of your changes, and isn't updated.
The ObservableCollection works because it uses a whole different mechanism: the ListBox directly subscribes to the CollectionChanged event of your collection, and thus isn't hindered by the limitations of the dependency properties.
Now, how to get around this limitation? The only workarounds I can think of are:
Use an ObservableCollection instead of a List
Assign null to the ItemsSource property of your ListBox, then re-assign your collection
Bind the ListBox to a property that will return a different collection every time it's called:
public List<A> CollectionCopy
{
get
{
return this.Collection.ToList();
}
}

Categories