1
votes

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.

3
I updated my ItemsSource and my SelectedItem remains where it should be. Cant reproduce your issue. Show definition of Invoice.Customer property.AnjumSKhan

3 Answers

0
votes

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.

-1
votes

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.

-1
votes

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);