How to get an ItemsSource to refresh its bind? - wpf

I've got a view which shows a listbox that is bound to GetAll():
<DockPanel>
<ListBox ItemsSource="{Binding GetAll}"
ItemTemplate="{StaticResource allCustomersDataTemplate}"
Style="{StaticResource allCustomersListBox}">
</ListBox>
</DockPanel>
GetAll() is an ObservableCollection property in my ViewModel:
public ObservableCollection<Customer> GetAll
{
get
{
return Customer.GetAll();
}
}
which in turn calls a GetAll() model method which reads an XML file to fill the ObservableCollection.:
public static ObservableCollection<Customer> GetAll()
{
ObservableCollection<Customer> customers = new ObservableCollection<Customer>();
XDocument xmlDoc = XDocument.Load(Customer.GetXmlFilePathAndFileName());
var customerObjects = from customer in xmlDoc.Descendants("customer")
select new Customer
{
Id = (int)customer.Element("id"),
FirstName = customer.Element("firstName").Value,
LastName = customer.Element("lastName").Value,
Age = (int)customer.Element("age")
};
foreach (var customerObject in customerObjects)
{
Customer customer = new Customer();
customer.Id = customerObject.Id;
customer.FirstName = customerObject.FirstName;
customer.LastName = customerObject.LastName;
customer.Age = customerObject.Age;
customers.Add(customer);
}
return customers;
}
This all works fine EXCEPT when the user goes to another view, edits the XML file and comes back to this view where the old data is still showing.
How can I tell this view to "refresh its bindings" so that it shows the actual data.
It feels like I am going about WPF here with too much of an HTML/HTTP metaphor, I sense there is a more natural way to get ObservableCollection to update itself, hence its name, but this is the only way I can get the user to be able to edit data in a WPF application at the moment. So help on any level is appreciated here.

An ItemsControl requests its binding once and caches the reference thereafter.
If the content of the collection object are modified, and it implements INotifyCollectionChanged (as ObservableCollection does), it will pick up any added or removed object.
Now, if you want the binding to supply a new collection object to the ListBox, you can have your view-model implement INotifyPropertyChanged and raise PropertyChanged, passing in GetAll as the property name.
This will have the effect of warning the binding that the property value has changed (there is a new ObservableCollection ready to be picked up), which it will supply to the ListBox, which will re-generate its items.
So as long as you effect changes from your app, working on the ObservableCollection returned by GetAll, you can add and remove and the list will stay in synch. When you want to pick up external modifications (you might have a refresh button somewhere, or a natural point where it makes sense to reload the whole file), you can have your view-model raise the PropertyChanged event, which will automatically call the property getter, which will call the static method, which will return a fresh new collection.
Nitpicker note: why do you give method names to properties?

Below line work same as when we remove to add object in collection:
CollectionViewSource.GetDefaultView(CustomObservableCollection).Refresh();

Keep a reference to your ObservableCollection and the XML file's last-modified time as of the time you loaded it. Whenever the window gets focus, check the timestamp on the disk file. If it's changed, clear and re-populate the ObservableCollection. The GUI is automatically listening for change events from the ObservableCollection and will re-populate automatically when you modify the collection's contents.

Related

Setting SelectedItem of Listbox or ComboBox through ViewModel MVVM WPF

Objective: having bound the SelectedItem of a ListBox (or ComboBox) to an instance of an object through xaml, I would like to set the selected instance of the object through the view model and have it reflect on the ListBox or ComboBox.
<ComboBox x:Name="cboServers" HorizontalAlignment="Left" Margin="535,694,0,0" VerticalAlignment="Top" Width="225"
ItemsSource="{Binding Settings.Servers}"
SelectedItem="{Binding Settings.SelectedServer, Mode=TwoWay}"
DisplayMemberPath="UserFriendlyName">
C# Model View code
public ObservableCollection<AutoSyncServer> Servers { get; set; }
private AutoSyncServer _selectedServer;
public AutoSyncServer SelectedServer
{
get { return _selectedServer;}
set
{
_selectedServer = value;
OnPropertyChanged("SelectedServer");
}
}
The list or combo box populates correctly. Selecting an item on the ListBox or ComboBox will correctly set the SelectedServer object.
However, if I try to write a set statement in C# such as:
Servers.Add(newServer);
SelectedServer = newServer;
The ListBox or ComboBox will correctly add the item and the SelectedServer object will be correctly set on the MVVM model, but the front end will not reflect this selection.
In this specific case, an xml file is read saying what the user had selected last, and when the window opens the ComboBox has nothing selected (the servers are all loaded correctly within it though)
What's missing here?
The actual object in SelectedItem must be an object instance which is found in the Servers collection, in the Object.ReferenceEquals(a, b) sense. Not just the same Name and ID (or whatever) properties; the same exact class instance.
The classic case where people run afoul of this is deserializing equivalent items in multiple places. Servers has a collection of deserialized AutoSyncServer instances, and Settings.SelectedServer is a separately deserialized AutoSyncServer instance, which has identical property values to one of the items in Servers. But it's still a different object, and the ComboBox has no way of knowing that you intend otherwise.
You could override AutoSyncServer.Equals() to return true if the two instances of AutoSyncServer are logically equivalent. I don't like doing that because it changes the semantics of the = operator for that class, which has bitten me before. But it's an option.
Another option is to have one canonical static collection of AutoSyncServer and make sure every class gets its instances from that.
I don't understand why this code didn't work, given the above:
Servers.Add(newServer);
SelectedServer = newServer;
Once newServer is in Servers, it should be selectable. I tested that and it's working for me as you would expect.
i think you must avoid "sub-bindings", they work once when the view ask for, but not well after
Settings.SelectedServer ==> SelectedServer
and if you comment OnServerChanged?.Invoke(this, _selectedServer); what is happening ? it works ?

WPF DataGrid automatically updates in-memory data?

I'm using WPF and MVVM pattern to develop a desktop application. Maybe I'm not clear about how a DataGrid control would work, but if I modify an item (text, checkbox, etc.), the modification persists even if I don't make any permanent database update (using Entity Framework). For example, I may switch to view different data, and when I come back to view the grid with modified data (but without saving to db), the change is there. Somehow the in-memory data has been changed by the DataGrid control and is not refreshed or synced with database.
In other words, the data in the DataGrid remained modified until I stop and re-run it from visual studio.
UPDATED:
Another way to ask this question would be: What actually happens when I update, say, an item of a DataGrid? If it is bound to a ViewModel's property P in two-way mode then I suppose P will be updated. But even if I refresh its value (setting the P to null then calling the data access methods again), the modified data are still there.
Does anybody have any idea of what happened?
Thanks!
UPDATED 2:
Here is the xaml code which binds a DataGrid to a property named UserList in the ViewModel.
<DataGrid
x:Name="UserList"
ItemsSource="{Binding UserList, Mode=TwoWay}"
AutoGenerateColumns="False"
AllowDrop="True"
RowBackground="Orange"
AlternatingRowBackground="#FFC4B0B0">
<!-- define columns to view -->
<DataGrid.Columns>
...
</DataGrid.Columns>
</DataGrid>
Here is the code running in the ViewModel. The method InitialiseData() is called in the constructor of the VM and before I want to do something else with persistent data, so I supposed is always refreshed.
private void InitialiseData()
{
// Retrieves user list from the business layer's response
Response userList = _userBL.GetUserList();
if (userList is FailResponse)
{
MessageBox.Show(userList.Message);
return;
}
else
{
UserList = null;
UserList = (IEnumerable<User>)((SuccessResponse)userList).Data;
}
** UPDATED 3 **:
private IEnumerable<User> _userList;
public IEnumerable<User> UserList
{
get
{
return _userList;
}
set
{
_userList = value;
NotifyOfPropertyChange(() => UserList);
}
}
If you switch back, you are switching to in-memory collection which was updated by DataGrid before. Or do you load data from dtb again?
EDIT:
Ok, now as you have posted the code, I know where is the problem.
DataGrid is not refreshed as I thought. Make sure, you will raise NotifyProperyChanged on the property UserList. Then it will work. See ObservableCollection class as well.

Where the combobox bound items are coming from?

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.

Make a Dialog ViewModel binding ready, call Dialog and return data from it in MVVM

Do you see a better way how I can call/contstruct a Dialog from a Controller/ViewModel return data from it and set the DocumentViewModel as DataContext of the Dialog?
The problem is I can not use View first approach in the DocumentDetailWindow and its belonging UserControl because I can not set the Model to the DocumentViewModel`s Document Property in XAML!
How would you solve that scenario? Make Dialog properly bindable, call dialog and return data from it to the LessonPlannerController so the new Document can be saved on database and added to the bound ObservableCollection of Documents so the GUI is refreshed with one more Document.
LessonPlannerController/ViewModel:
private void OnAddDocument()
{
DocumentDetailWindowaddDocumentWindow = new DocumentDetailWindow();
DocumentViewModeldocumentViewModel = new DocumentViewModel();
documentViewModel.Document = new Document();
documentViewModel.Repository = new LessonPlannerRepository();
documentViewModel.SaveDocumentDelegate += new Action<Document>(OnSaveDocument);
addDocumentWindow.DataContext = documentViewModel;
addDocumentWindow.ShowDialog();
}
UPDATE:
I have even thought about not making this => documentViewModel.Document = new Document();
because why do I need a Model in a in a ViewModel when I can just do this:
IN REALITY those properties have a NotifyPropertyChange...
public string DocumentName {get;set;}
public string Keywords {get;set;}
then I could create a Document instance with the above properties in the DocumentViewModel, when the Save command is executed and then pass the Document via Callback to the LessonPlannerControl etc... it seems View first is not working when you have to subscribe your event to a method. Only ViewModel first works then.
What do you think? Should I not use ocumentViewModel.Document = new Document();
and create those 2 properties in the DocumentViewModel. Hm... but why recreate if they are already in the Document Model?...
Do these answer your question?
WPF MVVM dialog example
or
http://www.codeproject.com/KB/architecture/MVVM_Dialogs.aspx

Inserting a new object into L2S table and databinding to it prior to SubmitChanges() in WPF

I'm just getting started with Linq-to-SQL and data binding in WPF, most of which works like a dream so far!
I've got (what I though was) a common scenario:
a) Query list of records from a table via datacontext and bind to the current user control
this.DataContext = db.ClientTypes;
b) Have the user see a bound ListView and some bound detail controls to make changes to the existing records, with a db.SubmitChanges(ConflictMode.FailOnFirstConflict); to push the changes back to the DB. No problem.
c) User wants to add a new record, so we:
ClientType ct = new ClientType();
ct.Description = "<new client type>";
db.ClientTypes.InsertOnSubmit(ct);
However at this point I dont want to call db.SubmitChanges as I want the user to be able to update the properties of the object (and even back out of the operation entirely), but I want them to be able to see the new record in the bound ListView control. Thinking I just needed to re-run the query:
ClientType ct = new ClientType();
ct.Description = "<new client type>";
db.ClientTypes.InsertOnSubmit(ct);
// Rebind the WPF list?
this.DataContext = db.ClientTypes;
listView1.SelectedItem = ct;
listView1.ScrollIntoView(ct);
However this doesn't work, the newly created record is not part of the returned list. I'm not sure if this is because of caching within L2S or if I'm just going about this the wrong way. Is there a better way to accomplish this?
Thanks.
Instead of setting your Control.DataContext = db.ClientTypes, store db.ClientTypes somewhere else and bind to an ObservableCollection that wraps it.
var somewhereElse = db.ClientTypes;
var toBind = new ObservableCollection<ClientType>(somewhereElse);
toBind.CollectionChanged += (object sender, NotifyCollectionChangedEventArgs e) =>
{
if (e.Action == NotifyCollectionChangedAction.Add)
types.InsertAllOnSubmit<AddressType>(e.NewItems.Cast<AddressType>());
};
this.DataContext = toBind;
Then, when the user wants to add a new item:
ObservableCollection<ClientType> toBind = this.DataContext as ObservableCollection<ClientType>;
System.Diagnostics.Debug.Assert(toBind != null);
ClientType ct = new ClientType();
ct.Description = "<new client type>";
toBind.Add((ct);
Calling toBind.Add will cause the CollectionChanged event handler above to call InsertOnSubmit on the original Table instance, so you can call SubmitChanges() when convenient. Obviously, you'd probably want to do the same with Remove ...
Hope that helps :)
It may be worth looking into the MVVM pattern. In MVVM you have a ViewModel which wraps your Model, so you would have a ClientTypeViewModel class.
public class ClientTypeViewModel : INotifyProperyChanged
{
public ClientTypeViewModel(ClientType dataModel)
{
this.dataModel = dataModel;
}
public string Description
{
get { return this.dataModel.Description; }
set
{
this.dataModel.Description = value;
// Raise PropertyChanged event
}
}
private ClientType dataModel;
}
And something like an ApplicationView model, which would contain an ObservableCollection of ClientTypeViewModels.
public ApplicationViewModel
{
public ObservableCollection<ClientTypeViewModel> ClientTypes { get; private set; }
}
You then bind to ApplicationViewModel.ClientTypes instead of the plain data model. This way, your view will be automatically updated whenever a new item is added to ClientTypes, or a property is changed on the ClientType view model. ApplicationViewModel can listen for changes on the ClientTypes collection and automatically add newly added items to the DataContext.
You may think it's overkill for your application, I don't know - but MVVM is definitely somthing worth learning. If it feels like you're struglling or fighting with WPF, MVVM is likely where to look ;)
Look at CreateBindingList.
I think it's just because you're assigning the same reference to the DataContext. Hence, WPF doesn't see the need to refresh the binding. The easiest way around this is to:
// rebind
this.DataContext = null;
this.DataContext = db.ClientTypes;

Resources