Search code examples
c#wpfdevexpressbackgroundworkerxamlparseexception

Modifiing ItemsSource within BackgroundWorker Causes an Exception


I have a ListBox with an ObservableCollection as ItemsSource in my application. Also I have serveral classes that provides data for this ItemsSource.

    public ObservableCollection<Notification> NotificationItems { get; set; }
    private object _stocksLock = new object();

I create the collection within my constructor like that

this.NotificationItems = new ObservableCollection<Notification>();
System.Windows.Data.BindingOperations.EnableCollectionSynchronization(
       this.NotificationItems, _stocksLock);

I am loading the modules providing data for the ListBox from serveral dll assemblies. The method to get Notification data for the collection is called within a BackgroundWorker

BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += GetPluginModuleNotifications;
List<IModul> Modules = new List<IModul>();
//[...]
foreach (IModul PluginModul in AssemblyList)
{
     //[...]
     Modules.Add(PluginModul);
     //[...]
}
this.Notifications.ItemsSource = this.NotificationItems;

object[] Parameter = new object[] { Modules, this.ComponentFactory, 
      this.MyListBox};

//-->Edited
worker.WorkerReportsProgress = true;
worker.ProgressChanged += OnProgressChanged;
//<--Edited

worker.RunWorkerAsync(Parameter);
//...

//-->EDITED
private void OnProgressChanged(object sender, ProgressChangedEventArgs e)
{
    Notification toAdd = (Notification)e.UserState;
    this.NotificationItems.Add(toAdd);
}
//<--EDITED

I want each of the IModul items to provide Items for the ListBox. This part works fine at all so the data I want to receive is loaded. Here is my BackgroundWorker.DoWork Event

    private void GetPluginModuleNotifications(object sender, DoWorkEventArgs e)
    {
        object[] args = e.Argument as object[];
        if (args == null || args.Length != 3) return;
        List<IModul> Module = args[0] as List<IModul>;
        IComponentFactory Factory = args[1] as IComponentFactory;
        // DXListBox lb = args[2] as DXListBox;
        if (Module == null || Factory == null) return;

        foreach (IModul Modul in Module)
        {
            Notification[] res = Modul.GetImportantNotifications(Factory);
            if (res == null || res.Length == 0) continue;

            foreach (Notification notif in res)
            {
                //-->EDITED
                (sender as BackgroundWorker).ReportProgress(1, notif);
                System.Threading.Thread.Sleep(100);
                //this.ReceiveNotification(notif);
                //<--EDITED
            }
        }

    }

    private void ReceiveNotification(Notification obj)
    {
        if (obj == null) return;

        Dispatcher.BeginInvoke(new Action(() =>
        {
            this.NotificationItems.Add(obj);
            if (this.NotificationPanel.Width.Value == 0)
            this.NotificationPanel.Width = new GridLength(NOTIFICATION_BAR_WIDTH);
         }));
    }

The XAML for the NotificationPanel looks like this:

 .<dx:DXListBox x:Name="Notifications" VerticalAlignment="Stretch" BorderBrush="Transparent" MouseDoubleClick="NotificationGotoSource" ItemsSource="{Binding NotificationItems}">
    <dx:DXListBox.ItemTemplate>
    <DataTemplate DataType="{x:Type cbcore:Notification}">
        <StackPanel Orientation="Horizontal">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="38" />
                    <ColumnDefinition Width="*" MinWidth="157"/>
                    <ColumnDefinition Width="15" />
                </Grid.ColumnDefinitions>
                <Image Source="{Binding ImageSource}" Grid.Column="0" Width="32" Height="32" VerticalAlignment="Top" HorizontalAlignment="Left" />
                <StackPanel Orientation="Vertical" Grid.Column="1">
                    <Label FontWeight="Bold" FontSize="10" MaxHeight="25" MaxWidth="150">
                        <TextBlock Text="{Binding Headline}" TextWrapping="Wrap" />
                    </Label>

                    <Label FontWeight="Normal" FontSize="9" MaxHeight="100" MaxWidth="150">
                        <TextBlock Text="{Binding Note}" TextWrapping="Wrap" />
                    </Label>
                </StackPanel>
                <Label Cursor="Hand" Padding="0" Margin="0" MouseLeftButtonUp="Notification_RemoveSelected" Grid.Column="2" 
                        OverridesDefaultStyle="True" BorderBrush="Black" Background="Transparent" 
                        FontSize="8" FontWeight="Bold" VerticalAlignment="Top" HorizontalAlignment="Right">
                    <TextBlock Foreground="Red" TextAlignment="Center">X</TextBlock>
                </Label>
            </Grid>
        </StackPanel>
    </DataTemplate>
    </dx:DXListBox.ItemTemplate>
</dx:DXListBox>

When I am running my application it will cause an XamlParseException that the object is owned by another thread and the main ui thread cannot acces it.

Can anyone help me to solve that problem?


Solution

  • Set the BackgroundWorker.WorkerSupportProgress to True and attach the ProgressChangedEvent. Replace your ReceiveNotification method with ReportProgress calls to update the UI. The ProgressChangedEventHandler is marshalling to the UI thread, so no invokerequired.