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
Related
I am trying to implement a searchbar using MVVM in Xamarin.forms. so far I have managed to borrow some code from around the internet and it seems to do go through the motions of the search. the only issue is I don't know what code to put in the command.
I would like the search bar to search recipeNames from a list of Recipes. this information is all stored on a local database and displayed using an observable collection.
please can you help me work it out.
XAML
<SearchBar x:Name="SearchBar"
Placeholder="Search"
SearchCommand="{Binding SearchCommand}"
SearchCommandParameter="{Binding Text, Source={x:Reference SearchBar}}"
Text="{Binding SearchText, Mode=TwoWay}">
<SearchBar.Behaviors>
<local:TextChangedBehavior />
</SearchBar.Behaviors>
</SearchBar>
<ListView x:Name="ListViewItems"
ItemsSource="{Binding Recipes}"
IsPullToRefreshEnabled="True"
Refreshing="ListViewItems_Refreshing"
SelectedItem="{Binding SelectedRecipe}">
Text changed Behaviour
class TextChangedBehavior: Behavior<Xamarin.Forms.SearchBar>
{
protected override void OnAttachedTo(Xamarin.Forms.SearchBar bindable)
{
base.OnAttachedTo(bindable);
bindable.TextChanged += Bindable_TextChanged;
}
protected override void OnDetachingFrom(Xamarin.Forms.SearchBar bindable)
{
base.OnDetachingFrom(bindable);
bindable.TextChanged -= Bindable_TextChanged;
}
private void Bindable_TextChanged(object sender, TextChangedEventArgs e)
{
((Xamarin.Forms.SearchBar)sender).SearchCommand?.Execute(e.NewTextValue);
}
}
and viewModel
public class RecipeListViewModel : ObservableCollection<Recipe>
{
private ObservableCollection<Recipe> Recipes {get; set;}
public INavigation Navigation { get; internal set; }
public ICommand NewAddPage { get; protected set; }
public RecipeListViewModel(INavigation navigation)
{
this.Navigation = navigation;
Recipes = new ObservableCollection<Recipe>();
this.NewAddPage = new Command(async () => await CreateNewAddPage());
Init();
}
// Gets all recipes from the database and adds them to the observable collection
private void Init()
{
var enumarator = App.RecipeDbcontroller.GetRecipe();
if (enumarator == null)
{
App.RecipeDbcontroller.SaveRecipe(new Recipe { RecipeName = "Moussaka", Serves = 6, PrepTime = "30", CookTime = "2 Hours", MealType = "Dinner" });
enumarator = App.RecipeDbcontroller.GetRecipe();
}
while (enumarator.MoveNext())
{
//cleans database of all empty records
if (enumarator.Current.RecipeName == null || enumarator.Current.CookTime == null)
{
App.RecipeDbcontroller.DeleteRecipe(enumarator.Current.RecipeID);
}
else
Add(enumarator.Current);
}
}
private ICommand _searchCommand;
public ICommand SearchCommand
{
get
{
return _searchCommand ?? (_searchCommand = new Command<string>((text) =>
{
**// THIS IS WHAT I DON'T KNOW WHAT TO DO**
}));
}
}
private string _searchText { get; set; }
public string SearchText
{
get { return _searchText; }
set
{
if (_searchText != value)
{
_searchText = value;
}
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
RecipeDatabaseController Class
public RecipeDatabaseController()
{
this.database = DependencyService.Get<ISQLite>().GetConnection();
this.database.CreateTable<Recipe>();
}
//Recipe CRUD
public IEnumerator<Recipe> GetRecipe()
{
lock (locker)
{
if (database.Table<Recipe>().Count() == 0)
{
return null;
}
else
{
return this.database.Table<Recipe>().GetEnumerator();
}
}
}
public IEnumerator<Recipe> GetRecipeBySearchTerm(text)
{
var enumarator = GetRecipe();
lock (locker)
{
while (enumarator.MoveNext)
{
if(enumarator.Current.RecipeName.Contains(text)
return this.
}
}
}
public int SaveRecipe(Recipe recipe)
{
lock (locker)
{
if (recipe.RecipeID != 0)
{
this.database.Update(recipe);
return recipe.RecipeID;
}
else
{
return this.database.Insert(recipe);
}
}
}
public int DeleteRecipe(int Id)
{
lock (locker)
{
return this.database.Delete<Recipe>(Id);
}
}
right so the search command ought to look like this.
public ICommand SearchCommand => _searchCommand ?? (_searchCommand = new Command<string>((text) =>
{
if (text.Length >=1)
{
Recipes.Clear();
Init();
var suggestions = Recipes.Where(c => c.RecipeName.ToLower().StartsWith(text.ToLower())).ToList();
Recipes.Clear();
foreach (var recipe in suggestions)
Recipes.Add(recipe);
}
else
{
Recipes.Clear();
Init();
ListViewVisible = true;
SuggestionsListViewVisible = false;
}
}));
using System.Linq;
//Recipe CRUD
public IEnumerable<Recipe> GetRecipe()
{
lock (locker)
{
return this.database.Table<Recipe>();
}
}
public IEnumerable<Recipe> GetRecipeBySearchTerm(string text)
{
var recipes = GetRecipe();
lock (locker)
{
return recipes.Where(m => m.RecipeName.ToLower().Contains(text));
}
}
Add the using System.Linq reference
Change those two methods and return IEnumerable
Note. RecipeName is the property you want to filter your Recipe with.
And your search command as below
private ICommand _searchCommand;
public ICommand SearchCommand
{
get
{
return _searchCommand ?? (_searchCommand = new Command<string>((text) =>
{
var filteredRecipes = App.RecipeDbcontroller.GetRecipeBySearchTerm(text);
recipes.Clear();
foreach(var recipe in filteredRecipes )
recipes.Add(recipe);
}));
}
}
I have not tested this code, so not sure where I might get errors, but you can work out the rest because the logic is given to you
Good luck
I have one gridcontrol with various fields which i havent mentioned in my code
<dxg:GridControl HorizontalAlignment="Stretch" Height="300" VerticalAlignment="Top" x:Name="grid1" AutoPopulateColumns="False" ItemsSource="{Binding Collection1}" >
<dxg:GridControl.View >
<dxg:TableView x:Name="TableView1" />
</dxg:GridControl.View>
.
.
.
.
I have another grid control on the same page with various fields
<dxg:GridControl HorizontalAlignment="Stretch" Height="250" VerticalAlignment="Top" x:Name="grid2" AutoPopulateColumns="False"
ItemsSource="{Binding ElementName="TableView1" ,path=Collection2.FocusedRow}" >
<dxg:GridControl.View >
<dxg:TableView x:Name="TableView2" />
</dxg:GridControl.View>
.
.
.
.
now collection1 Id is primary key and collection2 colID is foreign key both are having relationship with each other
Scenario here is if i select a row in grid1 all the corresponding records must be displayed in grid 2
public class myCollection: BindingList<orders>
{
public DataContext dc;
public myCollection(IList<orders> list)
: base(list)
{
}
protected override void RemoveItem(int index)
{
orders deleteItem = this.Items[index];
if (Dc.Order != null)
{
Dc.Order.DeleteOnSubmit(deleteItem);
}
base.RemoveItem(index);
}
}
My generic class for orders and generic class for master is the same
If I speak in terms of XAML properties, here you want to update ItemsSource property of 2nd Datagrid on basis of SelectedItem property of 1st Datagrid.
To achieve this, add a new property "SelectedItemDg1" in ViewModel which will hold the selection of 1st DataGrid. In Setter of this "SelectedItemDg1" property, set Collection2 as per your need.
Make sure to implement INotifyPropertyChanged interface and use ObservableCollection type for both the collections.
Following is the code sample for same :
Model Classes:
public class Country
{
public string CountryName { get; set; }
public int CountryId { get; set; }
public List<State> States { get; set; }
}
public class State
{
public string StateName { get; set; }
public int StateId { get; set; }
}
ViewModel :
public class MainWindowViewModel : INotifyPropertyChanged
{
public MainWindowViewModel()
{
CountriesCollection = new ObservableCollection<Country>();
StateCollection = new ObservableCollection<State>();
LoadData();
}
private ObservableCollection<Country> _CountriesCollection;
public ObservableCollection<Country> CountriesCollection
{
get { return _CountriesCollection; }
set
{
_CountriesCollection = value;
NotifyPropertyChanged("CountriesCollection");
}
}
private ObservableCollection<State> _StatesCollection;
public ObservableCollection<State> StateCollection
{
get { return _StatesCollection; }
set
{
_StatesCollection = value;
NotifyPropertyChanged("StateCollection");
}
}
private Country _SelectedCountry;
public Country SelectedCountry
{
get { return _SelectedCountry; }
set
{
_SelectedCountry = value;
if (_SelectedCountry != null && _SelectedCountry.States != null)
{
StateCollection = new ObservableCollection<State>(_SelectedCountry.States);
}
NotifyPropertyChanged("SelectedCountry");
}
}
private void LoadData()
{
if (CountriesCollection != null)
{
CountriesCollection.Add(new Country
{
CountryId = 1,
CountryName = "India",
States = new List<State>
{
new State { StateId = 1, StateName = "Gujarat"},
new State { StateId = 2, StateName = "Punjab"},
new State { StateId = 3, StateName = "Maharastra"}
}
});
CountriesCollection.Add(new Country
{
CountryId = 2,
CountryName = "Chine",
States = new List<State>
{
new State { StateId = 4, StateName = "Chine_State1"},
new State { StateId = 5, StateName = "Chine_State2"},
new State { StateId = 6, StateName = "Chine_State3"}
}
});
CountriesCollection.Add(new Country
{
CountryId = 3,
CountryName = "japan",
States = new List<State>
{
new State { StateId = 7, StateName = "Japan_State1"},
new State { StateId = 8, StateName = "Japan_State2"},
new State { StateId = 9, StateName = "Japan_State3"}
}
});
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(info));
}
}
XALM :
<StackPanel Orientation="Horizontal" >
<DataGrid AutoGenerateColumns="True"
Height="300" Width="300"
HorizontalAlignment="Left" Margin="30"
ItemsSource="{Binding CountriesCollection}"
SelectedItem="{Binding SelectedCountry}">
</DataGrid>
<DataGrid AutoGenerateColumns="True"
Height="300" Width="300"
HorizontalAlignment="Left" Margin="30"
ItemsSource="{Binding SelectedCountry.States}">
</DataGrid>
</StackPanel>
Here I have AutoGenerateColumns property of DataGrid but you have to change it as per your requirement.
I hope this sample code will make things easy to understand for you.
The simplest and cleanest way I found to do this sort of master-details binding on collections is to wrap ObservableCollection in a class, expose its ListCollectionView and bind your ItemsSource to it, like below (it has some extra code that is used to simplify xml serialization):
public class ViewableCollection<T> : ObservableCollection<T>
{
private ListCollectionView _View;
public ViewableCollection(IEnumerable<T> items)
: base(items) { }
public ViewableCollection()
: base() { }
[XmlIgnore]
public ListCollectionView View
{
get
{
if (_View == null)
{
_View = new ListCollectionView(this);
_View.CurrentChanged += new EventHandler(InnerView_CurrentChanged);
}
return _View;
}
}
[XmlIgnore]
public T CurrentItem
{
get
{
return (T)this.View.CurrentItem;
}
set
{
this.View.MoveCurrentTo(value);
}
}
private void InnerView_CurrentChanged(object sender, EventArgs e)
{
this.OnPropertyChanged(new PropertyChangedEventArgs("CurrentItem"));
}
public void AddRange(IEnumerable<T> range)
{
if (range == null)
throw new ArgumentNullException("range");
foreach (T item in range)
{
this.Items.Add(item);
}
this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, (IList)range.ToList()));
}
public void ReplaceItems(IEnumerable<T> range)
{
if (range == null)
throw new ArgumentNullException("range");
this.Items.Clear();
foreach (T item in range)
{
this.Items.Add(item);
}
this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public void RemoveItems(IEnumerable<T> range)
{
if (range == null)
throw new ArgumentNullException("range");
foreach (T item in range)
{
this.Items.Remove(item);
}
this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, (IList)range.ToList()));
}
public void ClearAll()
{
IList old = this.Items.ToList();
base.Items.Clear();
this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, old));
}
public void CallCollectionChaged()
{
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
// necessary for xml easy serialization using [XmlArray] attribute
public static implicit operator List<T>(ViewableCollection<T> o)
{
return o == null ? default(List<T>) : o.ToList();
}
// necessary for xml easy serialization using [XmlArray] attribute
public static implicit operator ViewableCollection<T>(List<T> o)
{
return o == default(List<T>) || o == null ? new ViewableCollection<T>() : new ViewableCollection<T>(o);
}
}
Then in your ViewModel (Remember to implement INotifyPropertyChanged, I use a nuget package Fody.PropertyChanged to automatically implement it on properties):
[ImplementPropertyChanged]
public class MyViewModel
{
public ViewableCollection<MySecondViewModel> Collection1 { get; set; }
public MyViewModel()
{
this.Collection1 = new ViewableCollection<MySecondViewModel>();
}
}
[ImplementPropertyChanged]
public class MySecondViewModel
{
public string MyColumn1 { get; set; }
public string MyColumn2 { get; set; }
public ViewableCollection<MyThirdViewModel> Collection2 { get; set; }
public MySecondViewModel()
{
this.Collection1 = new ViewableCollection<MyThirdViewModel>();
}
}
[ImplementPropertyChanged]
public class MyThirdViewModel
{
public string MyColumn1 { get; set; }
public string MyColumn2 { get; set; }
}
//...
this.DataContext = new MyViewModel();
Then, keeping your grids synchronized is as simple as this:
<DataGrid ItemsSource="{Binding Collection1.View, Mode=OneWay}"
IsSynchronizedWithCurrentItem="True" />
<DataGrid ItemsSource="{Binding Collection1.CurrentItem.Collection2.View, Mode=OneWay}"
IsSynchronizedWithCurrentItem="True" />
For example binding to a Column1 property in currently selected item in currently selected Collection2 will be:
<TextBlock Text="{Binding Collection1.CurrentItem.Collection2.CurrentItem.Column1}" />
Also, you can manage selection in your code behind, for example:
Collection1.CurrentItem=null;
will clear the selection on the collection.
You can also sort (and filter and group) the ViewableCollection from code behind like this:
Collection1.View.SortDescriptions.Add(new SortDescription("Column1",ListSortDirection.Ascending));
Just remember that you should't replace the whole ViewableCollection after it's been instantiated, just add/remove items from it (for this purpose there is the method AddRange and ReplaceItems for adding/replacing items in bulk without rising CollectionChanged events unnecessarily)
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;
My oneway binding isn't working as I'm expecting.
When I click a button to add a new "person" it doesn't add the newly entered person to the listview until I shut it down and restart the application (so the value gets added to the DB just not to the UI)
What am I doing incorrectly? I have the INotifyPropertyChanged, I have the ObservableCollection... What am I missing?
I have my Model:
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _personName;
private string _personEmail;
private string _personPhone;
private DateTime _personDOB;
[PrimaryKey, AutoIncrement]
public int personId { get; set; }
[MaxLength(25)]
public string personName {
get { return _personName; }
set
{
_personName = value;
OnPropertyChanged("personName");
}
}
[MaxLength(50)]
public string personEmail {
get { return _personEmail; }
set
{
_personEmail = value;
OnPropertyChanged("personEmail");
}
}
[MaxLength(13)]
public string personPhone {
get { return _personPhone; }
set
{
_personPhone = value;
OnPropertyChanged("personPhone");
}
}
public DateTime personDOB {
get { return _personDOB;}
set
{
_personDOB = value;
OnPropertyChanged("personDOB");
}
}
public Boolean isPersonActive { get; set; }
public string Summary
{
get { return string.Format("{0} - {1} : {2} -- {3}", personName, personEmail, personPhone, personDOB); }
}
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
In my ViewModel I have this:
public class ChoresVM
{
private ObservableCollection<win8Chores.Model.databaseTables.Person> _personList;
public ObservableCollection<win8Chores.Model.databaseTables.Person> personList
{
get { return _personList; }
set { _personList = value; }
}
...
public ObservableCollection<win8Chores.Model.databaseTables.Person> selectAllPerson()
{
using (var db = new SQLiteConnection(dbPath))
{
ObservableCollection<win8Chores.Model.databaseTables.Person> pList = new ObservableCollection<win8Chores.Model.databaseTables.Person>(db.Query<win8Chores.Model.databaseTables.Person>("select personId,personName,personEmail,personDOB from Person"));
_personList = new ObservableCollection<Model.databaseTables.Person>(db.Query<win8Chores.Model.databaseTables.Person>("select personId,personName,personEmail,personDOB from Person"));
return _personList;
}
}
public void insertPerson(string name, string email, string phone, DateTime dob, Boolean isActive = true)
{
dbPath = Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "myDB");
db = new SQLiteConnection(dbPath);
using (db)
{
var p = db.Insert(new win8Chores.Model.databaseTables.Person()
{
personName = name,
personEmail = email,
personPhone = phone,
personDOB = dob,
isPersonActive = isActive
});
}
selectAllPerson();
}
Then in my View:
public MainPage()
{
this.InitializeComponent();
VM = new ViewModel.ChoresVM();
DataContext = VM;
}
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
DateTime myDOB = new DateTime(1955, 02, 28);
VM.insertPerson("test","test#live.com","123-456-7890", myDOB);
}
With my XAML like this:
<ListView HorizontalAlignment="Left" Height="224" Margin="287,344,0,0" VerticalAlignment="Top" Width="740" x:Name="test" DisplayMemberPath="Summary" ItemsSource="{Binding personList, Mode=OneWay}" />
Try to implement INotifyPropertyChanged on ChoresVM and raise PropertyChanged("personList") when you initialize the collection (in selectAllPerson() or personList setter). This way itemssource binding will be notified, that collection property was changed and will pickup new collection. Also you have a typo. In Binding you have "PersonList" (Pascal case) but you property is in camel case (personList)
Im writing a simple wpf application, but Im stuck. I'd like to achieve, that I have a filter class, and If the id has been changed in the filter class by a user input, a list should refresh applying the filter. All the initial bindings are working. The list is displayed properly along with the CompanyId.
the databinding in xaml:
<ListBox Height="212" HorizontalAlignment="Left" Margin="211,31,0,0" Name="listBoxProducts" VerticalAlignment="Top" Width="267" ItemsSource="{Binding ElementName=this, Path=Products}" DisplayMemberPath="CompanyId" />
<TextBox Height="28" HorizontalAlignment="Left" Margin="12,31,0,0" Name="textBoxCompanyId" VerticalAlignment="Top" Width="170" Text="{Binding ElementName=this, Path=Company.Id}" />
The code-behind for the xaml:
private TestFactory _testFactory = new TestFactory();
private Company _company;
public Company Company
{
get { return _company; }
}
private IProductList _products;
public IProductList Products
{
get { return _products; }
}
public MainWindow()
{
_company = _testFactory.Company;
_products = _testFactory.Products;
InitializeComponent();
_company.FilterChanged += _testFactory.FilterChanging;
}
The (dummy)factory class:
private IProductList _products;
public IProductList Products
{
get { return _products; }
}
private Company _company = new Company();
public Company Company
{
get { return _company; }
}
public TestFactory()
{
_company = new Company() { Id = 2, Name = "Test Company" };
GetProducts();
}
public void GetProducts()
{
var products = new List<Product>();
products.Add(new Product() { ProductNumber = 1, CompanyId = 1, Name = "test product 1" });
products.Add(new Product() { ProductNumber = 2, CompanyId = 1, Name = "test product 2" });
products.Add(new Product() { ProductNumber = 3, CompanyId = 2, Name = "test product 3" });
if (Company.Id != 2)
{
products = products.Where(p => p.CompanyId == Company.Id).ToList();
}
_products = new ProductList(products);
}
public void FilterChanging(object sender, EventArgs e)
{
GetProducts();
}
The ProductList interface:
public interface IProductList : IList<Product>, INotifyCollectionChanged {}
The productlist class:
public class ProductList : IProductList
{
private readonly IList<Product> _products;
public ProductList() { }
public ProductList(IList<Product> products)
{
_products = products;
}
public IEnumerator<Product> GetEnumerator()
{
return _products.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Add(Product item)
{
_products.Add(item);
notifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
}
public void Clear()
{
_products.Clear();
notifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public bool Contains(Product item)
{
return _products.Contains(item);
}
public void CopyTo(Product[] array, int arrayIndex)
{
_products.CopyTo(array, arrayIndex);
}
public bool Remove(Product item)
{
var removed = _products.Remove(item);
if (removed)
{
notifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));
}
return removed;
}
public int Count
{
get { return _products.Count; }
}
public bool IsReadOnly
{
get { return _products.IsReadOnly; }
}
public int IndexOf(Product item)
{
return _products.IndexOf(item);
}
public void Insert(int index, Product item)
{
_products.Insert(index, item);
notifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public void RemoveAt(int index)
{
_products.RemoveAt(index);
notifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public Product this[int index]
{
get { return _products[index]; }
set
{
_products[index] = value;
notifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, _products[index]));
}
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
private void notifyCollectionChanged(NotifyCollectionChangedEventArgs args)
{
if (CollectionChanged != null)
{
CollectionChanged(this, args);
}
}
}
The Company class (filter class):
public class Company : INotifyPropertyChanged
{
private int _id;
public int Id
{
get { return _id; }
set
{
if (_id == value)
return;
_id = value;
OnPropertyChanged("Id");
OnFilterChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
private string _name;
public string Name
{
get { return _name; }
set
{
if (_name == value)
return;
_name = value;
OnPropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public event EventHandler FilterChanged;
private void OnPropertyChanged(string name)
{
if (PropertyChanged == null)
return;
var eventArgs = new PropertyChangedEventArgs(name);
PropertyChanged(this, eventArgs);
}
private void OnFilterChanged(NotifyCollectionChangedEventArgs e)
{
if (FilterChanged == null)
return;
FilterChanged(this, e);
}
}
The list is refreshed in factory, but there is no change in the view. I probably do something wrong, maybe my whole approach is not best. Maybe I have to use ObservableCollection type with valueconverter? Any help would be greatly appreciated. Cheers!
Use an ObservableCollection<Product> instead of creating your own list based on IList
The purpose of an ObservableCollection is to track changes to the collection, and it will automatically update the UI when the collection changes.
You might also consider using ICollectionView for this use-case.
Refer this post.