views:

97

answers:

3

Hi all.

I've seen other issues similar to mine, but i haven't seen any that i can apply to make my code work.

So i'm new to MVVM, and i'm trying to get some stuff that's executing in a background thread to update on my UI. What i'm noticing is that the first time bring up the UI, and the background thread executes the first time, if the collection is IEnumerable<> the UI isn't fully updated against backing data. If the collection is ObservableCollection<>, it throws an error.

From what i've read, changes to collections need to be executed on the dispatcher thread, but OnPropertyChanged() calls do not. So someone, please tell me how this could be happening:

I'm altering my _Printers observable collection:

foreach (PrinterViewModel pv in _Printers)
            {
                DispatcherExec(() =>
                {
                var abilities = from x in _ServerData.Types
                                select new PrinterAbility(
                                    new PrintableType() { ID = x.ID, Name = x.Name, NumInProcUnit = x.NumInProcUnit, PrintersMappedTo = x.PrintersMappedTo, SysName = x.SysName },
                                    x.PrintersMappedTo.Contains(pv.Printer.ID)
                                    );


                    pv.Printer.SetAbilities(abilities);
                });

My DispatcherExec looks like so:

 private void DispatcherExec(Action action)
    {
        //Dispatcher.Invoke((Action)delegate 
        //{
        //    action.BeginInvoke(null, null); 
        //}, null);
        Dispatcher.CurrentDispatcher.Invoke((Action)delegate
        {
            action.Invoke();
        }, null);
    }

And here's the SetAbilities code that fails:

 public void SetAbilities(IEnumerable<PrinterAbility> abilities)
    {
        if (log.IsInfoEnabled)
            log.Info("SetAbilities(IEnumerable<PrinterAbility> abilities): called on printer "+Name);

        List<PrinterAbility> l = new List<PrinterAbility>();
        abilities.ForEach(i =>
            {
                i.PrinterAbilityChanged += new PrinterAbilityChangedEventHandler(OnPrinterAbilityChanged);
                l.Add(i);
            }
            );
        lock (_Abilities)
        {
            foreach (PrinterAbility pa in l)
                _Abilities.Add(pa);
        }
        if (log.IsDebugEnabled)
            log.Debug("SetAbilities(IEnumerable<PrinterAbility> abilities): leaving");
    }

On the _Abilities.Add(pa) observable collection add it says "This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread." I'm thinking, "are you joking?"

Further, I would think that a change to an object in the observable collection would automatically make it call OnCollectionChanged(), is that right?

Thanks in advance everyone.

A: 

May be this and this will be helpfull with changing Observable Collection through threads.

Eugene Cheverda
A: 

You should use the Dispatcher associated with any of your WPF controls, not the Dispatcher.CurrentDispatcher for the background thread.

Also

Dispatcher.CurrentDispatcher.Invoke((Action)delegate
        {
            action.Invoke();
        }, null);

is redundant, it should be

wpfDispatcher.Invoke(action, null);

And finally for the first block, you should usually avoid passing loop variables to the lambdas, use temporary assignment trick to get around these sneaky closure problems. Almost certain that it's not the problem in this case though.

Grozz
If i'm using the Dispatcher thats attached to one of the controls then aren't i tightly coupling the VM to the V?
Micah
That's the only way. You should use dispatcher associated with the WPF thread. You can consolidate the logic using it in the view or pass it as a parameter for the VM classes/methods you are calling.
Grozz
+2  A: 

Using Dispatcher.CurrentDispatcher is not something you should do from a BG thread. You need to use the Dispatcher for a DependencyObject-derived object that has been created on the UI thread.

Also, you're iterating over *ViewModel objects (PrinterViewModel) from within a BG thread. This really goes against MVVM. Your model should be doing asynchronous stuff, and your ViewModel(s) should be handling those asynchronous operations in a way that the view can consume (by marshalling to the proper thread via the Dispatcher).

Also, you're closing over a loop variable (pv). Bad, bad. This (depending on the order of execution) could mean that by the time the dispatcher comes around, you'll get multiple pv.Printer.SetAbilities(...) calls on the same PrinterViewModel instance. Create a local variable inside the loop and use that within your anonymous method to avoid this problem.

FMM
Thank you for your criticism.
Micah
What about Application.Current.Dispatcher?
Micah
Well, instead of putting my datasource in the xaml, i put it in the xaml.cs and had passed a dispatcher from one of the controls to my VM. That worked. Thanks again.
Micah