从后台线程更新集合中的项属性时的异常

时间:2013-12-11 15:29:18

标签: c# wpf binding

我有一个名为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));
        }
    }
}

该应用看起来像这样: enter image description here 每个系列都有两个按钮 第一个在主线程上运行一个项属性更新,这在两个集合上都可以正常工作 第二个在后台线程上运行项属性更新,这在第一个集合上运行正常。第二个引发异常。 enter image description here

[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.ObservableCollection 1.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:

1 个答案:

答案 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))));