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.
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 a list item which has more or less around 10 objects. I could able to detect which item is selected and also I sending this item properties into the DetailViewModel,I am using messageprotocol in mvvmcross.
I could able to observe changes in the MainViewModel when user enters new value in the edittext in DetailViewModel.
I wonder how I am going to put these values back into the selected item and update list.
MainViewModel
private readonly IMvxMessenger _messenger;
private readonly MvxSubscriptionToken _token;
private MainViewModel _selectedItem;
public MainViewModel SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
ShowViewModel<DetailViewModel>(
new DetailViewModel.Parameter
{
Age= _selectedItem.Age,
Category = _selectedItem.Category,
});
RaisePropertyChanged(() => SelectedItem);
}
}
public MainViewModel(IMvxMessenger messenger) {
_messenger = messenger;
_token = messenger.Subscribe<SelectedItemMessage>(OnMessageReceived);;
}
private void OnMessageReceived(SelectedItemMessage obj)
{
// I could observe the DetailView Changes in the MainViewModel
// I wonder how to put these value back to selectedItem
double? Age = obj.Age;
int? Category= obj.Category;
}
public virtual ICommand ItemSelected
{
get{ return new MvxCommand<TestViewModel>(item =>{ SelectedItem = item;});
}
}
private ObservableCollection<TestViewModel> _testViews;
private ObservableCollection<WellTestViewModel> _allTestItemViews;
public void Init(string Id)
{
List<Test> allTests = new List<Test>();
allTests = _TestService.GetAllTestById(Id);
foreach (var test in allTests)
{
_testViews.Add(TestViewModel.CreateViewModel(test, this));
}
_allTestItemViews = _testViews;
}
TestViewModel
public static TestViewModel CreateViewModel(Test entity, MainViewModel parent = null)
{
if (entity == null)
{
return null;
}
return new TestViewModel(parent)
{
Age = entity.Age,
Category= entity.Category,
};
}
public TestViewModel()
{
// parameterless constructor
}
readonly MainViewViewModel _mainViewModel ;
public TestViewModel(MainViewViewModel mainViewViewModel)
{
_mainViewModel = mainViewViewModel;
}
DetailViewModel
private readonly IMvxMessenger _messenger;
public class Parameter
{
public double? Age{ get; set; }
public int? Category { get; set; }
}
public void Init(Parameter param)
{
Age= param.Age;
Category= param.Category;
}
public DetailViewModel(IMvxMessenger messenger) {
_messenger = messenger;
}
public void UpdateMethod() {
var message = new SelectedItemMessage(this, age, category);
_messenger.Publish(message, typeof(SelectedItemMessage));
}
SelectedItemMessage
public SelectedItemMessage(object sender, double? age, int? category) : base(sender)
{
Age = age;
Category = category;
}
public double? Age { get; set; }
public int? Category{ get; set; }
}
Just use your _selectedItem and set the properties on it.
private void OnMessageReceived(SelectedItemMessage obj)
{
_selectedItem.Age = obj.Age;
_selectedItem.Category= obj.Category;
}
You need to update the collection inside the OnMessageReceived method:
var item = _allTestItemViews.FirstOrDefault(i => i.Id == id);
if (item != null)
{
item.Age = age;
item.Category = category;
}
You need to add Id to your model class so that you can uniquely identify the item you need to update.
I populate a data grid with a list of objects that come from a repository like this:
public static IEnumerable<Order> GetOrdersForDataGrid()
{
IEnumerable<Order> query;
using (RSDContext = new RSDContext())
{
query = context.Orders.Include(o=>o.OrderDetails).ToList();
}
return query;
}
When I want to edit an order I pass the selected row to a new window like this:
OrderEditWindow orderEdit = new OrderEditWindow();
orderEdit.SelectedOrder = SelectedOrder;
orderEdit.ShowDialog();
Here I set the DataContext of the Window to:
DataContext = SelectedOrder;
In this window I have another data grid that binds to OrderDetails collection property of Order. The problem is on CRUD operations on OrderDetails. For example, after I add a new orderDetail like this:
private void AddProductDetailButton_OnClick(object sender, RoutedEventArgs e)
{
if (!ValidateProductDetail())
return;
var _selectedProduct = ProductAutoCompleteBox.SelectedItem as Product;
var selectedProduct = ProductsRepository.GetProductById(_selectedProduct.ProductId);
OrderDetail orderDetail = new OrderDetail();
orderDetail.Price = selectedProduct.Price;
orderDetail.ProductCode = selectedProduct.Code;
orderDetail.ProductName = selectedProduct.Name;
orderDetail.Quantity = int.Parse(QuantityNumericUpDown.Value.ToString());
orderDetail.Um = selectedProduct.Um;
orderDetail.Total = selectedProduct.Price * int.Parse(QuantityNumericUpDown.Value.ToString());
orderDetail.Group = selectedProduct.Subgroup.Group.Name;
orderDetail.Subgroup = selectedProduct.Subgroup.Name;
orderDetail.SupplierName = selectedProduct.Supplier.Name;
//orderDetail.Order=SelectedOrder;
//orderDetail.OrderId = SelectedOrder.OrderId;
SelectedOrder.OrderDetails.Add(orderDetail);
ProductAutoCompleteBox.Text = string.Empty;
QuantityNumericUpDown.Value = 1;
ProductAutoCompleteBox.Focus();
}
and then I call the update method from repository:
public static void UpdateOrder(Order order)
{
using (RSDContext context = new RSDContext())
{
context.Orders.Attach(order);
context.Entry(order).State = EntityState.Modified;
context.SaveChanges();
}
}
I get an error about OrderId. If i set manualy the navigation property and the id I don't get an error but changes dont get saved into db.
My Order model look like this:
public class Order : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Order()
{
_OrderDetails = new ObservableCollection<OrderDetail>();
_OrderDetails.CollectionChanged += _OrderDetails_CollectionChanged;
}
void _OrderDetails_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
AttachProductChangedEventHandler(e.NewItems.Cast<OrderDetail>());
if (e.OldItems != null)
CalcualteTotals();
}
[NotMapped]
public decimal CalculatedTotal
{
get
{
return OrderDetails.Sum(x => x.Total);
}
}
public int OrderId { get; set; }
private int _Number;
public int Number
{
get { return _Number; }
set
{
_Number = value;
NotifyPropertyChanged("Number");
}
}
private DateTime _Date;
public DateTime Date
{
get { return _Date; }
set
{
_Date = value;
NotifyPropertyChanged("Date");
}
}
private bool _Canceled;
public bool Canceled
{
get { return _Canceled; }
set
{
_Canceled = value;
NotifyPropertyChanged("Canceled");
}
}
private string _ClientName;
public string ClientName
{
get { return _ClientName; }
set
{
_ClientName = value;
NotifyPropertyChanged("ClientName");
}
}
private string _ClientPhone;
public string ClientPhone
{
get { return _ClientPhone; }
set
{
_ClientPhone = value;
NotifyPropertyChanged("ClientPhone");
}
}
private string _DeliveryAddress;
public string DeliveryAddress
{
get { return _DeliveryAddress; }
set
{
_DeliveryAddress = value;
NotifyPropertyChanged("DeliveryAddress");
}
}
private decimal _Transport;
public decimal Transport
{
get { return _Transport; }
set
{
_Transport = value;
NotifyPropertyChanged("Transport");
}
}
private decimal _Total;
public decimal Total
{
get { return _Total; }
set
{
_Total = value;
NotifyPropertyChanged("Total");
}
}
private ObservableCollection<OrderDetail> _OrderDetails;
public virtual ObservableCollection<OrderDetail> OrderDetails
{
//get { return _OrderDetails ?? (_OrderDetails = new ObservableCollection<OrderDetail>()); }
get
{
return _OrderDetails;
}
set
{
_OrderDetails = value;
NotifyPropertyChanged("OrderDetails");
}
}
private void AttachProductChangedEventHandler(IEnumerable<OrderDetail> orderDetails)
{
foreach (var p in orderDetails)
{
p.PropertyChanged += (sender, e) =>
{
switch (e.PropertyName)
{
case "Quantity":
case "Price":
case "Total":
CalcualteTotals();
break;
}
};
}
CalcualteTotals();
}
public void CalcualteTotals()
{
NotifyPropertyChanged("CalculatedTotal");
}
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
}
And my OrderDetail model look like this:
public class OrderDetail : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int OrderDetailId { get; set; }
public int OrderId { get; set; }
public Order Order { get; set; }
private int _ProductCode;
public int ProductCode
{
get { return _ProductCode; }
set
{
_ProductCode = value;
NotifyPropertyChanged("ProductCode");
}
}
private string _ProductName;
public string ProductName
{
get { return _ProductName; }
set
{
_ProductName = value;
NotifyPropertyChanged("ProductName");
}
}
private string _Um;
public string Um
{
get { return _Um; }
set
{
_Um = value;
NotifyPropertyChanged("Um");
}
}
private decimal _Price;
public decimal Price
{
get { return _Price; }
set
{
_Price = value;
NotifyPropertyChanged("Price");
NotifyPropertyChanged("Total");
}
}
private int _Quantity;
public int Quantity
{
get { return _Quantity; }
set
{
_Quantity = value;
NotifyPropertyChanged("Quantity");
NotifyPropertyChanged("Total");
}
}
private string _SupplierName;
public string SupplierName
{
get { return _SupplierName; }
set
{
_SupplierName = value;
NotifyPropertyChanged("SupplierName");
}
}
private string _Subgroup;
public string Subgroup
{
get { return _Subgroup; }
set
{
_Subgroup = value;
NotifyPropertyChanged("Subgroup");
}
}
private string _Group;
public string Group
{
get { return _Group; }
set
{
_Group = value;
NotifyPropertyChanged("Group");
}
}
public decimal _Total;
public decimal Total
{
get { return Quantity * Price; }
set
{
_Total = value;
NotifyPropertyChanged("Total");
}
}
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
}
I'm really trying to use some sort of unit of work and I don't understand how i'm supposed to apply CRUD on objects with child collections and keep the UI updated in the same time (by working in a ObservableCollection and using Binding ClientPhone, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged my parent window is updated as I type)
A final working solution:
using (RSDContext context = new RSDContext())
{
var details = order.OrderDetails;
order.OrderDetails = null;
List<int> OriginalOrderDetailsIds =
context.OrderDetails.Where(o => o.OrderId == order.OrderId).Select(o => o.OrderDetailId).ToList();
List<int> CurrentOrderDetailsIds = details.Select(o => o.OrderDetailId).ToList();
List<int> DeletedOrderDetailsIds = OriginalOrderDetailsIds.Except(CurrentOrderDetailsIds).ToList();
context.Entry(order).State = EntityState.Modified;
foreach (var deletedOrderDetailId in DeletedOrderDetailsIds)
{
context.Entry(context.OrderDetails.Single(o => o.OrderDetailId == deletedOrderDetailId)).State = EntityState.Deleted;
}
foreach (OrderDetail detail in details)
{
// Add.
if (detail.OrderDetailId == 0)
{
detail.OrderId = order.OrderId;
context.Entry(detail).State = EntityState.Added;
}
// Update.
else
{
context.Entry(detail).State = EntityState.Modified;
}
}
context.SaveChanges();
}
You could do this way for adding and updating the child, but not sure about deleted order details in the ui. If you don't want to get the order from entity, you need some kind of marking in the OrderDetail for deleted OrderDetail.
using (RSDContext context = new RSDContext())
{
var details = order.OrderDetails;
order.OrderDetails = null;
context.Entry(order).State = EntityState.Modified;
foreach (var detail in details)
{
if (detail.Id == 0)
{
// Adds.
detail.OrderId = order.Id;
context.Entry(detail).State = EntityState.Added;
}
else if (detail.IsDeleted)
// Adds new property called 'IsDeleted'
// and add [NotMapped] attribute
// then mark this property as true from the UI for deleted items.
{
// Deletes.
context.Entry(detail).State = EntityState.Deleted;
}
else
{
// Updates.
context.Entry(detail).State = EntityState.Modified;
}
}
order.OrderDetails = details;
context.SaveChanges();
}
I've faced a really strange behavior while using CollectionViews and wonder if somebody can explain me that behavior.
I've got an ObservableCollection, lets call it _sourcelist. Then I create several CollectionViews of _sourcelist, in that way:
CollectionViewSource cvs = new CollectionViewSource() { Source = _sourcelist };
ICollectionView list = cvs.View;
These CollectionViews are each used with a different filter and one CollectionView with no filter (so representing the original source). In my Application I have a TabControl where every TabItem contains a ListBox with one of these CollectionViews as ItemsSource. When I know delete an item from the sourcelist, the ListBox with the CollectionView with no filter gets updated, but the other ListBoxes that contains filtered CollectionViews and contain the deleted item don't get updated. So the deleted item remains in that ListBoxes.
Now, the strange behavior is following:
When I add the following code after code written above:
list.CollectionChanged += (o,e) => { };
Then the other ListBoxes get updated, too.
Why that? I mean, I just registered an event handler for the CollectionChanged event, which does nothing!
So, here is my Code Sample. I know it is a big amount of code, but it is ready to run and reproduces the strange behavior.
When you run the code and try to delete an item in the "All" List, then the corresponding item in the filtered "Under 30" or "Over 30" list will not be deleted. Now uncomment the commented line in the ListViewModel (it is just a registration of an empty Eventhandler), then the corresponding items in the filtered lists will be deleted.
If you have any questions, then please ask! :-)
Thank you for your interest in solving the problem. :-)
P.S.: The RelayCommand is from the assembly: Microsoft.TeamFoundation.Controls
MainWindow.xaml:
<Window x:Class="CollectionTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CollectionTest"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type local:ListViewModel}">
<ListBox ItemsSource="{Binding List}" SelectedItem="{Binding Selected}" BorderBrush="Transparent" Margin="0" HorizontalContentAlignment="Stretch"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:PersonViewModel}">
<TextBlock Margin="5,0" VerticalAlignment="Center" Text="{Binding Name}"/>
</DataTemplate>
<Style x:Key="{x:Type TabItem}" TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding Name}"/>
</Style>
</Window.Resources>
<DockPanel>
<Button DockPanel.Dock="Bottom" Name="DeleteButton" Content="Delete" Command="{Binding DeleteCommand}"/>
<TabControl Name="TabControl" ItemsSource="{Binding Lists}" Padding="0" SelectedItem="{Binding Selected}"/>
</DockPanel>
</Window>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
private MainViewModel mvm;
public MainWindow()
{
InitializeComponent();
mvm = new MainViewModel(CreateRepo());
this.DataContext = mvm;
}
private Repository CreateRepo()
{
Repository repo = new Repository();
for (int i = 20; i < 45; i++)
{
Person p = new Person() { Name = "Person" + i, Age = i };
repo.Add(p);
}
return repo;
}
}
Person:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
Repository:
public class Repository
{
private readonly List<Person> _list;
public event EventHandler<RepositoryChangedEventArgs> Added;
public event EventHandler<RepositoryChangedEventArgs> Removed;
public Repository()
{
this._list = new List<Person>();
}
public void Add(Person person)
{
if (person == null)
{
throw new ArgumentNullException("person");
}
if (!this._list.Contains(person))
{
this._list.Add(person);
if (this.Added != null)
{
this.Added(this, new RepositoryChangedEventArgs(person));
}
}
}
public void Remove(Person person)
{
if (person == null)
{
throw new ArgumentNullException("person");
}
if (this._list.Contains(person))
{
this._list.Remove(person);
if (this.Removed != null)
{
this.Removed(this, new RepositoryChangedEventArgs(person));
}
}
}
public List<Person> Get()
{
return new List<Person>(this._list);
}
}
public class RepositoryChangedEventArgs : EventArgs
{
public Person Entity { get; set; }
public RepositoryChangedEventArgs(Person entity)
{
this.Entity = entity;
}
}
PersonViewModel:
public class PersonViewModel : INotifyPropertyChanged
{
private Person _person;
private Repository _repository;
public string Name
{
get
{
return this._person.Name;
}
set
{
if (this._person.Name != value)
{
this._person.Name = value;
this.OnPropertyChanged("Name");
}
}
}
public int Age
{
get
{
return this._person.Age;
}
set
{
if (this._person.Age != value)
{
this._person.Age = value;
this.OnPropertyChanged("Age");
}
}
}
public PersonViewModel(Person person, Repository repository)
{
this._person = person;
this._repository = repository;
}
public void Delete()
{
this._repository.Remove(this._person);
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name)
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
ListViewModel:
public class ListViewModel : INotifyPropertyChanged
{
private IEnumerable _sourceList;
private ICollectionView _list;
private PersonViewModel _selected;
private string _name;
public ICollectionView List
{
get
{
return this._list;
}
}
public PersonViewModel Selected
{
get
{
return this._selected;
}
set
{
if (this._selected != value)
{
this._selected = value;
this.OnPropertyChanged("Selected");
}
}
}
public string Name
{
get
{
return this._name;
}
set
{
if (this._name != value)
{
this._name = value;
this.OnPropertyChanged("Name");
}
}
}
public ListViewModel(string name, IEnumerable list)
{
this.Name = name;
this._sourceList = list;
CollectionViewSource cvs = new CollectionViewSource() { Source = list };
this._list = cvs.View;
// cvs.View.CollectionChanged += (o, e) => { }; // uncomment this line to let it work
}
public ListViewModel(String name, IEnumerable list, Predicate<object> filter)
: this(name, list)
{
this._list.Filter = filter;
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
MainViewModel:
public class MainViewModel : INotifyPropertyChanged
{
protected ObservableCollection<PersonViewModel> _sourceList;
protected ObservableCollection<ListViewModel> _lists;
protected Repository _repository;
protected Dictionary<Person, PersonViewModel> _dictionary;
private RelayCommand _deleteCommand;
private ListViewModel _selected;
public ListViewModel Selected
{
get
{
return this._selected;
}
set
{
if (this._selected != value)
{
this._selected = value;
this.OnPropertyChanged("Selected");
}
}
}
public RelayCommand DeleteCommand
{
get
{
if (this._deleteCommand == null)
{
this._deleteCommand = new RelayCommand(this.ExecutedDelete, this.CanExecuteDelete);
}
return this._deleteCommand;
}
}
public IEnumerable<ListViewModel> Lists
{
get
{
return this._lists;
}
}
public MainViewModel(Repository repository)
{
this._sourceList = new ObservableCollection<PersonViewModel>();
this._lists = new ObservableCollection<ListViewModel>();
this._dictionary = new Dictionary<Person, PersonViewModel>();
this._repository = repository;
this._repository.Added += this.OnAdded;
this._repository.Removed += this.OnRemoved;
this.CreateSourceList(repository);
this.CreateLists();
if (this._lists.Count > 0)
{
this.Selected = this._lists[0];
}
}
protected void CreateSourceList(Repository repository)
{
foreach (Person person in repository.Get())
{
PersonViewModel pvm = new PersonViewModel(person, repository);
this._dictionary.Add(person, pvm);
this._sourceList.Add(pvm);
}
}
protected void CreateLists()
{
this._lists.Add(new ListViewModel("All", this._sourceList));
this._lists.Add(new ListViewModel("Under 30", this._sourceList,
delegate(object o)
{
PersonViewModel pvm = o as PersonViewModel;
return (pvm != null && pvm.Age < 30);
}));
this._lists.Add(new ListViewModel("Over 30", this._sourceList,
delegate(object o)
{
PersonViewModel pvm = o as PersonViewModel;
return (pvm != null && pvm.Age > 30);
}));
}
protected void Remove(Person person)
{
PersonViewModel pvm = this._dictionary[person];
this._dictionary.Remove(person);
this._sourceList.Remove(pvm);
}
protected void OnAdded(object sender, RepositoryChangedEventArgs args)
{
Person addedPerson = args.Entity;
if (addedPerson != null)
{
PersonViewModel pvm = new PersonViewModel(addedPerson, this._repository);
this._dictionary.Add(addedPerson, pvm);
this._sourceList.Add(pvm);
}
}
protected void OnRemoved(object sender, RepositoryChangedEventArgs args)
{
Person removedPerson = args.Entity;
if (removedPerson != null)
{
this.Remove(removedPerson);
}
}
private bool CanExecuteDelete(object o)
{
return true;
}
private void ExecutedDelete(object o)
{
PersonViewModel pvm = null;
if (this.Selected != null && (pvm = this.Selected.Selected) != null)
{
pvm.Delete();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
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