WPF ComboBox setting to null after ItemsSource updated - c#

I'm having a struggle with a ComboBox in a WPF app. It's similar to some other questions but the classic solution to this problem don't appear to be working.
Essentially, it's the same problem as this:
WPF ComboBox SelectedItem Set to Null on TabControl Switch
However, my ItemsSource is already in the XAML after the SelectedItem, which is what normally sorts this out.
What is happening is that I have the a view with the combobox on it with data already loaded then an event is fired that updates the data feeding into the ComboBox. The ViewModel consumes the event (fired by a BackgroundWorker that gets the data) and updates its ObservableCollection that is the ItemsSource with the new data. Like this:
int id = (int)Invoice.Customer.DatabaseID;
Customers = new ObservableCollection<Customer>(customers);
Invoice.Customer = Customers.FirstOrDefault(x => x.DatabaseID == id);
As you can see it attempts to set the Customer on the Invoice back to what it was originally. This does occur, observed with a break point, however, as soon as this is completed the Customer gets set back to null from an unidentified source (none of my code appears in the call stack, it's all framework stuff).
The XAML for the ComboBox is this:
<ComboBox DisplayMemberPath="AccountCode"
SelectedItem="{Binding Invoice.Customer, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"
ItemsSource="{Binding Customers}"/>
So to summarise, my ComboBox SelectedItem gets set to null after the ItemsSource is updated and ensuring ItemsSource is after SelectedItem does nothing. I really can't figure out why it's getting set to null and I'm not sure where to look. Any pointers or things I can look at in order to find a solution to this would be greatly appreciated.
EDIT: Ok, I've been playing with it a bit more and I suspect it has something to do with the update coming from a BackgroundWorker. I use a Timer and a BackgroundWorker in my data service to periodically update the customer list from the database to ensure the data is relatively current. The BackgroundWorker fires an event when it is finished to inform interested objects that the list has been updated. This appears to mean that when the events are consumed they are in a different thread. When it updates this way the SelectedItem gets set to null after I set it to the correct item and therefore sets Invoice.Customer to null. I quickly added a button to my view to update the customers without using the BackgroundWorker and this appears to work every time. I'd like to keep updating the data periodically but I need to figure this out before I can do so.

Ok, as I suspected in my edit this was to do with threading in some fashion. Updating the ComboBox ItemsSource from after the update was kicked off by a Timer was causing it to get set to null after it had been updated to the correct Customer. I confirmed this behaviour in a fresh app that didn't have all the other bits in it and therefore potentially me setting it to null somewhere I wasn't meant to (even though the call stack seemed to heavily suggest that it wasn't me doing it). When the event fired to update the results I got what looked like exactly the same call stack in the fresh app compared to the real app.
After a bit of playing around with different things the method I've come upon (in my fresh app - not deployed it to the real one yet fingers crossed it works there too!) was to have the event fired by a competed update run through a TaskFactory (idea from here Is it wrong to use the Dispatcher within my ViewModel?).
In the ViewModel declare a TaskFactory:
TaskFactory uiFactory;
In your constructor set it up like this:
uiFactory = new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext());
Then in the event that runs after the data has been updated do something like this:
private void AsyncMethods_TaskCompleted(object sender, EventArgs e)
{
uiFactory.StartNew( () => UpdateResults());
}
And UpdateResults in this context is the same as updating the Customer on the Invoice. It gets the old id, sets the ItemsSource to the new collection and then sets the property bound to SelectedItem to the equivalent item in the new collection. This appears to be working and not giving me the odd behavious I was having before. I'm going to deploy it to the actual app and hope it works there too. If it does I'll come back and accept this answer.

Sometimes when you create a 'new' instance of an Object, it can break your bindings. You can update the existing Collection without calling 'new' OR you can make the ObservableCollection a dependency property.

The issue is caused by these 2 lines.
Customers = new ObservableCollection<Customer>(customers);
Invoice.Customer = Customers.FirstOrDefault(x => x.DatabaseID == id);
Your combobox source is Customers and you're initializing it again. Then you're trying to fetch data from a newly initialized member. A newly initialized member will have no data in it.
ie Customers has no data in it. Hence Invoice.Customer will probably be null.
I don't understand why you initialize it and simply try to get data from it. Did you skip to fill the source?
If you missed to fill the source, then fill the source with data first. Then you can run this code without initializing it again so that Invoice.Customer is not null.
Invoice.Customer = Customers.FirstOrDefault(x => x.DatabaseID == id);

Related

WPF - MVVM - C# Cannot set SelectedItem of combobox after ItemsSource changes

I've been struggling with this problem for several days now.
I have a combobox that gets its ItemsSource twice.
The first time i try to see if i have an offline backup of the data that i am trying to show.
While the offline data is showing, the software needs to download the updated data from the database.
Everything works fine if i don't have any stored data in the offline backup.
The problem arises when i have a list of items backed up, i put it in the combobox itemsSource, and then change that list after a couple seconds.
The list actually shows correctly, but the selectedItem (which is something that i get from the viewmodel) gets reset and i can't set it again from code-behind.
The SelectedItem property in the viewModel is actually properly set, but it doesn't get to the UI.
The list is an ObservableCollection of the same type as the selectedItem.
My combobox
<ComboBox ItemsSource="{Binding SomeList, Mode=OneWay}"
SelectedItem="{Binding ListItem, Mode=TwoWay}"
DisplayMemberPath="ItemProperty"
Margin="10,0,0,0" Width="300" VerticalAlignment="Center"/>
The way i set the selectedItem in both the offline and online methods:
SomeList= await _mainRepository.BackEndOfflineFirst_GetList();
if (SomeList.Count > 0)
{
ListItem = SomeList.SingleOrDefault(list=> list.Property.Equals(SomeVariable.Property));
}
Any help is appreciated.
I should mention that adding IsSynchronizedWithCurrentItem="True" to the combobox always makes the selectedItem the first item of the list.
The check for list.property equals another variable.property ALWAYS returns one single element that is guaranteed to be in the list, and is never null.
Forgot to mention (kinda important, my bad), but i'm using Prism and i implement BindableBase, so the properties are defined as follows:
private ObservableCollection<Type> _someList ;
public ObservableCollection<Type> SomeList
{
get { return _someList ; }
set { SetProperty(ref _someList , value); }
}
It is impossible to infer the cause of the problem from your explanations.
Everything is correct in the code examples - the problem is somewhere else.
Just in case, I ask you to clarify: do you raise PropertyChanged in all properties?
One tip (including as a probable cause), use the ReadOnly property for the observable collection:
public ObservableCollection<...> SomeList {get;}
= new ObservableCollection<...>();
SomeList.Clear();
foreach(var item in await _mainRepository.BackEndOfflineFirst_GetList())
SomeList.Add(item);
Found out why it wasn't working.
BindableBase propertyChanged only gets raised when the value actually changes.
I was getting basically the same object (different objects, same value), so it wasn't actually raising the propertyChanged.
To fix this, i put a
ListItem = null;
SomeList.Clear();
inside a finally clause, before actually giving the value i receive from the DB to the ListItem.
It also does not seem to work if i change properties too quickly (hence the finally clause), so i couldn't just put a
SomeList.Clear();
SomeList = await...
back to back.
Another problem i found was that SomeList.Clear() actually throws a NotSupportedException that wasn't actually showing up, hence skipping some of the code.
This was caused by SomeList being an ObservableCollection. Changed it to a generic List and i can Clear it now.
If you need to clear the ObservableCollection, a simple Collection = null; works too.
Thank you to everyone that helped me.

UI is not responsive when datagridview loading data in c#

I have a listbox, datagridview and textbox in winforms.
Here is what I am trying to do
When the user selects an item in listbox, datagridview is loaded with some data regarding to the item selected and also there is a autocomplete textbox with a custom source which changes dynamically based on the item selected.
datasource for datagridview and autocomplete textbox is from the rdbms (MYSQL).
private void listBox1_SelectedValueChanged(object sender, EventArgs e)
{
string item=listBox1.SelectedValue.Tostring();
UpdateDataGridView(item);
UpdateACTextBox(item);
}
Until this operation is completed, user cannot able to select another item in listbox, it takes almost 10 seconds to finish.
To resolve this, I came across some solutions like asynchronous method.
Which is the best way to resolve this problem?
Backgroundworker,
async and await,
threadpool,
multithreading.
Please suggest something if I have not researched properly.
What is the conventional way developers follow to these types of problems?
UPDATE:
I forgot to mention that I am using VS2010. Since async and await is not supported in vs2010. I think I need to drop that option.
Thanks in advance
It seems to me that the problem is that the amount of data you are loading to the listView is to big. So i would suggest using a background task in combination with the VirtualMode of the listView. In virtual mode the datas are not stored inside of the listView you have to store the values outside in a list of your own. If needed the listView will call a event to ask you for the value. Simply return a dummy or use old values if the data is not available. This will let the listview simply blanke or showing the old values. After the values are finished copied in a list simply exchange the pointer (variable) of the list with a atomic operation. From that point on the new datas are used to update the list and not the old one. Call update function to load the new valuses. This methode allowes it although to update the data in the background and update it from time to time. Only drawback you need to store the values of your list twice. Inside of the old shown buffer and inside of the new currently updated one. I hope that helps.

ComboBox Binding Breaks on Changes to object in SelectedItem

I have a ComboBox that has an ItemSource bound to a an ObservableCollection and its SelectedItem property is bound to a property of T. Both properties live on my view model. Everything works great except for one issue that is causing me to tear my hair out. When I set a property on the object bound to the SelectedItem, the ComboBox binding stops working. VS 2012 output window does not report any binding errors and no exceptions are thrown.
XAML
<ComboBox ItemSource="{Binding Path=Connections, Mode=OneWay}"
SelectedItem="{Binding Path=SelectedConnection, Mode=TwoWay}"
DisplayMemberPath="Name"/>
<TextBlock Text="{Binding Path=ConnectionName, Mode=TwoWay}" />
<TextBlock Text="{Binding Path=ConnectionGroup, Mode=TwoWay}" />
View Model
private void SaveChanges()
{
this.SelectedConnection.Name = this.ConnectionName;
this.SelectedConnection.Group = this.ConnectionGroup;
// -- Save the changes
}
Now all of that works just fine. But as soon as SaveChanges() completes the ComboBox starts acting funny. It looks like everything is fine, but if you attempt to change the selection nothing happens; the same item is selected and the setter for "SelectedConnection" does not fire. I tried systematically commenting out code in SaveChanges() and if I leave only a single line that is setting any property on SelectedConnection I see this failure happen.
I also added an event handler in the code behind of the control to handle the SelectionChanged event on the ComboBox. After the break happens, any time I try to change the value via the UI or in Code the event handler fires twice in immediate succession. The first has the event args' AddedItems set with a single value which is the value I just selected and would expect. However the event fires again immediately with only a single item in the RemovedItems collection of the event args. That collection also contains the item I just selected.
I have tried setting the SelectedConnection property to null and then after the method finishes, setting back to the object in question. No luck.
I also saw an old bug post about moving the ItemSource property after the SelectedItem property on my XAML mark up but that had no effect either.
I'm trying to avoid creating a shadow list and trying to keep everything in sync as that was going to be my next try but that is just getting ugly to do something so simple. Any ideas would be greatly appreciated.
If it matters, this is Visual Studio 2012 building to the 4.5 framework.
EDIT
I've now also tried just completely removing the modified object from the ObservableCollection after setting its properties. It still shows as if it was the selected value (its name is immediately visible in the ComboBox) but it no longer shows up in the drop down list. It also will still not allow itself to be "unselected" even though it doesn't exist in ItemSource any more.
** UPDATE **
I changed the method in question to completely null out the ObservableCollection that is bound to the ItemSource and then restore it once the process is over with like so:
private void SaveChanges()
{
var selected = this.SelectedConnection;
var name = this.ConnectionName; // -- bound to fields on the view
var group = this.ConnectionGroup;
this.Connections = null;
this.RaisePropertyChanged("Connections"); // -- SelectionChanged fires as expected
selected.Name = name;
selected.Group = group;
this._repository.SaveChanges();
this.Connections = new ObservableCollection<Connection>(this._repository.AllConnections);
this.RaisePropertyChanged("Connections"); // -- SelectionChanged event doesn't fire
this.SelectedConnection = selected; // -- SelectionChanged event doesn't fire
}
After that is done the ComboBox once again allows me to change the value as you would expect. However the selected item does not actually appear as selected (even though it is correctly set in the View Model). So yeah, it's a really ugly workaround and it creates its own issue as well.
Have just had this same issue, and I think I tracked down what was doing on.
It only appeared after I made some recent changes (suggested by the code analysis) to add equals/hashcode/operator overloads for a class that I had as IComparable, and was used in an ObservableCollection for a combobox. I made the hashcode based on a descriptive field of the object. However that field could be user-modified, and after the user modified the value, the combobox broke, being unable to unselect that item anymore.
Given that there are no unchanging fields in this object, there's no way to create a fixed hash code based on the object's properties. However if I remove the hashcode override entirely (or set it to something fixed, like type.GetHashCode), everything works again.
So, check to see if your hashcode is based on whatever value you were changing.
I gave up on doing it the way I had originally envisioned. Instead of directly modifying the object in the ItemSource collection, I made the ItemSource collection to be a list of Key/Value pairs and extracted the object I needed from the source list based on the key of the selected pair. I was able to edit that object without issue (as expected). It works now, I just added a layer of code in there that I was hoping to avoid. The UI also allowed me to update the Value side of the pair to reflect changes to the underlying model's "Name" field without giving me the same issues it was previously. I'll just count it as a small win and move on.

DataGrid won't display data after refresh if ObservableCollection is null at startup

Hopefully I can make this clear. I have a DataGrid
<DataGrid Grid.Row="6" Grid.Column="1"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
ItemsSource="{Binding projectEntriesForEmployee}">
bound to an
public ObservableCollection<ProjectEntry> projectEntriesForEmployee {
get { return (ObservableCollection<ProjectEntry>)GetValue(projectEntriesForEmployeeProperty); }
set { SetValue(projectEntriesForEmployeeProperty, value); }
}
public DependencyProperty projectEntriesForEmployeeProperty = DependencyProperty.Register("projectEntriesForEmployee", typeof(ObservableCollection<ProjectEntry>), typeof(MainWindowVC));
If I set projectEntriesForEmployee before I load my UserControl (which I did to debug), my rows show properly in the DataGrid. If however, projectEntriesForEmployee is null when the UserControl loads, when I set projectEntriesForEmployee to a valid ObservableCollection with items in the list (based on an event), no rows show on the DataGrid. What could be going on?
Edit:
I've tried
CollectionViewSource.GetDefaultView(projectEntriesForEmployee).Refresh();
but no joy.
I'm not sure what's going on, but I would try using Snoop to drill into the DataGrid and make sure that, after you update projectEntriesForEmployeeProperty the DataContext of the DataGrid is still set to the appropriate object and to verify the binding on ItemsSource. If there are any binding errors, Snoop will show them and it'll also let you drill into the object and see the full binding expression. Just a suggestion.
When your control loads, both your OC, and your DataGrid's ItemsSource both point to the same thing: a null piece of memory.
Some time later, your initialize your OC to a collection. Now your OC holds a collection, but your DataGrid's ItemSource still points to the same null piece of memory.
Can you not just initialize your OC in your control's constructor?
EDIT
I'm not a WPF guru, so there might be a reason to do this, but why are you setting your ObservableCollection as a dependency property? If all your doing is binding it to your DataGrid's ItemsSource, a regular, vanilla C# property will work fine, and still provide you all of the automatic updates that occur when you add to, or remove from the collection.
Sorry everyone. I've answered my own question, as it turns out that the problem was the Dependency Property Owner Type. I had it as MainWindowVC. It should have been TimeEntryVC. As soon as I changed that, I commented out the resetting the ItemsSource in the Controller class, and everything worked as it should. So #Tim, you're right that the Binding system takes care of things; I just told it to look for the property in the wrong class. Thanks all regardless, as I understand better what's going on behind the scenes now than I did before.

WPF DataGrid: handling CanUserAddRows=true cleanly with MVVM

I've been looking into MVVM lately, and after I discovered Caliburn.Micro things have been pretty swell; I'm still in the early learning stages, but I belive I have an OK feel for the MVVM basics.
I'm bumping into issues with WPF DataGrid, though - and it's pretty much the same issues that I had with WinForms DataGridView: how the heck do you handle CanUserAddRows=true in-grid item adding cleanly?
I obviously don't want to add DataGrid-specific hacks to my ViewModel, since it ideally should be repurposable for other View controls. At the same time, I'd like to be able to get a notification when a new row item has been added, so I can persist it right away.
I'm binding the DataGrid to a BindableCollection<FooModel> FooItems - with a clean MVVM design, if I understand things correctly, I would be able to handle FooItems.CollectionChanged and react to Add/Remove events. However, the DataGrid fires the Add event as soon as a default-constructed item is added - this is obviously not the right time to persist the object!
After a lot of googling and digging through StackOverflow, I'm getting the impression that DataGrid is being utterly retarded in how it's firing the Add/Remove events. People who use it with CanUserAddRows=true seem to only work on in-memory collections, where people who persist data seem to use separate input fields + buttons Commands to add new items.
My FooModel implements INotifyPropertyChanged but not IEditableObject - as far as I can tell that shouldn't be the problem, though, since IEO seems related to property edit/undo, whereas my problem is with when the Add event is fired...
So, what do you do to handle in-grid editing cleanly?
It sounds like the WPF DataGrid behaves in much the same way as the WinForms DataGridView, in that it creates an item in the data source as soon as the user begins entering into the 'new row'. Subsequent edits would result in changes to the properties of the new item in the collection.
If you were to use BindingList<FooModel> instead, you get the additional event called ListChanged - providing your FooModel type implements INotifyPropertyChanged, the event will fire when the properties of the item are changed (as well as when items are added/removed from the collection).
Hope this helps!
I know it's been a long time since this was asked but I'm working on something similar now and thought I'd post my solution. You may consider this a hack but it's the best way I could figure to get DataGrid to tell me when it was out of edit mode (for my own reasons).
I looked through the DataGrid code and found the closest thing to end edit I could override, which turns out to be OnExecutedCommitEdit(). I then just raise an event after that function finishes. When my event subscriber gets called DataGrid is no longer in edit mode and I can do whatever I need to it. Here's the code:
/// <summary>
/// Deriving a new DataGrid because we need an event to tell us when editing is complete.
/// </summary>
public class DataGridMod : DataGrid
{
public delegate void EditCompletedDelegate();
public EditCompletedDelegate EditCompleted;
public DataGridMod() { }
protected override void OnExecutedCommitEdit(ExecutedRoutedEventArgs e)
{
base.OnExecutedCommitEdit(e);
if (EditCompleted != null)
EditCompleted();
}
}

Categories