I have a derived ObservableCollection class called ItemsObservableObservableCollection. This was created because I want to bind it and get notified when properties within its items got changed.
When updating an item's properties from a background thread it fails. I would like some help to solve this.
This project has two collections to compare behaviour, the first set is type ObservableCollection and the second is ItemsObservableObservableCollection.
MainWindow.xaml
<Window x:Class="MultiBindTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:MultiBindTest"
Title="Multibind Test" Height="482" Width="976">
<Window.DataContext>
<vm:MainViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Content="First Set - ObservableCollection" FontWeight="Bold" Grid.Row="0" HorizontalAlignment="Center" HorizontalContentAlignment="Left" />
<ItemsControl Grid.Row="1" Width="Auto" ItemsSource="{Binding Path=ModuleCollection1}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel HorizontalAlignment="Stretch" Name="WrapPanel1" VerticalAlignment="Center" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Margin="10">
<Button Content="{Binding ModuleAbbreviation}"
Background="{Binding ModuleColor}"
FontSize="32" FontFamily="Tahoma" Width="130" Height="100">
<Button.Resources>
<vm:IsEnabledMultiValueConverter x:Key="converter" />
</Button.Resources>
<Button.IsEnabled>
<MultiBinding Converter="{StaticResource converter}">
<Binding Path="ModuleID" />
<Binding Path="ModuleEnabled" />
<Binding Path="ModuleLicenseDate" />
</MultiBinding>
</Button.IsEnabled>
</Button>
<Label Content="{Binding ModuleName}" FontSize="18" FontWeight="Medium" HorizontalAlignment="Center" HorizontalContentAlignment="Center" Width="210" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Content="Switch the second Module Enable/Disabled in First Set" Width="400" Grid.Row="2" Command="{Binding SwitchCommand1}" />
<Button Content="Switch the second Module Enable/Disabled in First Set, by background thread" Width="500" Grid.Row="3" Command="{Binding SwitchCommand2}" />
<Label Content="Second Set - ItemsObservableObservableCollection" FontWeight="Bold" Grid.Row="4" HorizontalAlignment="Center" HorizontalContentAlignment="Left" />
<ItemsControl Grid.Row="5" Width="Auto" ItemsSource="{Binding Path=ModuleCollection2}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel HorizontalAlignment="Stretch" Name="WrapPanel1" VerticalAlignment="Center" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Margin="10">
<Button Content="{Binding ModuleAbbreviation}"
Background="{Binding ModuleColor}"
FontSize="32" FontFamily="Tahoma" Width="130" Height="100">
<Button.Resources>
<vm:IsEnabledMultiValueConverter x:Key="converter" />
</Button.Resources>
<Button.IsEnabled>
<MultiBinding Converter="{StaticResource converter}">
<Binding Path="ModuleID" />
<Binding Path="ModuleEnabled" />
<Binding Path="ModuleLicenseDate" />
</MultiBinding>
</Button.IsEnabled>
</Button>
<Label Content="{Binding ModuleName}" FontSize="18" FontWeight="Medium" HorizontalAlignment="Center" HorizontalContentAlignment="Center" Width="210" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Content="Switch the second Module Enable/Disabled in Second set" Width="400" Grid.Row="6" Command="{Binding SwitchCommand3}" />
<Button Content="Switch the second Module Enable/Disabled in Second Set, by background thread" Width="500" Grid.Row="7" Command="{Binding SwitchCommand4}" />
</Grid>
</Window>
MainViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using IOOC;
using ModKey;
using System.Windows.Data;
using System.Globalization;
using System.Threading;
using System.Collections.ObjectModel;
using System.Windows.Input;
using System.Threading.Tasks;
using System.Diagnostics;
namespace MultiBindTest
{
class MainViewModel
{
public MainViewModel()
{
ModuleKey.setModules();
}
public ObservableCollection<Module> ModuleCollection1
{
get { return ModuleKey.module_objects1; }
}
public ItemsObservableObservableCollection<Module> ModuleCollection2
{
get { return ModuleKey.module_objects2; }
}
RelayCommand switchCommand1;
public ICommand SwitchCommand1
{
get
{
if (switchCommand1 == null)
{
switchCommand1 = new RelayCommand(SwitchExecute1, CanSwitchExecute1);
}
return switchCommand1;
}
}
private void SwitchExecute1(object parameter)
{
Module item1 = ModuleKey.module_objects1.FirstOrDefault(i => i.ModuleID == 1);
if (item1.ModuleEnabled)
item1.ModuleEnabled = false;
else
item1.ModuleEnabled = true;
}
private bool CanSwitchExecute1(object parameter)
{
return true;
}
RelayCommand switchCommand2;
public ICommand SwitchCommand2
{
get
{
if (switchCommand2 == null)
{
switchCommand2 = new RelayCommand(SwitchExecute2, CanSwitchExecute2);
}
return switchCommand2;
}
}
private void SwitchExecute2(object parameter)
{
Task.Factory.StartNew(() =>
{
Module item = ModuleKey.module_objects1.FirstOrDefault(i => i.ModuleID == 1);
if (item.ModuleEnabled)
item.ModuleEnabled = false;
else
item.ModuleEnabled = true;
});
}
private bool CanSwitchExecute2(object parameter)
{
return true;
}
RelayCommand switchCommand3;
public ICommand SwitchCommand3
{
get
{
if (switchCommand3 == null)
{
switchCommand3 = new RelayCommand(SwitchExecute3, CanSwitchExecute3);
}
return switchCommand3;
}
}
private void SwitchExecute3(object parameter)
{
Module item = ModuleKey.module_objects2.FirstOrDefault(i => i.ModuleID == 1);
if (item.ModuleEnabled)
item.ModuleEnabled = false;
else
item.ModuleEnabled = true;
}
private bool CanSwitchExecute3(object parameter)
{
return true;
}
RelayCommand switchCommand4;
public ICommand SwitchCommand4
{
get
{
if (switchCommand4 == null)
{
switchCommand4 = new RelayCommand(SwitchExecute4, CanSwitchExecute4);
}
return switchCommand4;
}
}
private void SwitchExecute4(object parameter)
{
Task.Factory.StartNew(() =>
{
Module item = ModuleKey.module_objects2.FirstOrDefault(i => i.ModuleID == 1);
if (item.ModuleEnabled)
item.ModuleEnabled = false;
else
item.ModuleEnabled = true;
});
}
private bool CanSwitchExecute4(object parameter)
{
return true;
}
}
public class IsEnabledMultiValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
try
{
int ModuleID = (int)values[0];
bool ModuleEnabled = (bool)values[1];
string ModuleLicenseDate = (string)values[2];
DateTimeFormatInfo dtfi = new DateTimeFormatInfo();
dtfi.ShortDatePattern = "yyyy-MM-dd";
dtfi.DateSeparator = "-";
DateTime MLicenseDate = System.Convert.ToDateTime(ModuleLicenseDate, dtfi);
return (ModuleEnabled && (MLicenseDate >= DateTime.Now));
}
catch
{
return false;
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion // ICommand Members
}
}
IOOC.cs
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Collections;
namespace IOOC
{
/// <summary>
/// This class adds the ability to refresh the list when any property of
/// the objects changes in the list which implements the INotifyPropertyChanged.
///
/// </summary>
/// <typeparam name="T">
/// The type of elements in the collection.
/// </typeparam>
public class ItemsObservableObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
RegisterPropertyChanged(e.NewItems);
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
UnRegisterPropertyChanged(e.OldItems);
}
else if (e.Action == NotifyCollectionChangedAction.Replace)
{
UnRegisterPropertyChanged(e.OldItems);
RegisterPropertyChanged(e.NewItems);
}
base.OnCollectionChanged(e);
}
protected override void ClearItems()
{
UnRegisterPropertyChanged(this);
base.ClearItems();
}
private void RegisterPropertyChanged(IList items)
{
foreach (INotifyPropertyChanged item in items)
{
item.PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
}
}
private void UnRegisterPropertyChanged(IList items)
{
foreach (INotifyPropertyChanged item in items)
{
item.PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);
}
}
private void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
}
ModKey.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using IOOC;
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace ModKey
{
public static class ModuleKey
{
public static void setModules()
{
module_objects1 = new ObservableCollection<Module>();
module_objects1.Add(new Module(0, 0, "Customer Services", "CSM", "#FFA32A3D", true, "2014-06-30"));
module_objects1.Add(new Module(1, 1, "Asset Management", "AMS", "#FF0C51DB", true, "2014-06-30"));
module_objects1.Add(new Module(2, 2, "Works Management", "WKS", "#FF8BDB46", false, "2014-06-30"));
module_objects1.Add(new Module(3, 3, "Project Management", "PRJ", "#FFC7BA00", false, "2014-06-30"));
module_objects2 = new ItemsObservableObservableCollection<Module>();
module_objects2.Add(new Module(0, 0, "Customer Services", "CSM", "#FFA32A3D", true, "2014-06-30"));
module_objects2.Add(new Module(1, 1, "Asset Management", "AMS", "#FF0C51DB", true, "2014-06-30"));
module_objects2.Add(new Module(2, 2, "Works Management", "WKS", "#FF8BDB46", false, "2014-06-30"));
module_objects2.Add(new Module(3, 3, "Project Management", "PRJ", "#FFC7BA00", false, "2014-06-30"));
}
public static ObservableCollection<Module> module_objects1
{
get;
set;
}
public static ItemsObservableObservableCollection<Module> module_objects2
{
get;
set;
}
}
public class Module : INotifyPropertyChanged
{
public Module(int ModuleID, int ModuleIndex, string ModuleName, string ModuleAbbreviation, string ModuleColor, bool ModuleEnabled, string ModuleLicenseDate)
{
this.ModuleID = ModuleID;
this.ModuleIndex = ModuleIndex;
this.ModuleName = ModuleName;
this.ModuleAbbreviation = ModuleAbbreviation;
this.ModuleColor = ModuleColor;
this.ModuleEnabled = ModuleEnabled;
this.ModuleLicenseDate = ModuleLicenseDate;
}
private bool _module_enabled;
public int ModuleID { get; private set; }
public int ModuleIndex { get; private set; }
public string ModuleName { get; private set; }
public string ModuleAbbreviation { get; private set; }
public string ModuleColor { get; private set; }
public bool ModuleEnabled
{
get { return _module_enabled; }
set
{
_module_enabled = value;
RaisePropertyChanged("ModuleEnabled");
}
}
public string ModuleLicenseDate { get; private set; }
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
}
The app looks like this:
There are two buttons for each collection.
The first runs an item property update on the main thread, this works fine on both collections.
The second runs an item property update on a background thread, this works fine on the first collection. An exception is thrown on the second.

[EDIT1]
Call Stack
[External Code]
> MultiBindTest.exe!IOOC.ItemsObservableObservableCollection<ModKey.Module>.item_PropertyChanged(object sender = {ModKey.Module}, System.ComponentModel.PropertyChangedEventArgs e = {System.ComponentModel.PropertyChangedEventArgs}) Line 61 + 0x25 bytes C#
MultiBindTest.exe!ModKey.Module.RaisePropertyChanged(string propertyName = "ModuleEnabled") Line 79 + 0x32 bytes C#
MultiBindTest.exe!ModKey.Module.ModuleEnabled.set(bool value = false) Line 68 + 0xe bytes C#
MultiBindTest.exe!MultiBindTest.MainViewModel.SwitchExecute4.AnonymousMethod__8() Line 134 + 0xc bytes C#
[External Code]
[EDIT2] Just tidying up here's the trace
System.NotSupportedException occurred HResult=-2146233067
Message=This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.
Source=PresentationFramework StackTrace: at System.Windows.Data.CollectionView.OnCollectionChanged(Object sender, NotifyCollectionChangedEventArgs args) at System.Collections.ObjectModel.ObservableCollection1.OnCollectionChanged(NotifyCollectionChangedEventArgs e) at IOOC.ItemsObservableObservableCollection1.item_PropertyChanged(Object sender, PropertyChangedEventArgs e) in C:\Users\HFisher\Documents\Visual Studio 2010\Projects\MultiBindTest\MultiBindTest\iooc.cs:line 63
InnerException: