I hope my title isnt too misleading, but heres a quick explanation.
As mentioned in the title im using WPF and I set the Itemsources of a Listbox to an ObservableCollection. I also made a DataTemplate for it to show the values correctly. However my problem is that when im changing the values in the ObservableCollection it doesnt show in the listbox.
The question is now, what am I doing wrong?
Heres the code:
public ObservableCollection<Employee> employees;
employees = DatabaseControl.GetEmployees();
Employee_ComboBox.ItemsSource = employees;
Then im switching out the whole Collection:
private void save_Employee_Click(object sender, RoutedEventArgs e)
{
deactivateEmployee();
if (isnewEmployee)
{
DatabaseControl.AddEmployee(employee_firstName.Text, employee_lastName.Text, employee_phoneNumber.Text, employee_city.Text, employee_address.Text);
isnewEmployee = false;
}
if (updateEmployee)
{
DatabaseControl.UpdateEmployee(((Employee)Employee_ComboBox.SelectedItem).ID, employee_firstName.Text, employee_lastName.Text, employee_phoneNumber.Text, employee_city.Text, employee_address.Text);
updateEmployee = false;
}
employees = DatabaseControl.GetEmployees();
Employee_ComboBox.ItemsSource = employees;
}
But this doesnt seem to work out as it should. So what am I doing wrong here?
GetEmpoyees() returns an ObservableCollection btw.
The point of the ObservableCollection<Employee> is that when you bind to it then the UI will react when you add/remove items from it,
but right now you are adding items to another instance.
If you don't want to change your design too much then I would suggest having the DatabaseControl.GetEmployees() return an IList and put the result into the employees ObservableCollection
A simple approach that works well for not too many employees, but may perform poorly if you have many thousands of employees is to clear and add all
IList<Employee> result = DatabaseControl.GetEmployees();
employees.Clear();
foreach (Employee employee in result)
{
employees.Add(employee);
}
A more clean design IMHO would be to instead create an Employee instance outside of your DatabaseControl and then both send that to the DatabaseControl and add it to the employees collection.
Also - you may want to consider using a ViewModel with an ICommand Save and a property ObservableCollection<Employee> {get; private set;} and binding to those from your view.
If the employees collection is the same, then the combobox will not see any change. Therefore first assign null to the items source:
Employee_ComboBox.ItemsSource = null;
Employee_ComboBox.ItemsSource = employees;
Considering you are not using bindings, have you tried:
Employee_ComboBox.InvalidateVisual();
Hope it helps!
Related
Could someone help me, how can I update ObservableCollection, which is binded to ListView ItemSource, without blinking? When I do:
Contacs = _contacs;
the whole ListView is blinking. I would like to search in ListView too, but always after replacing the old results with new one, the listview blinks.
The problem here is, that you are reassigning the whole collection. This does not take advantage of the observability and forces the ListView to reload all items. Try to remove/add the items instead so the ListView only needs to update the Items that actually changed.
In the case of searching hiding the unmatched results might be a viable solution too. To do that create a property on your Contact type (called "IsVisible" for example) and bind it to the ListViewItems Visibility Property. (You might need the build in BooleanToVisibility Converter here)
Update
As pointed out in the comments using a CollectionViewSource is the correct wpf way of implementing a search filter. See this answer for details on how to use it.
If you want to enable filtering in your collection then there is no actual need to perform operations directly on your collection.
Use ICollectionView and CollectionViewSource for this purpose.
As you have an ObservableCollection so you can do something like this.
ICollectionView contactsView;
public ICollectionView ContactsView
{
get { return contactsView; }
set
{
if(contactsView != value)
{
contactsView = value;
}
}
}
And in the setter of the ObservableCollection
public ObservableCollection<ContactType> Contacs
{
get { return _contacs; }
set
{
if(_contacs != value)
{
_contacs = value;
ContactsView = CollectionViewSource.GetDefaultView(value);
ContactsView.Filter = ContactsFilter;
}
}
}
where ContactsFilter is a function with following definition.
bool ContactsFilter(object item)
{
var contact = item as ContactType;
if(condition)
return true; //show this item in ListView.
return false; //Do not show this item in ListView
}
and whenever you want to filter items you can do that just by
ContactsView.Refresh();
which I think will be in the TextChanged Event of your text box in which you are entering search query.
More detailed article is at CollectionViewSource
I have searched around but could not find any references.
How do I delete an item in a generic list that relates to items in a listbox?
I currently have a public static List<Employees> and a listbox named lstRecords, I can remove the item in the listbox just fine, but either everything is removed from the list or nothing at all.
This was my first set of code I was working with:
private void DeleteRecord()
{
if (lstRecords.Items.Count > 0)
{
for (int i = 0; i < lstRecords.Items.Count; i++)
{
if (lstRecords.GetSelected(i) == true)
{
Employees employeeRecord = lstRecords.SelectedItem as Employees;
employee.Remove(employeeRecord);
}
}
lstRecords.Items.Remove(lstRecords.SelectedItem);
}
}
}
This is my 2nd set of code I was working with, I have my List right under partial class, but this is all contained in a method.
private void DeleteRecord()
{
ListBox lstRecords = new ListBox();
List<object> employee = new List<object>();
employee.RemoveAt(lstRecords.SelectedIndex);
lstRecords.Items.RemoveAt(lstRecords.SelectedIndex);
}
So far I haven't gotten either set of code to work the way I would like it to, I'm obviously doing something wrong.
I have a few other blocks of code I played around with but these seemed to be headed in the right direction.
Eventually I'll need to be able to double click an item in the list to pull up the properties menu.
Your code runs fine you just have to make some small changes.
The first code block is Ok however I dont know where your lstRecords are.
But have a look at this just copy the code and run it after you have some records in your employee object.
It's createing a listbox in code then adds it to the form(Winforms) and having the lstRecords globaly.
ListBox lstRecords;
private void IntializeDemoListbox()
{
lstRecords = new ListBox();
this.Controls.Add(lstRecords);
foreach (var item in employee)
{
lstRecords.Items.Add(item);
}
}
And then you will be able to use your first set of code the other set will be like this.
private void DeleteRecord()
{
employee.RemoveAt(lstRecords.SelectedIndex);
lstRecords.Items.RemoveAt(lstRecords.SelectedIndex);
}
What you want to do is bind your ListBox to you List of employees. This post shows the binding and the comments shows the removing code as well. The idea is that when you remove an item from the DataSource, then you won't see it in the ListBox.
Binding Listbox to List<object>
The problem with the DeleteRecord() method is that the lstRecords object you just created isn't the ListBox that is on the form.
I have a datagrid in my wpf application and I have a simple problem. I have a generic list and I want to bind this collection to my datagrid data source every time an object is being added to the collection. and I'm not interested to use observable collection.
the point is I'm using the same method somewhere else and that works fine. but this time when i press Add button an object is added and datagrid updates correctly but from the second item added to collection datagrid does not update anymore.
Here is the Code :
private void btnAddItem_Click(object sender, RoutedEventArgs e)
{
OrderDetailObjects.Add(new OrderDetailObject
{
Price = currentitem.Price.Value,
Quantity = int.Parse(txtQuantity.Text),
Title = currentitem.DisplayName,
TotalPrice = currentitem.Price.Value * int.Parse(txtQuantity.Text)
});
dgOrderDetail.ItemsSource = OrderDetailObjects;
dgOrderDetail.UpdateLayout();
}
any idea ?
The ItemsSource is always the same, a reference to your collection, no change, no update. You could null it out before:
dgOrderDetail.ItemsSource = null;
dgOrderDetail.ItemsSource = OrderDetailObjects;
Alternatively you could also just refresh the Items:
dgOrderDetail.ItemsSource = OrderDetailObjects; //Preferably do this somewhere else, not in the add method.
dgOrderDetail.Items.Refresh();
I do not think you actually want to call UpdateLayout there...
(Refusing to use an ObservableCollection is not quite a good idea)
I also found that just doing
dgOrderDetails.Items.Refresh();
would also accomplish the same behavior.
If you bind the ItemSource to a filtered list with for example Lambda its not updated.
Use ICollectionView to solve this problem (Comment dont work):
//WindowMain.tvTemplateSolutions.ItemsSource = this.Context.Solutions.Local.Where(obj=>obj.IsTemplate); // templates
ICollectionView viewTemplateSolution = CollectionViewSource.GetDefaultView(this.Context.Solutions.Local);
viewTemplateSolution.SortDescriptions.Clear();
viewTemplateSolution.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
viewTemplateSolution.Filter = obj =>
{
Solution solution = (Solution) obj;
return solution.IsTemplate;
};
WindowMain.tvTemplateSolutions.ItemsSource = viewTemplateSolution;
i use ObservableCollection as my items collection and than in the view model
call CollectionViewSource.GetDefaultView(my_collection).Refresh();
I have an ItemsControl displaying a collection of files. Those files are sorted by most recent modification, and there's a lot of them.
So, I want to initially only show a small part (say, only 20 or so) of them, and display a button labelled "Show More" that would reveal everything when clicked.
I already have a solution, but it involves using a good old LINQ Take on my view model's source property. I was wondering if there was a cleaner way.
Thanks.
Why not have the object that you assign to the ItemsSource handle this logic - on first assignment, it would report a limited subset of the items. When Show More is clicked, the object is updated to show more (or all entries) and then notifies the framework that the property has changed (e.g. using the IPropertyNotifyChanged).
public class MyItemSource
{
private List<string> source = { ... };
public MyItemSource()
{
this.ShowThisMany = 20;
}
public int ShowThisMany
{
get;
set; // this should call\use the INotifyPropertyChanged interface
}
public IEnumerable<string> this[]
{
return this.source.Take(this.ShowThisMany);
}
}
...
MyItemsSource myItemsSource = new MyItemsSource();
ItemsControl.Source = myItemsSource;
...
void OnShowMoreClicked(...)
{
myItemsSource.ShowThisMany = 50;
}
In order to do this, you need to create some sort of 'view' on your data. There is nothing within the WPF framwork that will give you this functionality for free. In my opinion, a simple bit of Linq, Take(), is a clean and simple solution.
This little bit of code will help me describe my problem:
public class Car
{
...
}
public class CarQueue : ObservableCollection<Car>
{
public IEnumerable Brands
{
get { return (from Car c in this.Items select c.Brand).Distinct(); }
}
}
Ok now I have an instance of CarQueue class bound to a DataGrid. When I add a Car object to the queue the datagrid updates fine by itself, but I also have a listbox bound to the 'Brands' property which doesn't update. Here is a simple sequence of code to explain:
CarQueue cq = new CarQueue();
DataGrid1.ItemsSource = cq;
ListBox1.ItemsSource = cq.Brands; // all above done during window load
...
Car c;
cq.Add(c); // datagrid updates, but not listbox
Does the listbox not update because it is bound to a property with dynamic LINQ query?
One other thing I tried was inheriting INotifyPropertyChanged and adding a new event handler to the CollectionChanged event (in my CarQueue constructor):
this.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(CarQueue_CollectionChanged);
Then in the event handler:
void CarQueue_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
PropertyChanged(this, new PropertyChangedEventArgs("Brands"));
}
This didn't work either. So does anyone know what the problem is? Thanks
There are a couple of problems here.
The Brands property is a sequence built on the fly by LINQ when it is asked for it. WPF only asks for it during the initial binding: it has no way of knowing that if it were to ask again it would get a different answer, so it doesn't. To get WPF to track changes to the Brands collection, you would need to expose Brands as a collection, and have INotifyCollectionChanged implemented on that collection -- for example by making Brands an ObservableCollection. One way to do this is using Bindable LINQ.
As an alternative, your second approach, of raising a PropertyChanged event for Brands, can be made to work. However, in order for this to work, you have to bind ItemsSource to Brands. (At the moment, you are assigning it, which means WPF forgets where the collection came from and just keeps its private copy of the values.) To do this, either use the {Binding} markup extension in XAML:
<ListBox ItemsSource="{Binding Brands}" /> <!-- assumes DataContext is cq -->
or use BindingOperations.SetBinding:
BindingOperations.SetBinding(ListBox1, ListBox.ItemsSourceProperty,
new Binding("Brands") { Source = cq });