WPF combobox does not update when values change - c#

I have WPF application with Combobox and Button. After I enter value into textbox and press button, the value from textbox should appear in updated list of combobox. I am trying to achieve this with MVVM and binding to combobox. Here is part of code from ViewModel.
public class ViewModel:INotifyPropertyChanged
{
DomainLogic dl = new DomainLogic();
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<string> expenseCategories = new ObservableCollection<string>();
public ObservableCollection<string> ExpenseCategories
{
get
{
return expenseCategories;
}
set
{
expenseCategories = value;
OnPropertyChanged("ExpenseCategories");
}
}
public ViewModel()
{
expenseCategories = new ObservableCollection<string>(dl.GetExpenseCategories());
}
private void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
Also I am using EF to access DB and DomainLogic class has a method to list all Expense Categories.
Here is code-behind from window:
DomainLogic dl = new DomainLogic();
public Window1()
{
InitializeComponent();
DataContext = new ViewModel();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
if (!string.IsNullOrWhiteSpace(textBox.Text))
{
dl.CreateNewExpenseCategory(textBox.Text);
}
else
{
MessageBox.Show("Enter category!");
}
}
Here is also XAML:
<ComboBox x:Name="ExpCategory" HorizontalAlignment="Left" Margin="72,50,0,0" VerticalAlignment="Top" Width="130" ItemsSource="{Binding ExpenseCategories, UpdateSourceTrigger=PropertyChanged}" />
When I add the new category the combobox isn't updated. I'm new to the whole MVVM pattern and I think I'm missing something here.
//EDIT
public void CreateNewExpenseCategory(string name)
{
using (var context = new ExpenseEntities())
{
ExpenseCategory category = new ExpenseCategory() { CategoryName = name};
context.ExpenseCategory.Add(category);
context.SaveChanges();
}
}

The problem is that CollectionChanged event doesn't fire.
You are adding a new element inside your DataContext, but you aren't updating your local view of data.
Once you update the DataContext you should refresh your ObservableCollection or use Local.
Here how you could use Local:
public ViewModel()
{
expenseCategories = dl.GetExpenseCategories().Local;
}
So you can directly do:
expenseCategories.Add(new ExpenseCategory() {textBox.Text});
dl.GetContext().SaveChanges();
Or you have to update the ObservableCollection:
dl.CreateNewExpenseCategory(textBox.Text);
// Update your ViewModel ObservableCollection.
However i think that you should use a Command and not an Event so you can update the ObservableCollection directly inside the ViewModel.
Example:
using Prism.Commands;
//Other usings
public class ViewModel : INotifyPropertyChanged
{
// Your class methods and properties
public DelegateCommand<string> AddNewExpenseCategory
{
get
{
return new DelegateCommand<string>(Execute_AddNewExpenseCategory);
}
}
public void Execute_AddNewExpenseCategory(string param)
{
expenseCategories.Add(new ExpenseCategory() { param });
dl.GetContext().SaveChanges();
}

Related

WPF - DataGrid doesn't show list

My goal is to output a list in a datagrid, but this doesn't work and the datagrid is empty.
I tried to display the list in an other way and it did (but I can't remember what it was) and it worked, except for it not being in a datagrid but just data. I have changed up some things, but back then it reached the end and got displayed.
ViewModel in Mainwindow:
public class ViewModel
{
public List<ssearch> Items { get; set; }
private static ViewModel _instance = new ViewModel();
public static ViewModel Instance { get { return _instance; } }
}
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
//For simplicity, let's say this window opens right away
var Mdata = new MDataWindow { DataContext = DataContext };
Mdata.Show();
}
Other Window for data display:
string searchParam = "status = 1";
public MDataWindow()
{
InitializeComponent();
}
private void AButton_Click(object sender, RoutedEventArgs e)
{
MainWindow.ViewModel.Instance.Items = Search(searchParam);
}
public List<ssearch> Search(string where)
{
{
//Lots of stuff going on here
}
return returnList;
}
And in WPF:
<Window x:Class="WPFClient.MDataWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPFClient"
mc:Ignorable="d"
Title="MDataWindow" Height="Auto" Width="Auto">
<StackPanel>
<Button x:Name="AButton" Click="AButton_Click" Content="Load" />
<DataGrid ItemsSource="{Binding Items}" />
</StackPanel>
</Window>
I have no clue where the error is and tried to strip the code down as much as possible without killing error sources. The Datagrid just stays empty when I press the "Load" button.
EDIT:
I tried to convert the list into an observableColletion before passing it to the ViewModel, but this didn't work. I am working with a library, which I am not sure how to use observableCollection with, so I converted it instead of using it right away:
VM:
public ObservableCollection<Product> Items { get; set; }
Data Window:
List<Product> pp = Search_Products(searchParam);
var oc = new ObservableCollection<Product>(pp);
MainWindow.ViewModel.Instance.Items = oc;
First, change your List<Product> to an ObservableCollection<Product> as this will help to display the Items of the list on Add/Remove immediately.
This is because ObservableCollection implements the INotifyCollectionChanged interface to notify your target(DataGrid) to which it is bound, to update its UI.
Second, your binding can never work as expected due to changed reference of your collection.
private void AButton_Click(object sender, RoutedEventArgs e)
{
// You are changing your Items' reference completely here, the XAML binding
// in your View is still bound to the old reference, that is why you're seeing nothing.
//MainWindow.ViewModel.Instance.Items = Search(searchParam);
var searchResults = Search(searchParam);
foreach(var searchResult in searchResults)
{
MainWindow.ViewModel.Instance.Items.Add(searchResult);
}
}
Make sure you have changed the List to ObservableCollection upon running the Add loop, else you will get an exception saying the item collection state is inconsistent.
The ViewModel class should implement the INotifyPropertyChanged interface and raise its PropertyChanged event whenever Items is set to a new collection:
public class ViewModel : INotifyPropertyChanged
{
private List<ssearch> _items;
public List<ssearch> Items
{
get { return _items; }
set { _items = value; OnPropertyChanged(); }
}
private static ViewModel _instance = new ViewModel();
public static ViewModel Instance { get { return _instance; } }
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
This is required to notify the view regardless of the type of Items.
If you change the type of Items to ObservableCollection<T>, you should initialize the collection in the view model once:
public class ViewModel
{
public ObservableCollection<ssearch> Items { get; } = new ObservableCollection<ssearch>();
private static ViewModel _instance = new ViewModel();
public static ViewModel Instance { get { return _instance; } }
}
...and then add items to this collection instead of setting the property to a new one:
private void AButton_Click(object sender, RoutedEventArgs e)
{
MainWindow.ViewModel.Instance.Items.Clear();
var search = Search(searchParam);
if (search != null)
foreach (var x in search)
MainWindow.ViewModel.Instance.Items.Add(x);
}

WPF MVVM code for button binding with a parameter

I have a WPF application that has multiple comboboxes and buttons. I am learning the MVVM model with this application. The first combobox will display a list of database instances. This is done at the start of the application. This works fine.
There is a button object next to the database instances combobox. When the user clicks this button I need to get the contents of the database instance combobox and use it in a call to get all the databases in that instance. I am using a RelayCommand (ICommand) for the actions. The action for the button is getting setup correctly. I have a method SelectedDatabase in the DBInstance class but it is null when I click the button.
In the LoadDBInfo method below the selectedItem parameter is null.
Here is my XAML:
<ComboBox x:Name="cbxRLFDBInstances" ItemsSource="{Binding DBInstances}"
SelectedValue="{Binding SelectedDBInstance}" SelectedValuePath="value"
HorizontalAlignment="Left" Height="28" Margin="189,87,0,0" VerticalAlignment="Top"
Width="250" FontFamily="Arial" FontSize="14.667"
IsEditable="True"/>
<Button x:Name="btnRLFDBLoadDBInfo" Content="Load DB Info" Command="{Binding LoadDBInfoCommand}"
CommandParameter="{Binding SelectedDBInstance}" HorizontalAlignment="Left" Height="26" Margin="475,89,0,0" VerticalAlignment="Top"
Width="101" FontFamily="Arial" FontSize="14.667" Background="#FFE8F9FF"
ToolTip="Click here after choosing or typing in the datbase instance. This will populate the database list."/>
<ComboBox x:Name="cbxRLFDBName" HorizontalAlignment="Left" Height="28" Margin="189,132,0,0"
ItemsSource="{Binding DBDatabases}" SelectedValue="{Binding SelectedDBDatabase}"
SelectedValuePath="value" VerticalAlignment="Top" Width="250" FontFamily="Arial"
FontSize="14.667" IsEditable="True" IsReadOnly="True"
ToolTip="Once a database is choosen the table list will automatically be populated."/>
Here is my ViewModel:
namespace DatabaseTest.ViewModel
{
class RLFDatabaseTableViewModel
{
Utilities dbtUtilities = new Utilities();
public RelayCommand LoadDBInfoCommand
{
get;
set;
}
public RLFDatabaseTableViewModel()
{
LoadDBInstances();
LoadDBInfoCommand = new RelayCommand(LoadDBInfo);
}
#region Database Instance
public IList<DBInstance> DBInstances
{
get;
set;
}
public void LoadDBInstances()
{
IList<DBInstance> dbInstances = nList<DBInstance>();
DataTable dt = SmoApplication.EnumAvailableSqlServers(false);
dbInstances.Add(new DBInstance { DBInstanceName = "fal-conversion\\mun2012ci" });
dbInstances.Add(new DBInstance { DBInstanceName = "fal-conversion\\mun2014ci" });
if (dt.Rows.Count > 0)
{
foreach (DataRow dr in dt.Rows)
{
dbInstances.Add(new DBInstance { DBInstanceName = dr["Name"].ToString() });
}
}
DBInstances = dbInstances;
}
#endregion Database Instance
#region Database Names
public IList<DBDatabase> DBDatabases
{
get;
set;
}
public void LoadDBDatabases()
{
IList<DBDatabase> dbDatabases = new List<DBDatabase>();
dbDatabases.Add(new DBDatabase { DBDatabaseName = "DB - A" });
dbDatabases.Add(new DBDatabase { DBDatabaseName = "DB - B" });
DBDatabases = dbDatabases;
}
#endregion Database Names
#region Button Cammands
void LoadDBInfo(object selectedItem)
{
SqlConnection sqlConn = null;
IList<DBDatabase> dbDatabaseNames = new List<DBDatabase>();
// string selectedItem = dbInstances.
//Setting the PUBLIC property 'TestText', so PropertyChanged event is fired
if (selectedItem == null)
dbDatabaseNames = null;
else
{
SelectedDBInstance = selectedItem as DBInstance;
dbDatabaseNames = dbtUtilities.GetDBNames(sqlConn, _selectedDBInstance.ToString(),
_selectedDBDatabase.ToString());
}
DBDatabases = dbDatabaseNames;
}
#endregion Button Commands
}
Here is my Model:
namespace DatabaseTest.Model
{
public class RLFDatabaseTableModel { }
public class DBInstance : INotifyPropertyChanged
{
private string strDBInstance;
public override string ToString()
{
return strDBInstance;
}
public string DBInstanceName
{
get
{
return strDBInstance;
}
set
{
if (strDBInstance != value)
{
strDBInstance = value;
RaisePropertyChanged("DBInstanceName");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
public class DBDatabase : INotifyPropertyChanged
{
private string strDBDatabase;
public override string ToString()
{
return strDBDatabase;
}
public string DBDatabaseName
{
get
{
return strDBDatabase;
}
set
{
if (strDBDatabase != value)
{
strDBDatabase = value;
RaisePropertyChanged("DBDatabaseName");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
}
EDIT: This is my code to load the 2nd combobox, cbxRLFDBName, The DBDatabase has the values but the combobox is not loaded.
public void LoadDatabases(string strDBInstanceName)
{
string strQuery;
IList<DBDatabase> dbDatabases = new List<DBDatabase>();
SqlConnection sqlUtilDBConn = null;
try
{
if (sqlUtilDBConn != null)
{
sqlUtilDBConn.Close();
}
sqlUtilDBConn = dbtUtilities.LoginToDatabase(strDBInstanceName, "master");
strQuery = "select name from sys.databases order by 1";
using (SqlCommand sqlCmd = new SqlCommand(strQuery, sqlUtilDBConn))
{
SqlDataReader sqlDataRead = sqlCmd.ExecuteReader();
while (sqlDataRead.Read())
{
string strDBNme = sqlDataRead.GetString(0);
dbDatabases.Add(new DBDatabase { DBDatabaseName = strDBNme });
}
sqlDataRead.Close();
sqlCmd.Dispose();
}
}
catch (Exception exQuery)
{
string strMsg;
strMsg = "GetNumRows: Error, '" + exQuery.Message + "', has occurred.";
System.Windows.MessageBox.Show(strMsg);
}
DBDatabases = dbDatabases;
}
EDIT: I have removed some of the code that is not needed in the hopes that this will be easier to read. My issue is that combobox "cbxRLFDBInstances" with ItemsSource="{Binding DBInstances}" loads the combobox fine. I also have another combobox, "cbxRLFDBName" with ItemsSource="{Binding DBDatabases}". When I choose the appropriate database instance and click the Load DB Info button, LoadDatabases runs and I can see that DBDatabases has the information needed in it. However the combobox is not loaded and I do not have a failure. Why does one ItemsSource data binding work and the other does not? I believe I am setting the class correctly but it seems lo=ike the binding is not happening? What have I missed?
Your code look fine to me, except for the SelectedValuePath="value" on the ComboBoxes. SelectedValuePath specifies a property on the selected item that is to be bound to the SelectedValue. SelectedDBInstance is of type DBInstance and DBInstance class does not define a value property, so I'd say you just have to remove SelectedValuePath="value" from the ComboBoxes.
Edit:
You need your ViewModel to implement INotifyPropertyChanged:
class RLFDatabaseTableViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
// the rest of RLFDatabaseTableViewModel implementation ...
}
And then every time you change a property value inside ViewModel, you also need to call RaisePropertyChanged immediately after. For example:
DBDatabases = dbDatabaseNames;
RaisePropertyChanged("DBDatabases");
It is helpful to define your properties like so:
public string StringProperty
{
get { return this.stringProperty; }
set {
this.stringProperty = value;
this.RaisePropertyChanged("StringProperty");
}
}
private string stringProperty;
Then you can just write
this.StringProperty = "new value";
and the new value will be set and a change notification sent.
You have to send the notifications because the View (XAML) and ViewModel are different classes and the View has no way of knowing that a property on the ViewModel has changed. If ViewModel implements INotifyPropertyChanged, WPF will listen for property changes through the PropertyChanged event and update the View accordingly.
Have you tried to pass command parameter as selected izem from combobox, something like:
CommandParameter="{Binding SelectedItem,ElementName=yourComboBoxName}"

How to properly update UserControl combobox's Itemsource?

I'm completely new to WPF and I'm having problems with ItemsSource updates.
I created a single main window Metro application with tabs (TabItem(s) as UserControl DataContext="{Binding}") in which different data is displayed / different methods used.
What I've found myself struggling with is INotifyPropertyChanged (I wasn't able to understand the solution of my problem from similar examples/questions) interface's concept. I'm trying to make that if new data is entered in a window (which is initialized from one of the UserControl), a ComboBoxin another UserControl (or TabItem) would be automatically updated. Here's what I have:
UserControl1.xaml
public partial class UserControl1: UserControl
{
private userlist addlist;
public UserControl1()
{
InitializeComponent();
fillcombo();
}
public void fillcombo()
{
Fillfromdb F = new Fillfromdb(); // class that simply connects
// to a database sets a datatable as ListCollectionView
addlist = new addlist { List = F.returnlistview() }; // returns ListCollectionView
UsersCombo.ItemsSource = addlist.List;
}
userlist.cs
public class userlist: INotifyPropertyChanged
{
private ListCollectionView _list;
public ListCollectionView List
{
get { return this._list; }
set
{
if (this._list!= value)
{
this._list= value;
this.NotifyPropertyChanged("List");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Registration.xaml (called from another UserControl)
public partial class Registration: MetroWindow
{
public Registration()
{
InitializeComponent();
}
private void confirm_button_click(object sender, RoutedEventArgs e)
{
// new user is saved to database
// * here is where I don't know what to do, how to update the ItemSource
}
}
Here's the ComboBox's setting in UserControl.xaml:
<ComboBox x:Name="UsersCombo"
ItemsSource="{Binding List, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
Since I don't have any programming education/experience a very generic advice/explanation would be very much appreciated.
EDIT: Registration.xaml with propertychanged (still doesn't work):
public partial class Registration : MetroWindow
{
public userlist instance = new userlist();
public ListCollectionView _list1;
public ListCollectionView List1
{
get { return this._list1; }
set
{
if (this._list1 != value)
{
this._list1 = value;
this.NotifyPropertyChanged("List1");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public Registration()
{
InitializeComponent();
instance.List.PropertyChanged += ComboPropertyChangedHandler();
}
private void confirm_button_click(object sender, RoutedEventArgs e)
{
// new user is save to database
// still don't now what to do with new ListCollectionView from database
}
public void ComboPropertyChangedHandler(object obj)
{
List1 = instance.List; // when new data from database should be loaded?
}
This is where PropertyChanged event comes handy.
Bind the combobox in second xaml page to a List and create a similar property like in first xaml.
In second xaml.cs
public partial class Registration: MetroWindow, INotifyPropertyChanged
{
private userlist instance = new userlist();
private ListCollectionView _list1;
public ListCollectionView List1
{
get { return this._list1; }
set
{
if (this._list1 != value)
{
this._list1 = value;
this.NotifyPropertyChanged("List1");
}
}
}
public Registration()
{
InitializeComponent();
instance.List.PropertyChanged += ComboPropertyChangedHandler();
}
private void ComboPropertyChangedHandler(object obj)
{
List1 = instance.List;
//or iterate through the list and add as below
foreach(var item in instance.List)
{
List1.Add(item);
}
}
private void confirm_button_click(object sender, RoutedEventArgs e)
{
// new user is saved to database
// * here is where I don't know what to do, how to update the ItemSource
}
}

Databinding - Listbox doesn't update when updating data after reloading from database

I have a listbox which has items populated from a database. Now I want to update a listbox with a new string value each time I call the Add function.
I did this in 2 ways.
I added the new value to the database and updated the ViewModel class where the listbox is binded to. And this works fine. (see AddNewNameFirstWay method below)
I added the new value to the database, reloaded the values from the database and updated the ViewModel. But this doesn't work. (see AddNewNameSecondWay method below)
Here is my ViewModel code
public class ViewModel
{
private DBContext context = new DBContext("Data source=isostore:/names2.sdf");
private ObservableCollection<NameTable> nameCollection;
public ObservableCollection<NameTable> NameCollection
{
get
{
return nameCollection;
}
set
{
if (nameCollection != value)
{
nameCollection = value;
NotifyPropertyChanged();
}
}
}
public void AddNewNameSecondWay(string s)
{
NameTable t = new NameTable() { Name = s };
context.NameDatabaseTable.InsertOnSubmit(t);
context.SubmitChanges();
LoadFromDB();
}
public void AddNewNameFirstWay(string s)
{
NameTable t = new NameTable() { Name = s };
context.NameDatabaseTable.InsertOnSubmit(t);
context.SubmitChanges();
NameCollection.Add(t);
}
public void LoadFromDB()
{
var query = from i in context.NameDatabaseTable
select i;
NameCollection = new ObservableCollection<NameTable>(query);
}
private void NotifyPropertyChanged([System.Runtime.CompilerServices.CallerMemberName]string propertyName = null)
{
var tmp = PropertyChanged;
if (tmp != null)
tmp(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Here is my XAML binding code
<ListBox ItemsSource="{Binding NameCollection, Mode=OneWay}" >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name, Mode=OneWay}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
It seems to me that second method doesn't work, because the ObservableCollection memory reference changes. If this is correct, how to update the binding properly?
Reason I use the second method is that, I want to make sure all the DB constraints stands true for the values I insert.
You need to notify about the changes in property. Try this way for Framework 4.0,
Mode=OneWayToSource,UpdateSourceTrigger=PropertyChanged
Hope it helps...
Call OnPropertyChanged when you want notification about changes in your collection:
OnPropertyChanged("NameCollection")
Your ViewModel:
public class ViewModel: INotifyPropertyChanged
{
private DBContext context = new DBContext("Data source=isostore:/names2.sdf");
private ObservableCollection<NameTable> nameCollection;
public ObservableCollection<NameTable> NameCollection
{
get
{
return nameCollection;
}
set
{
if (nameCollection != value)
{
nameCollection = value;
OnPropertyChanged("NameCollection");
}
}
}
public void AddNewNameSecondWay(string s)
{
// your code
OnPropertyChanged("NameCollection");
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}

ObservableCollection via ListCollectionView displays incorrect list of items

C#:
public partial class MainWindow : Window
{
private readonly ViewModel vm;
public MainWindow()
{
InitializeComponent();
vm = new ViewModel();
DataContext = vm;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
vm.Models.RemoveAt(0);
}
}
public class ViewModel
{
public ObservableCollection<Model> Models { get; set; }
public ListCollectionView View { get; set; }
public ViewModel()
{
Models = new ObservableCollection<Model>()
{
new Model() { Name = "Gordon Freeman" },
new Model() { Name = "Isaac Kleiner" },
new Model() { Name = "Eli Vance" },
new Model() { Name = "Alyx Vance" },
};
Models.CollectionChanged += (s, e) => View.Refresh();
View = new ListCollectionView(Models);
}
}
public class Model
{
public string Name { get; set; }
public override string ToString()
{
return Name;
}
}
XAML:
<StackPanel>
<ListBox ItemsSource="{Binding Path=View}" />
<Button Click="Button_Click">Click</Button>
</StackPanel>
The ObservableCollection contains 4 elements, and the ListBox is displaying all 4, as expected. When the button is clicked, the 1st element of the ObservableCollection is removed. The ListBox, however, is now displaying only the 2nd and 3rd. It would appear the 1st and 4th have been removed.
If the line Models.CollectionChanged += (s, e) => View.Refresh(); is moved after View = new ListCollectionView(Models); (or commented out entirely) things work as expected.
Why?
P.S. This is a simple piece of a larger puzzle. In this small example, I realize I don't need to call View.Refresh(); on CollectionChanged for the ListBox to update itself.
My guess would be that the refresh interferes with the automatic update by the view. Presumably the view subscribes to CollectionChanged as well in the constructor, so if you also subscribe to the event before the view does and call refresh you get an unwanted update in between the collection change and the view's own update.
e.g.
Item 0 is removed -> Notify event listeners
=> Your handler: Refresh() -> Rebuild view => Item gets removed.
=> View handler: Event args say: Item X was removed -> Remove item X
This still does not explain why the first and the last items get removed but it seems reasonable to me.
If the subscription is after the view instantiation:
Item 0 is removed -> Notify event listeners
=> View handler: Event args say: Item X was removed -> Remove item X
=> Your handler: Refresh() -> Rebuild view => Nothing changed.
Seems to be a problem with ListView refresh. If you bind a labe/TextBlock to ListView.Items.Count, you'll see that the list still has 3 items (after first delete).
Even though you're wrapping your Model class in an ObservableCollection, you still need to implement INotifyPropertyChanged for it. I've banged my head against this problem enough times that now I remember to start troubleshooting at the lowest building block, i.e. Model.
public class Model : INotifyPropertyChanged
{
#region INotify Implementation
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
public string _Name;
public string Name {
get { return _Name; }
set { _Name = value; NotifyPropertyChanged("Name"); }
}
public override string ToString()
{
return Name;
}
}
Also, in your ViewModel, when INotifyPropertyChanged is implemented correctly you don't need the below lines. INotifyPropertyChanged will update your UI for you.
Models.CollectionChanged += (s, e) => View.Refresh();
View = new ListCollectionView(Models);
Try not to use the new keyword to define your new instance of listCollectionView. Use the following instead.
var sortedCities = CollectionViewSource.GetDefaultView(Models);

Categories