我有一个名为ItemsObservableObservableCollection的派生ObservableCollection类。这是因为我想绑定它并在其项目中的属性发生变化时得到通知。
从后台线程更新项目的属性时,它会失败。我想帮助解决这个问题。
这个项目有两个比较行为的集合,第一个集合是ObservableCollection类型,第二个集合是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));
}
}
}
该应用看起来像这样: 每个系列都有两个按钮 第一个在主线程上运行一个项属性更新,这在两个集合上都可以正常工作 第二个在后台线程上运行项属性更新,这在第一个集合上运行正常。第二个引发异常。
[EDIT1] 呼叫堆栈
[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] 只是整理一下这里的痕迹
发生System.NotSupportedException HResult = -2146233067
Message =这种类型的CollectionView不支持对其进行更改 来自与Dispatcher线程不同的线程的SourceCollection Source = PresentationFramework StackTrace: 在System.Windows.Data.CollectionView.OnCollectionChanged(对象发送者, NotifyCollectionChangedEventArgs args) at System.Collections.ObjectModel.ObservableCollection1.OnCollectionChanged(NotifyCollectionChangedEventArgs e) at IOOC.ItemsObservableObservableCollection
1.item_PropertyChanged(Object sender,PropertyChangedEventArgs e)in C:\ Users \ HFisher \ Documents \ Visual Studio 2010 \ Projects \ MultiBindTest \ MultiBindTest \ iooc.cs:第63行 的InnerException:
答案 0 :(得分:3)
将您的代码更改为此代码,它将使用UI线程进行更新
private void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
Application.Current.Dispatcher.Invoke(() =>base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)));
}
如果.net 4.0你可以这样做:
Application.Current.Dispatcher.Invoke(new Action(() =>base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))));