I have this entity (BaseModel Implements INotifypropertyChanged):
EDIT 1: SetProperty code:
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
{
if (object.Equals(storage, value)) return false;
storage = value;
this.OnPropertyChanged(propertyName);
return true;
}
public partial class Movimientos : BaseModel
{
public Movimientos()
{
}
private Nullable<int> _intIdBodega;
public Nullable<int> IntIdBodega
{
get { return _intIdBodega; }
set { SetProperty(ref _intIdBodega, value); }
}
public virtual INV_Bodegas INV_Bodegas { get; set; }
}
With this navigation property:
/// Nav property
public partial class INV_Bodegas : BaseModel
{
public INV_Bodegas()
{
}
private int _intIdBodega;
public int IntIdBodega
{
get { return _intIdBodega; }
set { SetProperty(ref _intIdBodega, value); }
}
private string _strCodigoBodega;
public string StrCodigoBodega
{
get { return _strCodigoBodega; }
set { SetProperty(ref _strCodigoBodega, value); }
}
}
If user changes IntIdBodega property at the View model
public class VieModel
{
ObservableCollection<Movimientos> mov {get;set;}
public VieModel()
{
mov = new ObservableCollection<Movimientos>();
mov.CollecTionChanged += mov_CollectionChanged;
}
void mov_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (var item in e.NewItems)
{
((Movimientos)item).PropertyChanged += det_PropertyChanged;
}
}
}
In the view model I update the navigation property, But Datagrid never update the values Binded to this property.
public void det_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
Movimientos Idet = (Movimientos)sender;
// Here I Update The navigation property.
// But DataGrid never refresh the changes
if (e.PorpertyName == "IntIdBodega")
{
Idet.INV_Bodegas = db.INV_Bodegas
.Where(x=> x.IntIdbodega == Idet.Intidbodega)
.FirstOrDefault();
}
}
}
This is how I bind.
<DataGridTextColumn Header="{x:Static resources:Labels.Bodega}"
Width="0.5*"
Binding="{Binding Inv_Bodegas.StrCodigoBodega,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
IsReadOnly="True">
My problem is that this column never refresh If i change IsReadOnly to false, the changes can be reflected only if I press F2 at this column.
My question is:
How to force the UI to refresh this navigation properties ?
You have implemented the interface, but never raise the event. Try:
public string StrCodigoBodega
{
get { return _strCodigoBodega; }
set {
SetProperty(ref _strCodigoBodega, value);
this.PropertyChanged(this, new PropertyChangedEventArgs("StrCodigoBodega"));
}
}
See the example:
https://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.propertychanged%28v=vs.100%29.aspx
Related
I'm having a problem with using MVVM for a Xamarin project.
I can not refresh the user interface if one of my objects in my viewModel is updated (after a PUT request, for example).
Let me explain :
My model :
public class MyObject
{
public string Id { get; private set; }
public string Name { get; private set; }
}
My viewmodel :
public class MyViewModel : BaseViewModel
{
public MyObject MyObject { get; private set; }
public string IdMvvm
{
set
{
if (this.MyObject.Id != value)
{
MyObject.Id = value;
OnPropertyChanged(nameof(IdMvvm));
}
}
get { return MyObject.Id; }
}
public string NameMvvm
{
set
{
if (this.MyObject.Name != value)
{
MyObject.Name = value;
OnPropertyChanged(nameof(NameMvvm));
}
}
get { return MyObject.Name; }
}
}
BaseViewModel implements INotifyPropertyChanged
public class BaseViewModel : INotifyPropertyChanged
{
public string PageTitle { get; protected set; }
LayoutViewModel() {}
// MVVM ----------------------------------------------------------------------------------------
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected void SetValue<T>(ref T backingField, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(backingField, value))
return;
backingField = value;
OnPropertyChanged(propertyName);
}
MyViewModel is defined as BindingContext for my page
My properties IdMvvm and NameMvvm are bind in Entry in my page in xaml
When I modify an Entry then the value is raised but if my MyModel object changes value, for example update (click on a button) then the value of the different Entry is not updated
Can you help me please? Because it seems that I missed something ...
If you need more explanation, tell me to know
Sorry if my english is not good
It is because when you change the model, your view is not aware about the change. Update your code so that you explicitly notify property changes when your model changes.
public class MyViewModel : BaseViewModel
{
private MyObject _myObject;
public MyObject MyObject
{
get { return _myObject; }
private set { _myObject = value; NotifyModelChange(); }
}
public string IdMvvm
{
set
{
if (this.MyObject.Id != value)
{
MyObject.Id = value;
OnPropertyChanged(nameof(IdMvvm));
}
}
get { return MyObject.Id; }
}
public string NameMvvm
{
set
{
if (this.MyObject.Name != value)
{
MyObject.Name = value;
OnPropertyChanged(nameof(NameMvvm));
}
}
get { return MyObject.Name; }
}
private void NotifyModelChange()
{
OnPropertyChanged(nameof(IdMvvm));
OnPropertyChanged(nameof(NameMvvm));
}
}
I have a small DataGrid to do a simple operation. Fields are 3: Number 1, 2 and Result Numer.
DataGrid code is as follows:
<DataGrid x:Name="dgNumbers" ItemsSource="{Binding lstOperations, Mode=TwoWay}" CanUserAddRows="True" AutoGenerateColumns="False" CellEditEnding="dgNumbers_CellEditEnding">
<DataGrid.Columns>
<DataGridTextColumn Header="Number 1" Width="*" Binding="{Binding N1, Mode=TwoWay}"/>
<DataGridTextColumn Header="Number 2" Width="*" Binding="{Binding N2, Mode=TwoWay}"/>
<DataGridTextColumn Header="Result" Width="*" Binding="{Binding Result, Mode=TwoWay}" IsReadOnly="True"/>
</DataGrid.Columns>
</DataGrid>
I created an object where I keep the number 1, the number and outcome. This is the class code:
public class Numbers
{
public decimal N1 { get; set; }
public decimal N2 { get; set; }
public decimal Result { get; set; }
}
I made this small example to try to understand the Binding that make the ObservableCollection.
For this example, I have the following code in the event:
public MainWindow()
{
InitializeComponent();
lstOperations = new ObservableCollection<Numbers>();
}
ObservableCollection<Numbers> lstOperations;
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Numbers n = new Numbers();
n.N1 = 10;
n.N2 = 5;
n.Result = 15;
lstOperations.Add(n);
dgNumbers.ItemsSource = lstOperations;
}
private void dgNumbers_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
foreach(var item in lstOperations)
{
item.Result = item.N1 + item.N2;
}
}
What I am trying to know if changing a data collection, this fact is reflected in the DataGrid, if possible, how to do it right ?, if not possible, how to achieve something similar?
You can also install by NuGet Prism.Core and use the BindableBase class:
using Prism.Mvvm;
using System.Collections.ObjectModel;
using System.Windows;
namespace YourApplication
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
private void dgNumbers_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
foreach (var item in (DataContext as MainWindowViewModel).LstOperations)
{
item.Result = item.N1 + item.N2;
}
}
}
public class MainWindowViewModel : BindableBase
{
private ObservableCollection<Numbers> _lstOperations;
public ObservableCollection<Numbers> LstOperations
{
get { return _lstOperations; }
set
{
_lstOperations = value;
OnPropertyChanged();
}
}
public MainWindowViewModel()
{
_lstOperations = new ObservableCollection<Numbers>();
Numbers n = new Numbers
{
N1 = 10,
N2 = 5,
Result = 15
};
LstOperations.Add(n);
}
}
public class Numbers : BindableBase
{
private decimal _n1;
public decimal N1
{
get { return _n1; }
set { SetProperty(ref _n1, value); }
}
private decimal _n2;
public decimal N2
{
get { return _n2; }
set { SetProperty(ref _n2, value); }
}
private decimal _result;
public decimal Result
{
get { return _result; }
set { SetProperty(ref _result, value); }
}
}
}
In the end you also have to change the Binding in the View:
<DataGrid x:Name="dgNumbers" ItemsSource="{Binding LstOperations, Mode=TwoWay}" CanUserAddRows="True" AutoGenerateColumns="False" CellEditEnding="dgNumbers_CellEditEnding">
Using BindableBase is quite easy because it takes the CallerMemberName attribute in the implementation, so you don't have to specify which property is being called (you write OnPropertyChanged() in the setter instead of OnPropertyChanged("propertyName")). And it's even better, because there is also the SetProperty method, which sets the new value AND calls OnPropertyChanged event only when needed (when the new value is really new, really changed).
Do you have a class which implements INotifyPropertyChanged?
You can subscribe to ObservableCollection events. CollectionChange would probably do the trick but doesn't take advantage of data binding.
public MainWindow()
{
InitializeComponent();
lstOperations = new ObservableCollection<Numbers>();
lstOperations.CollectionChanged += MyCollectionChanged;
}
private MyCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
dgNumbers.ItemsSource = lstOperations;
}
So the basic data binding would go like this.
public class ModelView : INotifyPropertyChanged
{
public ModelView()
{
lstOperations = new ObservableCollection<Numbers>();
lstOperations.CollectionChanged += new NotifyCollectionChangedEventHandler((obj, e) => { OnPropertyChanged("lstOperations "); });
}
//----------------- Implementing the interface here
public event PropertyChangedEventHandler PropertyChanged;
// Call this method when you want the GUI updated.
public void OnPropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
//-------------------Your Properties-----------------------------
private ObservableCollection<Numbers> _lstOperations ;
public ObservableCollection<Numbers> lstOperations
{
get{return _lstOperations ;}
set
{
_lstOperations = value;
OnPropertyChanged("lstOperations");
}
}
Ok the class above now contains your variable you want to bind. Now you need to set your datacontext for the Datagrid.
// Need your model instance.
private ModelView Model;
public MainWindow()
{
InitializeComponent();
Model = new ModelView();
dgNumbers.DataContext = Model;
}
Now anywhere you are manipulating lstOperations you manipulate Model.lstOperations.
forgive my stupid tip but for me it works when i bind such a list
public class ModelNumber
{
public decimal N1 { get; set; }
public decimal N2 { get; set; }
public decimal Result { get; set; }
}
public class ViewModelNumber : NotifyPropertyChanged, IDataErrorInfo
{
protected ModelNumber __dataModel = null;
#region ---constructor---
public ViewModelNumber()
{
// model init
this.__dataModel = new ModelNumber();
}
#endregion
#region ---accessoren model basis---
public decimal N1
{
get
{
return this.__dataModel.N1;
}
set
{
if (this.__dataModel.N1 != value)
{
this.__dataModel.N1 = value;
this.OnPropertyChanged("N1");
}
}
}
public decimal N2
{
get
{
return this.__dataModel.N2;
}
set
{
if (this.__dataModel.N2 != value)
{
this.__dataModel.N2 = value;
this.OnPropertyChanged("N1");
}
}
}
public decimal Result
{
get
{
return this.__dataModel.Result;
}
set
{
if (this.__dataModel.Result != value)
{
this.__dataModel.Result = value;
this.OnPropertyChanged("N1");
}
}
}
#endregion
#region ---validation---
/// <summary>Gets an error message indicating what is wrong with this object.</summary>
public string Error
{
get { throw new NotImplementedException(); }
}
/// <summary>Gets the error message for the property with the given name.</summary>
public string this[string _columnName]
{
get
{
try
{
if (_columnName != null)
{
switch (_columnName)
{
default:
break;
}
}
return (null);
}
catch (Exception _except)
{
// mlog
Log.Exception(this.GetType().FullName, MethodBase.GetCurrentMethod().Name, _except);
return (null);
}
}
}
#endregion
}
ObservableCollection<ViewModelNumber> lstOperations;
I have a list view where I bind multiple data templates to using a DataTemplateSelector. However, I cannot build my project, I'm getting an error of "Object reference not set to an instance of an object". I narrowed down the issue to the data binding ItemsSource of the combobox. If I comment out the combobox, my project will build. As I understand, the Binding keyword evaluates at runtime, but this is a compile time error. I tried to get around the error by swapping out to x:Bind, as that evaluates at compile-time but then that required the type of the DataTemplate to be defined.
x:DataType={x:Type templates:FilterDropdownDataTemplate}
However, x:Type isn't defined in a Windows 10 universal app. Any suggestions?
Here is my XAML:
<Page.Resources>
<DataTemplate x:Name="DateComboboxTemplate">
<TextBlock Text="{Binding}"/>
</DataTemplate>
<DataTemplate x:Name="FilterDropdownDataTemplate">
<StackPanel>
<TextBlock Text="{Binding Value}"/>
<ComboBox ItemsSource="{Binding DateRanges}"
SelectionChanged="{Binding DateSelection}"
SelectedItem="{Binding SelectedDateRange, Mode=TwoWay}"
ItemTemplate="{StaticResource DateComboboxTemplate}">
</ComboBox>
</StackPanel>
</DataTemplate>
<config:MainFilterTemplateSelector
x:Name="MainFilterSelector"
FilterDropdownTemplate="{StaticResource FilterDropdownDataTemplate}"
/>
</Page.Resources>
<ListView x:Name="FilterCategories"
Margin="0"
Padding="0"
BorderThickness="0"
ItemsSource="{Binding Filters}"
SelectedItem="{Binding SelectedFilterCategory, Mode=TwoWay}"
IsItemClickEnabled="True"
SelectionChanged="{x:Bind ViewModel.OpenSubFilters}"
ItemTemplateSelector="{StaticResource MainFilterSelector}">
</ListView>
And here is the relevant C# code (I'm using a MVVM pattern):
public class MainFilterViewDropdownListTemplate : Filter
{
private ObservableCollection<string> _dateRanges;
private string _selectedDateRange;
public MainFilterViewDropdownListTemplate() : base()
{
_dateRanges = new ObservableCollection<string>();
}
public ObservableCollection<string> DateRanges
{
get
{
return _dateRanges;
}
set
{
SetProperty(ref _dateRanges, value);
// hard coding these 4 lines for testing for now
_dateRanges.Add("Last 7 days");
_dateRanges.Add("Last 14 days");
_dateRanges.Add("Last 30 days");
_dateRanges.Add("Custom");
}
}
public string SelectedDateRange
{
get
{
return _selectedDateRange;
}
set
{
SetProperty(ref _selectedDateRange, value);
}
}
public IEnumerable<Filter> AllFilters { get; set; }
public void InitializeFields(Filter filter)
{
Column = filter.Column;
DisplayType = filter.DisplayType;
ID = filter.ID;
IsSelected = filter.IsSelected;
ParentId = filter.ParentId;
SelectedCount = filter.SelectedCount;
TotalCount = filter.TotalCount;
Value = filter.Value;
visibility = filter.visibility;
}
private void DateSelection()
{
}
}
And
public class MainFilterTemplateSelector : DataTemplateSelector
{
public DataTemplate FilterDropdownTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object selector)
{
if (selector == null)
{
throw new ArgumentNullException("Template is null");
}
if (!(selector is FilterBase))
{
throw new ArgumentException("This list can only be populated by a class that extends Filter.");
}
else
{
var filterType = selector as Filter;
switch (filterType.DisplayType)
{
case "date":
return FilterDropdownTemplate;
default:
return base.SelectTemplateCore(selector);
}
}
}
}
And
public class Filter : FilterBase
{
private int _selectedCount;
private int _totalCount;
private Visibility _visibility;
public IEnumerable<Filter> Children
{
get;
set;
}
public int TotalCount
{
get
{
return _totalCount;
}
set
{
SetProperty(ref _totalCount, value);
}
}
public int SelectedCount
{
get
{
return _selectedCount;
}
set
{
SetProperty(ref _selectedCount, value);
}
}
public Visibility visibility
{
get
{
return _visibility;
}
set
{
SetProperty(ref _visibility, value);
}
}
public void InitializeAllChildren(IEnumerable<Filter> allFilters)
{
foreach (Filter item in allFilters)
{
item.Children = allFilters.GetAllChildrenFiltersForParentID(item.ID) as IEnumerable<Filter>;
if (item.Children == null)
{
item.TotalCount = 0;
}
else
{
item.TotalCount = item.Children.Count();
item.SelectedCount = item.Children.Where(t => t.IsSelected.Value).Count();
}
}
Children = allFilters.GetAllChildrenFiltersForParentID(ID);
}
}
And
public class FilterBase : ViewModelBase
{
public int ID
{
get
{
return _id;
}
set
{
SetProperty(ref _id, value);
}
}
public string Value
{
get
{
return _value;
}
set
{
SetProperty(ref _value, value);
}
}
public string Column
{
get
{
return _column;
}
set
{
SetProperty(ref _column, value);
}
}
public int ParentId
{
get
{
return _parentId;
}
set
{
SetProperty(ref _parentId, value);
}
}
public string DisplayType
{
get
{
return _displayType;
}
set
{
SetProperty(ref _displayType, value);
}
}
public bool? IsSelected
{
get
{
return _isSelected;
}
set
{
SetProperty(ref _isSelected, value);
}
}
private int _id;
private string _value;
private string _column;
private int _parentId;
private string _displayType;
private bool? _isSelected;
}
And (BindableBase is from the Prism.Mvvm namespace)
public class ViewModelBase : BindableBase, IDisposable
{
PropertyChangeActionHelper _propertyChangeActionHelper;
protected void RegisterPropertyChangeAction(Action action, params string[] monitoredProperties)
{
if (_propertyChangeActionHelper == null)
{
_propertyChangeActionHelper = new PropertyChangeActionHelper(this);
}
_propertyChangeActionHelper.RegisterPropertyChangeAction(action, monitoredProperties);
}
#region IDisposable Support
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (_propertyChangeActionHelper != null)
{
_propertyChangeActionHelper.Dispose();
_propertyChangeActionHelper = null;
}
}
}
~ViewModelBase()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(false);
}
// This code added to correctly implement the disposable pattern.
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
Assuming that the templates namespace is declared and FilterDropdownDataTemplate is a type within that namespace, you should be able to define the DataTemplate's data type without x:Type using the following syntax:
<DataTemplate x:DataType="templates:FilterDropdownDataTemplate">
I've been trying for two days now to figure this out, and repeatedly searching for answers. It seems like it should be so simple.
The goal:
I have two comboboxs. One is for an object Country, the other is for CountrySubdivision. (EG USA has States, Canada has Provinces, etc.) I want to load the form and have both the Country and CountrySubdivision in place.
Project has a Country and CountrySubdivions. Country has an ObservableCollection of CountrySubdivision. All objects are INotifyCollectionChanged. So here's where it gets irritating. The bindings "work." In that, if I change the values and save, the proper values are put back into the database. However, every time I open up the application, CountrySubdivision starts empty. The backing value is still set, but the combo box isn't set.
Here are the applicable bindings:
<ComboBox
x:Name="uiProjectCountrySubdivisions"
DisplayMemberPath="Name"
SelectedItem="{Binding Project.ProjectCountrySubdivision}"
ItemsSource="{Binding Project.ProjectCountry.CountrySubdivisions}"
FontSize="10.667" />
<ComboBox
x:Name="uiProjectCountry"
SelectedItem="{Binding Project.ProjectCountry}"
ItemsSource="{Binding Countries}"
DisplayMemberPath="Name"
FontSize="10.667" />
And as requested, here are the (severly shorted) classes.
public class Project : BaseNotifyPropertyChanged
{
private Guid _projectId;
public virtual Guid ProjectId
{
get { return _projectId; }
set
{
if (_projectId != value)
{
_projectId = value;
OnPropertyChanged(() => ProjectId);
}
}
}
private string _projectName;
public virtual string ProjectName
{
get { return _projectName; }
set
{
if (_projectName != value)
{
_projectName = value;
OnPropertyChanged(() => ProjectName);
}
}
}
private string _projectNumber;
public virtual string ProjectNumber
{
get { return _projectNumber; }
set
{
if (_projectNumber != value)
{
_projectNumber = value;
OnPropertyChanged(() => ProjectNumber);
}
}
}
private string _projectAddressLineOne;
public virtual string ProjectAddressLineOne
{
get { return _projectAddressLineOne; }
set
{
if (_projectAddressLineOne != value)
{
_projectAddressLineOne = value;
OnPropertyChanged(() => ProjectAddressLineOne);
}
}
}
private string _projectAddressLineTwo;
public virtual string ProjectAddressLineTwo
{
get { return _projectAddressLineTwo; }
set
{
if (_projectAddressLineTwo != value)
{
_projectAddressLineTwo = value;
OnPropertyChanged(() => ProjectAddressLineTwo);
}
}
}
private string _projectCity;
public virtual string ProjectCity
{
get { return _projectCity; }
set
{
if (_projectCity != value)
{
_projectCity = value;
OnPropertyChanged(() => ProjectCity);
}
}
}
private string _projectZip;
public virtual string ProjectZip
{
get { return _projectZip; }
set
{
if (_projectZip != value)
{
_projectZip = value;
OnPropertyChanged(() => ProjectZip);
}
}
}
private Country _projectCountry;
public virtual Country ProjectCountry
{
get { return _projectCountry; }
set
{
if (_projectCountry != value)
{
_projectCountry = value;
OnPropertyChanged(() => ProjectCountry);
}
}
}
private CountrySubdivision _projectCountrySubdivision;
public virtual CountrySubdivision ProjectCountrySubdivision
{
get { return _projectCountrySubdivision; }
set
{
if (_projectCountrySubdivision != value)
{
_projectCountrySubdivision = value;
OnPropertyChanged(() => ProjectCountrySubdivision);
}
}
}
}
public class Country : BaseNotifyPropertyChanged
{
private int _id;
public virtual int CountryId
{
get { return _id; }
set
{
if (_id != value)
{
_id = value;
OnPropertyChanged(() => CountryId);
}
}
}
private string _name;
public virtual string Name
{
get
{
return _name;
}
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged(() => Name);
}
}
}
private string _code;
public virtual string Code
{
get
{
return _code;
}
set
{
if (_code != value)
{
_code = value;
OnPropertyChanged(() => Code);
}
}
}
private ObservableCollection<CountrySubdivision> _countrySubdivisions = new ObservableCollection<CountrySubdivision>();
public virtual ObservableCollection<CountrySubdivision> CountrySubdivisions
{
get
{
return _countrySubdivisions;
}
set
{
if (_countrySubdivisions != value)
{
_countrySubdivisions = value;
OnPropertyChanged(() => CountrySubdivisions);
}
}
}
private string _subdivisionTypeShortDescription;
public virtual string SubdivisionTypeShortDescription
{
get
{
return _subdivisionTypeShortDescription;
}
set
{
if (_subdivisionTypeShortDescription != value)
{
_subdivisionTypeShortDescription = value;
OnPropertyChanged(() => SubdivisionTypeShortDescription);
}
}
}
private string _subdivisionTypeLongDescription;
public virtual string SubdivisionTypeLongDescription
{
get
{
return _subdivisionTypeLongDescription;
}
set
{
if (_subdivisionTypeLongDescription != value)
{
_subdivisionTypeLongDescription = value;
OnPropertyChanged(() => SubdivisionTypeLongDescription);
}
}
}
}
public class CountrySubdivision : BaseNotifyPropertyChanged
{
private int _id;
public virtual int CountrySubdivisionId
{
get { return _id; }
set
{
if (_id != value)
{
_id = value;
OnPropertyChanged(() => CountrySubdivisionId);
}
}
}
private string _name;
public virtual string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged(() => Name);
}
}
}
private Country _country;
public virtual Country Country
{
get { return _country; }
set
{
if (_country != value)
{
_country = value;
OnPropertyChanged(() => Country);
}
}
}
}
public abstract class BaseNotifyPropertyChanged : INotifyPropertyChanged
{
public virtual event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(Expression<Func<object>> propertySelector)
{
//Stuff that turns () => Property into a fired Property Changed event.
}
}
Please help!!
Is the SelectedItem on your ComboBox an object? If so, is it the exact same instance that exists in the "Countries" collection? Remember that for WPF to consider something "Selected" it must be able to find the "SelectedItem" in the "Countries" collection. If these types differ or instances differ that won't work.
I prefer binding my ComboBoxes as follows:
IDictionary<int, string> Countries
int? CountryId
<ComboBox
ItemsSource="{Binding Countries}"
SelectedValue="{Binding CountryId}"
SelectedValuePath="Key"
DisplayMemberPath="Value"/>
Still, as Alex asks, please list the relevant class definitions and relevant properties too.
I have searched high and low for a solution but I dont seem to get to the bottom of it.
Like many posts on the net I dont seem to make my itemPropertyChanged work.
It does not fire when editing an item in the collection.Why.
a bit lenghty but this is an example I have put together.
I have a customerViewModel that contains a Collections of OrderViewModels
when editing the order in the datagrid the event does not fire.
I have implemented the following but never gets fired when editing only when loading.
As if it's not the same collection of something...
INotifyPropertyChanged inpc = OrderViewModels;
inpc.PropertyChanged += OnItemPropertyChanged;
Any suggestions?It's driving me mad
Models
public class Order
{
public int Id { get; set; }
public string Description { get; set; }
public int CustomerId{ get; set; }
}
public class Customer
{
public Customer()
{
Orders=new ObservableCollection<Order>();
}
public int Id { get; set; }
public string Name { get; set; }
public string Surname{ get; set;}
public ObservableCollection<Order> Orders{ get; set;}
}
ViewModels
public class CustomerViewModel : ViewModelBase
{
private Customer _customerModel;
public CustomerViewModel(Customer customerModel)
{
_customerModel = customerModel;
_orderViewModels = new ObservableCollection<OrderViewModel>();
OrderViewModels.CollectionChanged += OnOrdersCollectionChanged;
INotifyPropertyChanged inpc = OrderViewModels;
inpc.PropertyChanged += OnItemPropertyChanged;
}
private void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
//not firing!!!!!!!!!!!!!!!!!!!!!!!!!
}
void OnOrdersCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
_customerModel.Orders.Insert(e.NewStartingIndex, ((OrderViewModel)e.NewItems[0]).OrderModel);
break;
case NotifyCollectionChangedAction.Remove:
_customerModel.Orders.RemoveAt(e.OldStartingIndex);
break;
case NotifyCollectionChangedAction.Replace:
_customerModel.Orders[e.OldStartingIndex] = ((OrderViewModel)e.NewItems[0]).OrderModel;
break;
case NotifyCollectionChangedAction.Move:
_customerModel.Orders.Move(e.OldStartingIndex, e.NewStartingIndex);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
public int Id
{
get { return _customerModel.Id; }
set
{
_customerModel.Id = value;
OnPropertyChanged("Id");
}
}
public string Name
{
get { return _customerModel.Name; }
set
{
_customerModel.Name = value;
OnPropertyChanged("Name");
}
}
public string Surname
{
get { return _customerModel.Surname; }
set
{
_customerModel.Surname = value;
OnPropertyChanged("Surname");
}
}
public Customer CustomerModel
{
get { return _customerModel; }
set
{
_customerModel = value;
OnPropertyChanged("");
}
}
private ObservableCollection<OrderViewModel> _orderViewModels;
public ObservableCollection<OrderViewModel> OrderViewModels
{
get { return _orderViewModels; }
set
{
_orderViewModels = value;
OnPropertyChanged("OrderViewModels");
}
}
}
public class OrderViewModel:ViewModelBase
{
private Order _orderModel;
public OrderViewModel(Order orderModel)
{
_orderModel = orderModel;
}
public int Id
{
get { return _orderModel.Id; }
set
{
_orderModel.Id = value;
OnPropertyChanged("Id");
}
}
public int CustomerId
{
get { return _orderModel.CustomerId; }
set
{
_orderModel.CustomerId = value;
OnPropertyChanged("CustomerId");
}
}
public string Description
{
get { return _orderModel.Description; }
set
{
_orderModel.Description = value;
OnPropertyChanged("Description");
}
}
public Order OrderModel
{
get { return _orderModel; }
set
{
_orderModel = value;
OnPropertyChanged("");
}
}
}
Repository
public class OrderRepository
{
public static ObservableCollection<Order> GetOrders(int customerId)
{
return new ObservableCollection<Order>
{
new Order {Id = 1, CustomerId=1, Description = "MotherBoard"},
new Order {Id = 2, CustomerId=1,Description = "Video Card"},
new Order {Id = 3, CustomerId=1,Description = "TV"},
new Order {Id = 4, CustomerId=1, Description = "Video Recorder"},
new Order {Id = 5, CustomerId=1,Description = "Speakers"},
new Order {Id = 6, CustomerId=1,Description = "Computer"}
};
}
}
View
public partial class OrdersView
{
public OrdersView()
{
InitializeComponent();
if (!DesignerProperties.GetIsInDesignMode(this))
{
var customerVm =
new CustomerViewModel(new Customer
{
Id = 1,
Name = "Jo",
Surname = "Bloggs"
});
var orders = OrderRepository.GetOrders(1);
foreach (var orderModel in orders)
{
customerVm.OrderViewModels.Add(new OrderViewModel(orderModel));
}
DataContext = customerVm;
}
}
}
Xaml
<Grid>
<DataGrid AutoGenerateColumns="False" AlternationCount="2" ItemsSource="{Binding Path=OrderViewModels}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Id}" Header="Id"/>
<DataGridTextColumn Binding="{Binding CustomerId}" Header="Customer Id"/>
<DataGridTextColumn Header="Description" Binding="{Binding Description,UpdateSourceTrigger=PropertyChanged}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
INotifyPropertyChanged inpc = OrderViewModels;
inpc.PropertyChanged += OnItemPropertyChanged;
That code will notify you when any property on the ObservableCollection<T> changes, not when items in the ObservableCollection<T> have their properties changed. For example, your handler should be called when you add or remove an OrderViewModel because the Count property will change on the ObservableCollection<OrderViewModel>.
Nothing is propagating the PropertyChanged event inside OrderViewModels and aggregating them into a single event for you. I use a class I called ItemObservableCollection when I want to do this:
public sealed class ItemObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
public event EventHandler<ItemPropertyChangedEventArgs<T>> ItemPropertyChanged;
protected override void InsertItem(int index, T item)
{
base.InsertItem(index, item);
item.PropertyChanged += item_PropertyChanged;
}
protected override void RemoveItem(int index)
{
var item= this[index];
base.RemoveItem(index);
item.PropertyChanged -= item_PropertyChanged;
}
protected override void ClearItems()
{
foreach (var item in this)
{
item.PropertyChanged -= item_PropertyChanged;
}
base.ClearItems();
}
protected override void SetItem(int index, T item)
{
var oldItem = this[index];
oldItem.PropertyChanged -= item_PropertyChanged;
base.SetItem(index, item);
item.PropertyChanged += item_PropertyChanged;
}
private void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnItemPropertyChanged((T)sender, e.PropertyName);
}
private void OnItemPropertyChanged(T item, string propertyName)
{
var handler = this.ItemPropertyChanged;
if (handler != null)
{
handler(this, new ItemPropertyChangedEventArgs<T>(item, propertyName));
}
}
}
public sealed class ItemPropertyChangedEventArgs<T> : EventArgs
{
private readonly T _item;
private readonly string _propertyName;
public ItemPropertyChangedEventArgs(T item, string propertyName)
{
_item = item;
_propertyName = propertyName;
}
public T Item
{
get { return _item; }
}
public string PropertyName
{
get { return _propertyName; }
}
}
I can use it like this:
var orders = new ItemObservableCollection<OrderViewModel>();
orders.CollectionChanged += OnOrdersChanged;
orders.ItemPropertyChanged += OnOrderChanged;
System.ComponentModel.BindingList<Type> offer the same functionnality as ObservableCollection<Type> and handles the PropertyChanged events correctly.
Best regards