2
votes

So Im developing an addon with a UI that is used inside an application (main application). To make my UI responsive when the main application is working I'm starting my UI in its separate thread like this:

    public  void ShowDialog(IIFCConverter ifcConverter)
    {

        thread = new Thread(x =>
        {
            thread.Name = "UI-thread";
            window = new MainWindow();
            var mainViewModel = ServiceLocator.Current.GetInstance<MainWindowViewModel>();
            mainViewModel.SetIFCConverter(x as IIFCConverter);
            ViewModelLocator.MainWindow = window;
            window.ShowDialog();



        });
        thread.SetApartmentState(ApartmentState.STA);

        thread.Start(ifcConverter);

    }

The first time I start my addon it all works. The second time I start it and it tries to raise events (like OnCollectionChanged) I get a NotSupportedException with the message: "This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread"

This is one of my methods:

    private void AddNewFile(AddNewFileMessage obj)
    {
        if (!(obj.Sender is ButtonViewModel)) return;

        if (string.IsNullOrEmpty(obj.Path)) return;



        var ifcFileViewModel = new IFCFileViewModel(new Common.Model.IFCFile { Path = obj.Path, Active = true });
        DispatcherHelper.CheckBeginInvokeOnUI(() =>
        {
            ListBoxItems.Insert(ListBoxItems.Count - 1, ifcFileViewModel);
        });

    }

I get this eventhough Im using the DispatcherHelper from MVVM light. I have tried using the "normal" dispatcher to, but that gives me the same result.

First of all Im curious to know the mechanics of why its doing this? I've checked my threads and I can see that the OnOllectionChanged is called from my UI-thread. I cant seem to find any differences to the thread structure between the first run (that works) and the following.

OnCollectionChanged called on UI-thread, right?

Second, what can I do about this?

Things that I've tested that didnt help:

  1. Im using the IoC container from MVVM light and its registered as a LocatorProvider, but I create a new IoC container everytime I initialize the UI and set that instance as the LocatorProvider.
  2. Im initializing the DispatcherHelper in the constructor of my window. So that should be on the correct thread.
  3. Something that does actually work is wrapping the action in a try Catch-block like this:

    private void AddNewFile(AddNewFileMessage obj)
    {
    if (!(obj.Sender is ButtonViewModel)) return;
    
    if (string.IsNullOrEmpty(obj.Path)) return;
    
    
    
    var ifcFileViewModel = new IFCFileViewModel(new Common.Model.IFCFile { Path = obj.Path, Active = true });
    
    DispatcherHelper.CheckBeginInvokeOnUI(() =>
    {
        try
        {
            ListBoxItems.Insert(ListBoxItems.Count - 1, ifcFileViewModel);
        }
        catch(Exception ex)
        {
    
        }
    });
    

    }

However I find this very ugly and would like to avoid it and why does that even work? Everything seems to work fine when I wrap all my actions in try Catch-blocks.

2
I assume that ListBoxITems is owned by the default UI thread and not by your spawned STA thread. Maybe CheckBeginInvokeOnUI is fooled by your thread and tries to access the collection on your own thread instead of on its owner. - o_weisman
From what I can see your ViewModel is created by a thread that is not UI. Change that and your solution will work with either of the dispatchers. - XAMlMAX
@XAMlMAX, how do you see that? From the treads window? Or just by the fact that I get the exception? The ViewModel should be created by the IoC container that is created by a resource Dictionary used in the window. I cannot see how that is created on the wrong thread... I cannot see how I can do it in a different way... - Erik83
var mainViewModel = ServiceLocator.Current.GetInstance<MainWindowViewModel>(); is created inside the UI-Thread, that is different than the calling thread, so the object is owed by a different thread, how can you not see that? - XAMlMAX
Then you won't be able to interact with the window from any other thread so this is pretty pointless in your scenario I guess. A window created on thread B can never be accessed from thread A. - mm8

2 Answers

2
votes

Ok, so I found the real problem. The viewmodel was indeed created on the same thread as my addon UI.

HOWEVER: I was using the Messenger that is avaliable in the MVVMLight toolkit to handle communication between viewmodels and I forgot to unregister the viewmodel when my windows was closed. So the second time I opened my window and started sending messages between my viewmodels. The first viewmodel reacted and that was what was causing the problem. That is also why the try-Catch-block worked, because there was a second call from the correct thread that was working. I added a datetime to the constructor of the viewmodel and there are indeed two different viewmodels being called. I do not understand why I get that exception. If anything the first viewmodel should be connected via datacontext to the first view. Its like the view has hooked up to the events of two different viewmodels.

Anyway, it works now: I just unregister the viewmodel from the message service when my window is closed and it works like a charm.