2013-01-15 15 views
22

多くのItemControl(データグリッドとリストビュー)を使用するアプリケーションを構築しています。簡単に私がうまく働いていObservableCollections、この拡張機能を使用し、バックグラウンドスレッドからこれらのリストを更新するために:私が使用したいと.NET 4.5へのアップグレード:ItemsControlがアイテムソースと矛盾しています

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用に書かれたコンポーネント。私のプロジェクトを.NET 4.5(4.0から)にアップグレードする前に、私のDataGridはworkerthreadから更新されたときにInvalidOperationExceptionをスローし始めました。名前の「(名前)」で同意しないて、CollectionChangedイベントの順序を受け取った例外メッセージ:制御用発電機5「System.Windows.Controls.DataGrid Items.Count」のため

この例外がスローされましたItemsコレクションの現在の状態。以下の相違点が検出された: 累積カウント4が実際のカウント5累積カウント(最後のリセット+ #Addsでカウント - 最後のリセット以降の#Removes)がある。]とは異なる

リプロコード:

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

私はMicrosoft .NET Frameworkチームです。 Microsoftのドットコムでnetfx45compatの問題を再現するプロジェクトをお送りください。私は見てみたいと思います。よろしくお願いいたします。Varun Gupta – Varun

+1

この問題で進歩しましたか? Win8デベロッパーボックスには表示されませんが、Win7と.NET 4.5を使用していて、ソフトウェアを使用できないユーザーがいます。 4.5をアンインストールして4.0に移行しようとしています。 – Thomas

+1

確認済み:ロールバックにより問題が解決しました。 – Thomas

答えて

35

WPF 4.5は、いくつかの新しいFUNCを提供します非UIスレッドのコレクションにアクセスすることができます。

WPFを使用すると、コレクションを作成したスレッド以外のスレッド のデータコレクションにアクセスして変更することができます。これにより、 などの外部スレッドからのデータを受信するバックグラウンドスレッドをデータベースとして使用し、そのデータをUIスレッドに表示することができます。別の スレッドを使用してコレクションを変更すると、ユーザーインターフェイスはユーザー操作に応答して のままです。

これは、BindingOperationsクラスの静的メソッドEnableCollectionSynchronizationを使用して行うことができます。

あなたが収集したり変更するために多くのデータを持っている場合は、ユーザー インターフェースは、入力に反応するままになりますようにデータを収集し、修正する バックグラウンドスレッドを使用する場合があります。 に複数のスレッドがコレクションにアクセスできるようにするには、EnableCollectionSynchronizationメソッドを呼び出します。 EnableCollectionSynchronization(IEnumerable、Object)メソッドのこのオーバーロードを呼び出すと、アクセス時に システムがコレクションをロックします。 コールバックを指定してコレクションをロックするには、 EnableCollectionSynchronization(IEnumerable、Object、 CollectionSynchronizationCallback)オーバーロードを呼び出します。

使用方法は次のとおりです。コレクションの同期のためのロックとして使用されるオブジェクトを作成します。次に、BindingsOperationsのEnableCollectionSynchronizationメソッドを呼び出して、同期するコレクションとロックに使用するオブジェクトを渡します。

コードを更新して詳細を追加しました。また、競合を避けるためにコレクションを通常のObservableCollectionに変更しました。

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

    //lock object for synchronization; 
    private static object _syncLock = new object(); 

    public MainWindow() 
    { 
    InitializeComponent(); 
    Items = new ObservableCollection<int>(); 

    //Enable the cross acces to this collection elsewhere 
    BindingOperations.EnableCollectionSynchronization(Items, _syncLock); 

    DataContext = this; 
    Loaded += MainWindow_Loaded; 
    } 

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

も参照してください:http://10rem.net/blog/2012/01/20/wpf-45-cross-thread-collection-synchronization-redux

+0

また、あなたのバックグラウンドスレッドでロックオブジェクトを使用する必要があります - > 'lock(_syncLock){Items.Add(item)}' – DELUXEnized

+0

@DELUXEnizedそのためにバインディング操作が自動的に私のためにこれを行う理由はありません – Jehof

+0

私はこのメソッドを理解しました。コレクションにアクセスする際に使用するロックをバインディングシステムに通知するだけです。バックグラウンドスレッドからコレクションを使用する際にコレクションを確実にロックする必要があります。バックグラウンドスレッドからアクセスされたとき、コレクションはどのように知るべきですか? – DELUXEnized

6

Jehofからの答えが正解です。

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

、誰もがそれを有用見つけた場合、私は実行環境が.NET 4.5である場合は、この機能を使用することを可能にするために、.NET 4.0を対象とする我々のアプリケーションで次のコードを使用している:これを要約すると

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

をこの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 }); 
      } 
     } 
    } 
} 
+0

OnPropertyChanged()をオーバーライドするために同じ操作を行う必要があります – Felix

+0

Works 100%for 4.6.1 。 –

0

これはのWindows 10バージョン1607この問題を有していてもよくVS 2017のリリースバージョンを使用してユーザー向けです。

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 
} 
+0

これは新しい質問ではありませんが、問題の答えは尋ねられましたが、この問題に遭遇する可能性のあるウィンドウ10のユーザーにとってはそうです。 – Nasheayahu

関連する問題