0

I have created a WPF binding it was working fine but then I realised I needed a refresh button show when I call the SetUpData function I am getting this error:

Additional information: This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.

C#:

public async void GetShows(string serviceDocUri)
{

    await Task.Run(() => _Shows = new ObservableCollection<Show>());

    await Task.Run(() => GetAllShowsWithPath("", directoryShowDoc)); //Error shows on this line
}

More C#:

public MainWindow()
{
    InitializeComponent();
    this.DataContext = Workspace.This;

    //Awaits the list of shows
    SetUpData();
}

More C#:

private async void SetUpData()
{
    await Task.Run(() => _VizManager.GetShows(_TrioLocalHost));

    //This is binded to my WPF combobox
    Workspace.This.TrioShow = _VManager._Shows;

}

More C#:

private async Task GetAllShowsWithPath(string directoryPath, XmlDocument xmlDoc)
{
    string siteRoot = "http://";
    foreach (XmlNode showNode in xmlDoc.SelectNodes("//atom:feed/atom:entry[atom:category/@term='show']/atom:title", nameSpaceManager_))
    {
        _Shows.Add(new Show { Title = "showTitle", ShowLink = "showLink", FullPath = "fullPath" });
    }

    foreach (XmlNode dirNode in xmlDoc.SelectNodes("//atom:feed/atom:entry[atom:category/@term='directory' and not(atom:category/@term='show')]/atom:title", nameSpaceManager_))
    {
        string dir = dirNode.InnerText;
        XmlDocument xmlDoc1 = new XmlDocument();
        string directoryPath1 = directoryPath + "/" + dir;
        string xml = "";
        string page = siteRoot + "/directory/shows" + directoryPath1 + "/";
        using (HttpClient client = new HttpClient())
        using (HttpResponseMessage response = await client.GetAsync(page))
        {
            response.Headers.Add("Accepts", "application/atomsvc+xml");
            using (HttpContent content = response.Content)
            {
                string data = await content.ReadAsStringAsync();
                if (data != null)
                {
                    xml = data;
                }
            }
        }
        xmlDoc1.LoadXml(xml);
        await this.GetAllShowsWithPath(directoryPath1, xmlDoc1);
    }
}
Dev
  • 1,780
  • 3
  • 18
  • 46
  • 2
    Why don't your methods return any values? What are they doing? GetAllShowsWithPath should certainly return all "shows with path". – mm8 Jun 06 '17 at 11:17
  • Instead of returning the shows I add them directly to my list within "shows with path" function. – Dev Jun 06 '17 at 11:24
  • 3
    Well, then you should rename the methods. This code should never pass a code review :). Please show what the methods do exactly if you want someone to be able to point what else you are doing wrong. – mm8 Jun 06 '17 at 11:28
  • What is the type of _Show? – adPartage Jun 06 '17 at 11:30
  • My apologies I will edit the post so I have the full structure. and the _Shows is ObservableColection – Dev Jun 06 '17 at 11:32

2 Answers2

3

Your code is a mess, to be honest. You don't call a method Get* if it doesn't return something, to begin with. And why are you creating and awaiting several tasks inside other tasks?

Anyway, the ObservableCollection<Show> should (must) be created on the UI thread. Create it the first thing you do in the constructor of the window or view model or wherever it's defined:

public MainWindow()
{
    InitializeComponent();
    _Shows = new ObservableCollection<Show>();
    this.DataContext = Workspace.This;

    //Awaits the list of shows
    SetUpData();
}

Your GetAllShowsWithPath method should then return an IEnumerable<Show> that you populate the data-bound ObservableCollection with back on the UI thread. You could change the return type of the method to Task<List<Show>> and return a list of objects instead of trying to access the ObservableCollection from within the method.

The other option would be to use the BindingOperations.EnableCollectionSynchronization method to enable the collection to be accessed across multiple threads:

private readonly object _lock = new object();
public MainWindow()
{
    InitializeComponent();
    _Shows = new ObservableCollection<Show>();
    BindingOperations.EnableCollectionSynchronization(_Shows, _lock);

    this.DataContext = Workspace.This;

    //Awaits the list of shows
    SetUpData();
}

You must still create the collection and call this method on the UI thread though.

Dev
  • 1,780
  • 3
  • 18
  • 46
mm8
  • 163,881
  • 10
  • 57
  • 88
  • Thanks, Also for the feedback on the coding standards. Have you got any recommendation for good coding standards or does that come with time? I am always looking to move forward and learn new things. – Dev Jun 06 '17 at 12:02
  • You could refer to the C# programming guide: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/inside-a-program/coding-conventions. Thanks for the edits by the way :) – mm8 Jun 06 '17 at 12:03
0

Actually, you are trying to access an UI element from another thread probably here:

//..Function body
//Recurring function

UI element created on a thread is accessible only to that thread, and is not available to other threads

Take a look at Dispatcher

[Edit]:

public async Task GetShows(string serviceDocUri)
{
    _Shows = new ObservableCollection<Show>();
    await Task.Run(
        () =>
            Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,
                new Action(() => GetAllShowsWithPath("", directoryShowDoc))));
} 


private async Task SetUpData()
{
    await _VizManager.GetShows(_TrioLocalHost);

    //This is binded to my WPF combobox
    Workspace.This.TrioShow = _VManager._Shows;
}
adPartage
  • 829
  • 7
  • 12