2013-12-11 3 views
1

У меня есть производный класс ObservableCollection, который называется ItemsObservableObservableCollection. Это было создано, потому что я хочу связать его и получить уведомление, когда свойства внутри его элементов были изменены.

При обновлении свойств элемента из фонового потока это не удается. Мне хотелось бы помочь решить эту проблему.Исключение при обновлении свойства элемента в коллекции из фонового потока

Этот проект имеет две коллекции для сравнения поведения, первый набор - тип ObservableCollection, а второй - ItemObservableObservableCollection.

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)); 
     } 
    } 
} 

Приложение выглядит так: enter image description here Для каждой коллекции есть две кнопки.
Первый запускает обновление свойства элемента в основном потоке, это отлично работает в обеих коллекциях.
Второй запускает обновление свойства элемента в фоновом потоке, это отлично работает в первой коллекции. Исключение составляет второе. enter image description here

[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] произошло Просто прибирать здесь это трассировке

System.NotSupportedException HResult = -2146233067
сообщение = Этот тип CollectionView не поддерживает изменения в его SourceCollection из потока, отличного от потока Dispatcher.
Источник = PresentationFramework StackTrace: на System.Windows.Data.CollectionView.OnCollectionChanged (отправитель объекта, NotifyCollectionChangedEventArgs арг) на System.Collections.ObjectModel.ObservableCollection 1.OnCollectionChanged(NotifyCollectionChangedEventArgs e) at IOOC.ItemsObservableObservableCollection 1.item_PropertyChanged (Объект отправителя, PropertyChangedEventArgs е) в C : \ Users \ HFisher \ Documents \ Visual Studio 2010 \ Projects \ MultiBindTest \ MultiBindTest \ iooc.cs: линия 63
InnerException:

+0

что исключение (с трассировки стека)? – qujck

+0

@qujck был тем, чем вы были?См. [EDIT1] – Hank

+0

@ Мы хотим, чтобы полная копия текста, скопированного в ваш буфер обмена, когда вы нажимаете «Скопировать подробное описание исключения в буфер обмена» в окне ошибки с вашего последнего снимка экрана. –

ответ

3

изменить код, чтобы это и будет обновляться с помощью потока пользовательского интерфейса

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)))); 
+0

Я вытащил его и получил синтаксическую ошибку: «Невозможно преобразовать лямбда-выражение для ввода« System.Delegate », потому что это не тип делегата – Hank

+0

se обновленный ответ. .net 4.5 более прощающий :) – Snorre

+0

Готово и работает красиво, 4-х не так прощаю – Hank

Смежные вопросы