【发布时间】:2013-12-11 15:29:17
【问题描述】:
我有一个名为 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 不支持对其进行更改 SourceCollection 来自与 Dispatcher 线程不同的线程。
来源=PresentationFramework StackTrace: 在 System.Windows.Data.CollectionView.OnCollectionChanged(对象发件人, NotifyCollectionChangedEventArgs 参数) 在 System.Collections.ObjectModel.ObservableCollection1.OnCollectionChanged(NotifyCollectionChangedEventArgs e) at IOOC.ItemsObservableObservableCollection1.item_PropertyChanged(对象 发件人,PropertyChangedEventArgs e) 在 C:\Users\HFisher\Documents\Visual Studio 2010\Projects\MultiBindTest\MultiBindTest\iooc.cs:第 63 行
内部异常:
【问题讨论】:
-
有什么异常(带有堆栈跟踪)?
-
@qujck 这就是你想要的吗?见[EDIT1]
-
@Hank 我们想要的是当您在最后一个屏幕截图的错误窗口中单击“将异常详细信息复制到剪贴板”时复制到剪贴板的文本的完整副本。
-
我怀疑有办法用 Dispatcher 做到这一点,但另一个选项是后台工作人员,然后在 RunWorkerCompleted 事件中更新