30

多くの ItemControls (データグリッドとリストビュー) を使用するアプリケーションを構築しています。バックグラウンド スレッドからこれらのリストを簡単に更新するために、この拡張機能を ObservableCollections に使用しました。

http://geekswithblogs.net/NewThingsILearned/archive/2008/01/16/have-worker-thread-update-observablecollection-that-is-bound-to-a.aspx

今日、.NET 4.5用に作成されたコンポーネントを使用したいので、VS12をインストールしました(.NET 4.5をインストールしました)。プロジェクトを (4.0 から) .NET 4.5 にアップグレードする前に、データグリッドがワーカースレッドから更新されたときに InvalidOperationException をスローし始めました。例外メッセージ:

この例外は、名前が '(unnamed)' のコントロール 'System.Windows.Controls.DataGrid Items.Count:5' のジェネレーターが、Items コレクションの現在の状態と一致しない一連の CollectionChanged イベントを受け取ったため、スローされました。次の違いが検出されました: 累積カウント 4 は実際のカウント 5 とは異なります。

再現コード:

XAML:

<Window x:Class="Test1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
   <Grid>
      <DataGrid ItemsSource="{Binding Items, Mode=OneTime}" PresentationTraceSources.TraceLevel="High"/>       
   </Grid>
</Window>

コード:

public partial class MainWindow : Window
{
    public ExtendedObservableCollection<int> Items { get; private set; }

    public MainWindow()
    {
        InitializeComponent();
        Items = new ExtendedObservableCollection<int>();
        DataContext = this;
        Loaded += MainWindow_Loaded;
    }

    void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
            Task.Factory.StartNew(() =>
            {
                foreach (var item in Enumerable.Range(1, 500))
                {
                    Items.Add(item);
                }
            });                
    }
}
4

4 に答える 4

14

このトピックを要約すると、これAsyncObservableCollectionは .NET 4 および .NET 4.5 WPF アプリで機能します。

using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Windows.Data;
using System.Windows.Threading;

namespace WpfAsyncCollection
{
    public class AsyncObservableCollection<T> : ObservableCollection<T>
    {
        public override event NotifyCollectionChangedEventHandler CollectionChanged;
        private static object _syncLock = new object();

        public AsyncObservableCollection()
        {
            enableCollectionSynchronization(this, _syncLock);
        }

        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            using (BlockReentrancy())
            {
                var eh = CollectionChanged;
                if (eh == null) return;

                var dispatcher = (from NotifyCollectionChangedEventHandler nh in eh.GetInvocationList()
                                  let dpo = nh.Target as DispatcherObject
                                  where dpo != null
                                  select dpo.Dispatcher).FirstOrDefault();

                if (dispatcher != null && dispatcher.CheckAccess() == false)
                {
                    dispatcher.Invoke(DispatcherPriority.DataBind, (Action)(() => OnCollectionChanged(e)));
                }
                else
                {
                    foreach (NotifyCollectionChangedEventHandler nh in eh.GetInvocationList())
                        nh.Invoke(this, e);
                }
            }
        }

        private static void enableCollectionSynchronization(IEnumerable collection, object lockObject)
        {
            var method = typeof(BindingOperations).GetMethod("EnableCollectionSynchronization", 
                                    new Type[] { typeof(IEnumerable), typeof(object) });
            if (method != null)
            {
                // It's .NET 4.5
                method.Invoke(null, new object[] { collection, lockObject });
            }
        }
    }
}
于 2014-02-25T21:31:55.110 に答える
6

Jehof からの答えは正しいです。

まだ 4.5 をターゲットにすることはできず、(イベント通知中に Dispatcher を使用して) 既にバックグラウンド更新を許可しているカスタム監視可能コレクションでこの問題が発生しました。

誰かが役に立つと思ったら、.NET 4.0 を対象とするアプリケーションで次のコードを使用して、実行環境が .NET 4.5 の場合にこの機能を使用できるようにしました。

public static void EnableCollectionSynchronization(IEnumerable collection, object lockObject)
{
    // Equivalent to .NET 4.5:
    // BindingOperations.EnableCollectionSynchronization(collection, lockObject);
    MethodInfo method = typeof(BindingOperations).GetMethod("EnableCollectionSynchronization", new Type[] { typeof(IEnumerable), typeof(object) });
    if (method != null)
    {
        method.Invoke(null, new object[] { collection, lockObject });
    }
}
于 2013-09-09T11:10:39.650 に答える
0

これは、この問題が発生する可能性のあるVS 2017のリリース バージョンを使用しているWindows 10 バージョン 1607ユーザー向けです。

Microsoft Visual Studio Community 2017
Version 15.1 (26403.3) Release
VisualStudio.15.Release/15.1.0+26403.3
Microsoft .NET Framework
Version 4.6.01586

ロックEnableCollectionSynchronizationも必要ありませんでした。

<ListBox x:Name="FontFamilyListBox" SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}" Width="{Binding FontFamilyWidth, Mode=TwoWay}"
         SelectedItem="{Binding FontFamilyItem, Mode=TwoWay}"
         ItemsSource="{Binding FontFamilyItems}"
          diag:PresentationTraceSources.TraceLevel="High">
    <ListBox.ItemTemplate>
        <DataTemplate DataType="typeData:FontFamilyItem">
            <Grid>
                <TextBlock Text="{Binding}" diag:PresentationTraceSources.TraceLevel="High"/>

            </Grid>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

public ObservableCollection<string> fontFamilyItems;
public ObservableCollection<string> FontFamilyItems
{
    get { return fontFamilyItems; }
    set { SetProperty(ref fontFamilyItems, value, nameof(FontFamilyItems)); }
}

public string fontFamilyItem;
public string FontFamilyItem
{
    get { return fontFamilyItem; }
    set { SetProperty(ref fontFamilyItem, value, nameof(FontFamilyItem)); }
}

private List<string> GetItems()
{
    List<string> fonts = new List<string>();
    foreach (System.Windows.Media.FontFamily font in Fonts.SystemFontFamilies)
    {
        fonts.Add(font.Source);
        ....
        other stuff..
    }
    return fonts;
}

public async void OnFontFamilyViewLoaded(object sender, EventArgs e)
{
    DisposableFontFamilyViewLoaded.Dispose();
    Task<List<string>> getItemsTask = Task.Factory.StartNew(GetItems);

    try
    {
        foreach (string item in await getItemsTask)
        {
            FontFamilyItems.Add(item);
        }
    }
    catch (Exception x)
    {
        throw new Exception("Error - " + x.Message);
    }

    ...
    other stuff
}
于 2017-04-17T03:17:31.227 に答える