DaKirsche
DaKirsche

Reputation: 352

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?

Upvotes: 0

Views: 550

Answers (3)

DaKirsche
DaKirsche

Reputation: 352

The exception was thrown not cause the ObservableCollection was owned by another Thread, but the public BitmapImage ImageSource within the Notification model.

When the application tries to read the BitmapImage using Binding it fails. So my solution looks like following:

    private BitmapImage _ImageSource = null;
    public object _originalImageSource= null;
    public object ImageSource
    {
        get { return this._ImageSource; }
        set
        {
            this._originalImageSource = value;
            if (this._ImageSource != value)
            {
                this._ImageSource = value is BitmapImage ? (BitmapImage)value : 
                    value is Uri ?
                    new BitmapImage(value as Uri) :
                    new BitmapImage(new Uri(value.ToString()));
                this.RaisePropertyChanged("ImageSource");
            }
        }
    }

Within the OnProgressChanged Method I create a 1:1 copy of the Notification using the _originalImageSource to create a new BitmapImage

    //-->EDITED
    private void OnProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        Notification t = (Notification)e.UserState;
        Notification toAdd = new Notification(t.Parent, t.OriginalSource, t.Headline, t.Note, t._originalImageSource);
        this.NotificationItems.Add(toAdd);

        if (this.NotificationItems.Count > 0 && this.NotificationPanel.Width.Value == 0)
            this.NotificationPanel.Width = new GridLength(NOTIFICATION_BAR_WIDTH);
    }
    //<--EDITED

Thanks a lot for your support.

Upvotes: 0

William guth
William guth

Reputation: 46

I think you use the wrong dispatcher. In your case you use the dispatcher of the background worker.

Try to keep a refererence of the UI Dispatcher, according to here You just need to access with Application.Current.Dispatcher.

Upvotes: 0

Sievajet
Sievajet

Reputation: 3533

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.

Upvotes: 2

Related Questions