I want to pull data from a database to display into a ComboBox, and then allow users to select values from that ComboBox and add them into a ListBox (via add/remove buttons). Would I be able to get away with using an ObservableCollections to hold the database values to bind to the ComboBox, since it implements INotifyPropertyChanged (and CollectionChanged)? Sorry if this is a basic question, I starting learning WPF about a month ago.
I've read over the article (very well done) by Sacha Barber.
And I've looked over the MSDN page on ObservableCollection.
What would be the advantages/disadvantages of using an ObservableCollection vs a List (which I know does not implement INotifyPropertyChanged)?
Something you may want to note.
Don't confuse the ObservableCollection's implementation of INotifyPropertyChanged with the objects it contain's implementation.
If one of the properties of one of the objects within the ObservableCollection changes, the UI will not reflect it unless that object implements INotifyPropertyChanged as well. Do not expect the ObservableCollection to take care of this for you.
If the items in your combobox don't change (i.e. you don't add/remove/update items), then List will probably be OK for your needs (ObservableCollection will be too) if you manually notify that your List property changed when you affect it.
public List<X> MyList
{
get
{
...
}
set
{
if (... != value)
{
... = value;
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs("MyList"));
}
}
}
}
....
this.MyList = new List<X> { new X(...), new X(...) };
If you plan to add/remove or update items in your combobox (without creating a new MyList object, i.e. using this.MyList.Add(...)), then use ObservableCollection that is able to notify when the collection is updated (so it can update bindings).
Related
May be it's a silly (or more than trivial) kinda question, but it seems i just don't know the answer. Here's the case -
I assigned a UserList as the ItemsSource of a combobox. So what i did essentially is assigning a reference type to another.
I cleared the UserList. So now i get the Count of the ItemsSource 0 as well.
I still get the items present in my combobox. And i also can cast the SelectedItem of the combobox to a User object.
Here's the complete code -
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
public partial class MainWindow : Window
{
private List<User> _userList;
public MainWindow()
{
InitializeComponent();
_userList = new List<User>()
{
new User() {Id = 1, Name = "X"},
new User() {Id = 2, Name = "Y"},
new User() {Id = 3, Name = "Z"}
};
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.comboBox1.ItemsSource = _userList;
this.comboBox1.DisplayMemberPath = "Name";
}
private void button1_Click(object sender, RoutedEventArgs e)
{
_userList.Clear();
/* ItemsSource is cleared as well*/
IEnumerable userList = this.comboBox1.ItemsSource;
/*I can still get my User*/
User user = this.comboBox1.SelectedItem as User;
}
}
So, where the items are coming from? What actually happens under-the-hood when i make such binding? Does the control have some kind of cache? It's a royal pain to realize not having such basic ideas. Can anybody explain the behind-the-scene detail?
EDIT : I wrote the code in WPF, but i have the same question for WinForms Combobox.
EDIT : Doesn't a combobox display its items from it's in-memory Datasource? When that datasource contains 0 items, how does it display the items?
When you set an ItemsSource of any ItemsControl it copies the ref to the list into its Items property. Then it subscribes to the OnCollectionChanged event, and creates a CollectionView object. So, on the screen you can see that collectionView.
as I have found in source code ItemCollection holds two lists:
internal void SetItemsSource(IEnumerable value)
{
//checks are missed
this._itemsSource = value;
this.SetCollectionView(CollectionViewSource.GetDefaultCollectionView((object) this._itemsSource, this.ModelParent));
}
How could you get SelectedItem?
This is my assumption from quick look into the source code:
ItemsControl has a collection of "views" and each View sholud store a ref to the item (User instance), because it has to draw data on the screen. So, when you call SelectedItem it returns a saved ref.
Upd about references
Assume there is an User instance. It has the adress 123 in memory. There is a list. It stores references. One of them is 123.
When you set an ItemsSource ItemsControl saves a reference to the list, and creates a Views collection. Each view stores a references to an item. One view stores an address 123.
Then you cleared a list of users. Now list doesn't contains any references to Users. But in memory there is an adrress 123 and there is an instance of User by this adress. Garbage Collector doesn't destroy it, because View has a reference to it.
When you get SelectedItem it returns User instance from the 123 adress.
var user = new User();
var list = new List<User>();
list.Add(user);
list.Clear();
Console.WriteLine(list.Count()); //prints 0 - list is empty
Console.WriteLine(user == null); //prints false. - user instance is sill exists;
In answer to your comment to #GazTheDestroyer ("... why it doesn't get cleared, and how it holds the items?")
In WPF, when you set the ItemsSource property of an ItemsControl, the control will wrap the list of items in a CollectionView, which is a collection type optimised for use by the UI framework. This CollectionView is assigned to the Items property of the control and is what the display-drawing code actually works from. As you see, this collection is entirely separate of the object you originally assigned to ItemsSource, and so there is no propogation of changes from one to the other. This is why the items are still in the control when you clear the original list: the control is ignoring the original list, and has its own list that contains your objects.
It's for this reason that an ItemsSource value needs to raise events - specifically INotifyCollectionChanged.NotifyCollectionChanged - so that the control knows to refresh the Items list. ObservableCollection implements this interface and raises the correct event, and so the functionality works as expected.
It's hugely important to note that this is nothing like what happens in WinForms, which is why I've been pressing you for the clarification.
EDIT: To clarify, there is no "deep copy." The code that is happening is similar in principle to the following:
private List<object> myCopy;
public void SetItemsSource(List<object> yourCopy)
{
myCopy = new List<object>();
foreach (var o in yourCopy)
{
myCopy.Add(o);
}
}
Once this code has run, there's only one copy of every item in your list. But each of the items is in both of the lists. If you change, clear or otherwise manipulate yourCopy, myCopy knows nothing about it. You cannot "destroy" any of the objects that are within the list my clearing yourCopy - all you do is release your own reference to them.
Assuming you are using WPF:
List<User> doesn't fire any event that the UI will recognise to refresh itself. If you use ObservableCollection<User> instead, your code will work.
The key difference is that ObservableCollection implements INotifyCollectionChanged, which allows the UI to recognise that the content of the collection has changed, and thus refresh the content of the ComboBox.
(Note that this does not work in WinForms. In WinForms you can set the DataSource property of the control, but the same ObservableCollection trick does not work here.)
When you set a collection reference to ItemsControl, all the combo gets is a reference, that it knows is enumerable.
It will enumerate the reference and display the items. Whether it does a deep copy or shallow copy is irrelevant, all it has is a reference (memory address effectively).
If you change your collection in some way, the combo has no way of knowing unless you tell it somehow. The reference (address) hasn't changed, everything looks the same to the combo. You seem to be thinking that the object is somehow "live" and the combo can watch the memory changing or something? This isn't the case. All it has is a reference that it can enumerate over. The contents can change but without some trigger the combo doesn't know that, and so will sit doing nothing.
ObservableCollection is designed to overcome this. It implements INotifyCollectionChanged that fires events when it changes, so the Combo knows that it must update its display.
In the model of my application I have a list of "parents" each referecing a list of "children" (e.g. a list of footballteams each containing a list of players).
I visualize this list in a tree view. I created a viewmodel-class for the tree and a viewmodel-class for a footballteam (let's call it "footballteamViewModel"). The class for the tree holds an ObservableCollection with footballteamViewModel-items. The items-source for the tree is this ObservableCollection. During initialization, I create for every footballteam in the model a corresponding footballteamViewModel object and add it to the ObservableCollection.
The thing is, that the list of footballteams in my model can be changed from outside of the tree and I want the tree to be updated. So if someone removes a footballteam from my list in the model, I would have to remove the corresponding item in my ObservableCollection of footballteamViewModel-items.
I cannot bind the list of footballteams from the model directly to the view. So I have to update my ObservableCollection in the ViewModel somehow, every time the collection in the model is changed.
My way to handle this is to use an ObservableCollection in the model and register to the collectionChanged-event in the ViewModel, so that I update my ViewModel (the Observable Collection of footballteamViewModel objects) whenever the model-collection is changed. But this does not feel "right". Is there a better way?
While typing this I found another post which describes exactly the same problem: WPF/MVVM: Delegating a domain Model collection to a ViewModel. The answers there encourage me that the way I'm solving this problem is not totally wrong but still I wonder if there is another method.
EDIT: From the first answers that you provided I assume that there is no definite answer to my question. They were all helpful, so it's worth reading them all. I only mark the answer with the reference to the Bindable Linq/Continous Linq/Optics frameworks, because I think it will help other who stumble over my question most.
This is one of the more nasty spots of MVVM.
One thing I have done a while ago is create a ViewModelCollection<T> which inherits ObservableCollection<T> and has modificator methods (Add, Remove), that perform operations on both collections,like so:
public interface IViewModel<T>
{
T WrappedModel { get; }
}
public class ViewModelCollection<T,M> : ObservableCollection<T,M> where T : IViewModel<M>
{
private IList<M> _baseCollection;
public ViewModelCollection(IList<T> baseCollection)
{
_baseCollection = baseCollection;
}
public override void Add(T objectToAdd)
{
IViewModel<M> vm = objectToAdd as IViewModel<M>;
if (vm != null)
{
this.Add(objectToAdd);
_baseCollection.Add(vm.WrappedModel);
}
}
public override void Remove(T objectToRemove)
{
IViewModel<M> vm = objectToRemoveas IViewModel<M>;
if (vm != null)
{
this.Remove(objectToRemove);
_baseCollection.Remove(vm.WrappedModel);
}
}
}
By now I don't do this at all, I just work with Castle Proxies that add the INotifyPropertyChanged functionality to my models - saves a lot of boilerplate code!
Please note, I haven't tested the code, just typed it down from memory.
You said that you cannot bind the model collection directly to the view (which means that the viewmodel needs to make its own ObservableCollection with a copy of what the model collection contains), and additionally that the view needs to be updated live when the model collection changes (which means that the model needs to inform the viewmodel of such changes, and the viewmodel needs to update its internal copy).
All of this doesn't leave much wiggle room really. One variation that might be interesting is making the model's collection a read/write IEnumerable, in which case consumers of the model would be forced to swap it with a new instance whenever they need to modify the collection. In return, the viewmodel's "stay in sync" code can be simplified and sync can be triggered through INotifyPropertyChanged on the collection property.
Your solution is not wrong at all, but there are some libraries that could help you implement it easier, like BindableLinq, ContinuousLinq or Obtics. You have a discusion about them here. Sadly, none of them seem to be under further development.
My personal experience with Clinq is excellent and i still use it, should work for your case.
Late, but may helps other ppl...
read this excellent 3 Part blog post series about this topic.
Part 3 is about collections and shows some solutions - helps me a lot
MVVM: To Wrap or Not to Wrap? How much should the ViewModel wrap the Model?
I'm using Prism v4 , and MVVM.
In my viewmodel i have:
private TB_COMPANY tb;
public TB_COMPANY Tb {
get {
return this.tb;
}
private set {
if ( this.tb != value ) {
this.tb = value;
this.RaisePropertyChanged(() => this.Tb);
}
}
}
In my Page, i have a datagrid (i tried with a listview too, don't work!):
<DataGrid ItemsSource="{Binding Tb.TB_ADDRESS.RL_ADDRESS_PHONE}" .../>
RL_ADDRESS_PHONE is a list of phones of the company...
So, in some moment i add phone to the list:
private void MyCommand()
{
...
Tb.TB_ADDRESS.RL_ADDRESS_PHONE.Add(
new RL_ADDRESS_PHONE
{
TB_PHONE = new TB_PHONE
{
NU_PHONE = _txtTelefone,
ST_TYPE = _txtTipoTelefone
}
});
...
}
But nothing happens to the UI...
But in the Debug, the list is fullfiled....
What should i do to update the UI?
The RL_ADDRESS_PHONE property of TB_ADDRESS must implement INotifyPropertyChanged as well. If it is a List, you should use ObservableCollection which implements INotifyCollectionChanged which is necessary for the behaviour you are trying to achieve.
Also, as a piece of advise - have a look into the design guidelines and naming convention of the C# language, you can find it in the msdn (while it says .net 1.1, it still applies for any following versions of the framework).
Does your binding work? Have you checked the Output window? Are all the properties on your binding path public? Needs more code.
All property notifications aside: If your binding finds the ObservableCollection it will update if you add objects. So i think you might be using a field or a non-public property somewhere on the path.
If this is not the case make sure you do not overwrite the reference to the ObservableCollection unless you have a proper PropertyChanged notification in place. (You should implement that interface (INotifyPropertyChanged) in every class on your binding path since every overwritten reference that the view is not notified of will break your update).
You mentioned the use of a WCF service. Is it possible that your observable collection is being created on a different thread to the UI thread. If this is the case you UI will not be able to bind to it. Bea Stollnitz discusses issues with cross thread binding on her blog here -> http://bea.stollnitz.com/blog/?p=34.
You should be able to get round it by invoking the creation of the observable collection onto the UI thread.
I've built a custom control in WPF that inherits from ListBox. In this I have implementet my own property that is a BindingList. To make this property bindable I've implemeneted it as a DependencyProperty:
public BindingList<CheckableListItem> CheckedItems
{
get
{
return (BindingList<CheckableListItem>)GetValue(MultiComboBox.CheckedItemsProperty);
}
set
{
SetValue(MultiComboBox.CheckedItemsProperty, value);
}
}
public static readonly DependencyProperty CheckedItemsProperty;
I register this DependencyProperty in a static constructor inside my custom control:
CheckedItemsProperty = DependencyProperty.Register("CheckedItems",
typeof(BindingList<CheckableListItem>),
typeof(MultiComboBox),
new FrameworkPropertyMetadata(new BindingList<CheckableListItem>()));
(MultiComboBox is the name of my custom control. CheckableListItem is a simple class I've written just for this purpose).
This BindingList is then updated inside the custom control (never outside) as the user interacts with it.
When I use my custom control in XAML I bind to the CheckItems property with the mode "OneWayToSource". I'm using the MVVM pattern and the property in the ViewModel that I'm binding to is also a BindingList. The ViewModel never affects this list, it just reacts at the changes that the custom control make to the list. The property in the ViewModel looks like this:
private BindingList<CheckableListItem> _selectedItems;
public BindingList<CheckableListItem> SelectedItems
{
get
{
return _selectedItems;
}
set
{
if (value != _selectedItems)
{
if (_selectedItems != null)
{
_selectedItems.ListChanged -= SelectedItemsChanged;
}
_selectedItems = value;
if (_selectedItems != null)
{
_selectedItems.ListChanged += SelectedItemsChanged;
}
OnPropertyChanged("SelectedItems");
}
}
}
As you can see I'm listening to changes made to the list (these changes always occur inside my custom control), and in the "SelectedItemsChanged"-method I update my Model accordingly.
Now...this works great when I have one of these controls in my View. However, if I put two (or more) of them in the same View strange things start to happen. This will of course mean that I'll have two lists with selected items in my ViewModel. But if do something in the View that changes one of the lists, both lists are affected! That is, the event handlers for the event ListChanged is triggered for both list if changes are made to any one of them!
Does anyone recognize this problem and/or have a solution to it? What is wrong with my implementation?
My first though is that the DependencyProperty is static. Normally that means shared between all instances. But I guess DependencyProperties work in some other "magical" way so that might not be the problem.
Any tips or hints is appreciated!
I had a similar problem with a collection-type dependency property. My solution was taken from the MSDN article on Collection-Type Dependency Properties. It was adding the following line
SetValue(OperatorsPropertyKey, new List<ListBoxItem>()); //replace key and type
in the constructor of my control because it seems that a collection-type dependency property constructor is being called only once no matter how many instances your control containing this collection has (static eh).
This sounds like you bound both/all the Views to the same ViewModel. That would explain that changes to one cause changes in the other.
I am looking for a sample code/Article which would demonstrate WPF DataGrid in action with MVVM pattern to add, updated and delete record from database.
I have a specific requirement for allowing user to insert new record using DataGrid not a new child form.
If anyone can recommend good resource or provide a sample for that particular task it would be great help for me.
Here on CodeProject is an article about WPF DataGrid + MVVM pattern:
http://www.codeproject.com/KB/WPF/MVVM_DataGrid.aspx
I don't know of any good articles on the subject, but I don't see the problem; as long as you bind to an ObservableCollection or ListCollectionView containing objects whose class has a default constructor (I don't think there are other restrictions), the DataGrid will handle things pretty well. The collection you bind to must have some way of adding new items, which is why you need to bind to an ICollection or IEditableCollectionView - the latter is preferred, as it has specific options for controlling the creation of items - see AddNew, CanAddNew etc, which the DataGrid works well with.
Edit:Pasted the part, which fits your question.
Full article: http://www.codeproject.com/Articles/30905/WPF-DataGrid-Practical-Examples
This example demonstrates how to use a DataGrid to perform CRUD operations via binding where the database integration is decoupled via a Data Access Layer (DAL).
The Architecture
This example is a simple CRUD application which allows the user to edit items in the Customers table of the Northwind database. The example has a Data Access Layer, which exposes Find/ Delete/Update methods that operate on simple data objects, and a Presentation Layer that adapts these objects in such a way that they can be bound effectively by the WPF Framework. Because we are only performing CRUD functions, I have not added a Business Logic Layer (BLL); if you are a purist, you could add a pass-through BLL; however, I feel it would add little to this example.
The key classes within this architecture are shown below:
The Data Access Layer exposes an interface for managing the lifecycle of the Customer Data Objects. The class which implements this interface uses a typed DataSet as a database integration layer; however, this is hidden from the clients of the DAL. The presence of this layer means that we are not directly coupled to the database schema or the generated dataset schema, i.e., we can change our schema, yet still provide the interface given below to our clients:
public interface ICustomerDataAccessLayer
{
/// Return all the persistent customers
List<CustomerDataObject> GetCustomers();
/// Updates or adds the given customer
void UpdateCustomer(CustomerDataObject customer);
/// Delete the given customer
void DeleteCustomer(CustomerDataObject customer);
}
public class CustomerDataObject
{
public string ID { get; set; }
public string CompanyName { get; set; }
public string ContactName { get; set; }
}
As you can see, there are no UI framework specific interfaces or classes (such as ObservableCollection) exposed by the DAL. The problem here is how to bind the customers returned by ICustomerDataAccess.GetCustomers to our DataGrid and ensure that changes are synchronised with the database.
We could bind the DataGrid directly to our customer collection, List; however, we need to ensure that the UpdateCustomer and DeleteCustomer methods on our DAL interface are invoked at the appropriate points in time. One approach that we might take is to handle the events / commands exposed by the DataGrid in order to determine what action it has just performed or intends to perform on the bound customer collection. However, in doing so, we would be writing integration code that is specific to the DataGrid. What if we wanted to change the UI to present a ListView and a number of TextBoxes (details view)? We would have to re-write this logic. Also, none of the DataGrid events quite fit what we want. There are "Ending" events, but no "Ended" events; therefore, the data visible to event handlers is not in its committed state. A better approach would be if we could adapt our collection of Customer objects in such a way that they could be bound to any suitable WPF UI control, with add/edit/remove operations synchronised with the database via our DAL.
Handling Delete Operations
The ObservableCollection class is a good candidate for our data binding needs. It exposes a CollectionChanged event which is fired whenever items are added or removed from the collection. If we copy our customer data into an ObservableCollection and bind this to the DataGrid, we can handle the CollectionChanged event and perform the required operation on the DAL. The following code snippet shows how the CustomerObjectDataProvider (which is defined as an ObjectDataProvider in the XAML) constructs an ObservableCollection of CustomerUIObjects. These UI objects simply wrap their data object counterparts in order to expose the same properties.
public CustomerObjectDataProvider()
{
dataAccessLayer = new CustomerDataAccessLayer();
}
public CustomerUIObjects GetCustomers()
{
// populate our list of customers from the data access layer
CustomerUIObjects customers = new CustomerUIObjects();
List<CustomerDataObject> customerDataObjects = dataAccessLayer.GetCustomers();
foreach (CustomerDataObject customerDataObject in customerDataObjects)
{
// create a business object from each data object
customers.Add(new CustomerUIObject(customerDataObject));
}
customers.CollectionChanged += new
NotifyCollectionChangedEventHandler(CustomersCollectionChanged);
return customers;
}
void CustomersCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach (object item in e.OldItems)
{
CustomerUIObject customerObject = item as CustomerUIObject;
// use the data access layer to delete the wrapped data object
dataAccessLayer.DeleteCustomer(customerObject.GetDataObject());
}
}
}
When a user deletes a row with the DataGrid control, the CollectionChanged event is fired on the bound collection. In the event handler, we invoke the DAL DeleteCustomer method with the wrapped data object passed as the parameter.
Handling delete operations is relatively straightforward, but how about updates or insertions? You might think that the same approach can be used, the NotifyCollectionChangedEventArgs.Action property does include Add operations; however, this event is not fired when the items within the collection are updated. Furthermore, when a user adds a new item to the DataGrid, the object is initially added to the bound collection in a non-initialized state, so we would only ever see the object with its default property values. What we really need to do is determine when the user finishes editing an item in the grid.
Handling Updates / Inserts
To determine when a user finishes editing a bound item, we need to delve a little deeper into the binding mechanism itself. The DataGrid is able to perform an atomic commit of the row which is currently being edited; this is made possible if the bound items implement the IEditableObject interface which exposes BeginEdit, EndEdit, and CancelEdit methods. Typically, an object implementing this interface would return to its state at the point when the BeginEdit method was called as a response to the CancelEdit method being invoked. However, in this instance, we are not really concerned about being able to cancel edits; all we really need to know is when the user has finished editing a row. This is indicted when the DataGrid invokes EndEdit on our bound item.
In order to notify the CustomerDataObjectProvider that EndEdit has been invoked on one of the objects in the bound collection, the CustomerUIObject implements IEditableObject as follows:
public delegate void ItemEndEditEventHandler(IEditableObject sender);
public event ItemEndEditEventHandler ItemEndEdit;
#region IEditableObject Members
public void BeginEdit() {}
public void CancelEdit() {}
public void EndEdit()
{
if (ItemEndEdit != null)
{
ItemEndEdit(this);
}
}
#endregion
When items are added to the CustomerUIObjects collection, this event is handled for all the items in the collection, with the handler simply forwarding the event:
public class CustomerUIObjects : ObservableCollection<CustomerDataObject>
{
protected override void InsertItem(int index, CustomerUIObject item)
{
base.InsertItem(index, item);
// handle any EndEdit events relating to this item
item.ItemEndEdit += new ItemEndEditEventHandler(ItemEndEditHandler);
}
void ItemEndEditHandler(IEditableObject sender)
{
// simply forward any EndEdit events
if (ItemEndEdit != null)
{
ItemEndEdit(sender);
}
}
public event ItemEndEditEventHandler ItemEndEdit;
}
The CustomerObjectDataProvider can now handle this event to receive the notification of CommitEdit being invoked on any of the bound items. It can then invoke the DAL methods to synchronise the database state:
public CustomerUIObjects GetCustomers()
{
// populate our list of customers from the data access layer
CustomerUIObjects customers = new CustomerUIObjects();
List<CustomerDataObject> customerDataObjects = dataAccessLayer.GetCustomers();
foreach (CustomerDataObject customerDataObject in customerDataObjects)
{
// create a business object from each data object
customers.Add(new CustomerUIObject(customerDataObject));
}
customers.ItemEndEdit += new ItemEndEditEventHandler(CustomersItemEndEdit);
customers.CollectionChanged += new
NotifyCollectionChangedEventHandler(CustomersCollectionChanged);
return customers;
}
void CustomersItemEndEdit(IEditableObject sender)
{
CustomerUIObject customerObject = sender as CustomerUIObject;
// use the data access layer to update the wrapped data object
dataAccessLayer.UpdateCustomer(customerObject.GetDataObject());
}
The above code will handle both insert and update operations.
In conclusion, this method adapts the data items and collection provided by the DAL into UI items and collections which are more appropriate for data binding within the WPF Framework. All database synchronisation logic is performed by handling event from this bound collection; therefore, there is no WPF DataGrid specific code.