2013-01-15 1 views
22

Sto costruendo un'applicazione, che utilizza molti ItemControls (datagrids e listview). Al fine di aggiornare facilmente queste liste da sfondo le discussioni che ho usato questa estensione a ObservableCollections, che ha funzionato bene:Aggiornamento a .NET 4.5: Un controllo ItemsControl non è coerente con i relativi articoli

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

Oggi ho installato VS12 (che a sua volta installato .NET 4.5), come voglio usare un componente scritto per .NET 4.5. Prima ancora di aggiornare il mio progetto a .NET 4.5 (dalla 4.0), il mio datagrid ha iniziato a lanciare InvalidOperationException quando aggiornato da un workthread. Messaggio di eccezione:

Questa eccezione è stato gettato in quanto il generatore per il controllo 'System.Windows.Controls.DataGrid Items.Count: 5' con il nome '(senza nome)' ha ricevuto sequenza di eventi CollectionChanged che non sono d'accordo con lo stato attuale della raccolta articoli. Sono state rilevate le seguenti differenze: accumulato conteggio 4 è diverso da conteggio effettivo 5. [conteggio accumulato è (Conteggio finalmente reset + #Adds - #Removes dall'ultimo reset).]

codice

Repro:

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> 

Codice:

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

Sono del team Microsoft .NET Framework. Potresti inviarci un progetto che riproduce il problema su netfx45compat su Microsoft dot com? Vorrei dare un'occhiata Cordiali saluti, Varun Gupta – Varun

+1

Hai fatto qualche progresso con questo problema? Non lo vedo sulla mia scatola di sviluppo Win8, ma ho un utente che ha Win7 e .NET 4.5 e non è in grado di utilizzare il mio software. Stiamo cercando di disinstallare 4.5 e andare a 4.0. – Thomas

+1

verificato: rollback risolto il problema. – Thomas

risposta

35

WPF 4.5 fornisce qualche nuova func funzionalità per accedere alle raccolte su thread non UI.

E WPF consente di accedere e modificare raccolte di dati sulle discussioni diversi da quello che ha creato la collezione. Ciò consente a di utilizzare una thread in background per ricevere dati da un'origine esterna, ad esempio come database e visualizzare i dati sul thread dell'interfaccia utente. Usando un altro thread per modificare la raccolta, l'interfaccia utente rimane reattiva all'interazione dell'utente.

Questa operazione può essere eseguita utilizzando il metodo statico EnableCollectionSynchronization nella classe BindingOperations.

Se si dispone di un sacco di dati per raccogliere o modificare, si potrebbe desiderare di utilizzare un thread in background per raccogliere e modificare i dati in modo che l'interfaccia utente rimarrà reattivo all'ingresso. Per abilitare più thread a accedere a una raccolta, chiamare il metodo EnableCollectionSynchronization. Quando si chiama questo overload del metodo EnableCollectionSynchronization (IEnumerable, Object), il sistema blocca la raccolta quando ci si accede. Per specificare una richiamata per bloccare la raccolta, chiamare l'overload EnableCollectionSynchronization (IEnumerable, Object, CollectionSynchronizationCallback).

L'utilizzo è il seguente. Crea un oggetto che viene utilizzato come blocco per la sincronizzazione della raccolta. Quindi chiamare il metodo EnableCollectionSynchronization di BindingsOperations e passare ad esso la raccolta che si desidera sincronizzare e l'oggetto utilizzato per il blocco.

Ho aggiornato il codice e ha aggiunto i dettagli. Inoltre ho cambiato la collezione nel normale ObservableCollection per evitare conflitti.

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

Consulta anche: http://10rem.net/blog/2012/01/20/wpf-45-cross-thread-collection-synchronization-redux

+0

Dovresti usare anche l'oggetto lock nel tuo thread in background -> 'lock (_syncLock) {Items.Add (item)}' – DELUXEnized

+0

@DELUXEnize non è necessario per quella causa operazioni di binding lo faranno automaticamente per me – Jehof

+0

per quanto mi riguarda Ho capito questo metodo, dice solo al sistema di rilegatura che blocca da usare quando si accede alla collezione. devi comunque assicurarti di bloccare la raccolta quando la usi da un thread in background. come dovrebbe sapere la collezione quando vi si accede da un thread in background? – DELUXEnized

6

La risposta da Jehof è corretta.

Non possiamo ancora indirizzare 4.5 e ha avuto questo problema con le nostre collezioni osservabili personalizzato che già hanno permesso aggiornamenti in background (utilizzando il Dispatcher durante le notifiche degli eventi).

Se qualcuno trova utile, ho usato il seguente codice nella nostra applicazione che gli obiettivi di .NET 4.0 per consentirle di utilizzare questa funzionalità se l'ambiente di esecuzione è di .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 }); 
    } 
} 
11

Per riassumere questo argomento, questo AsyncObservableCollection opere con .NET 4 e .NET 4.5 le applicazioni 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

Devo fare lo stesso per sovrascrivere OnPropertyChanged() – Felix

+0

Works 100% per 4.6.1 . –

0

Questo è per di Windows 10 Version 1607 gli utenti che utilizzano la versione di VS 2017 che può avere questo problema.

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 

Non hai bisogno della bloccoEnableCollectionSynchronization.

<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

Questa non è una nuova domanda, ma e risposta al problema chiesto, ma per Window 10 utenti che possono incontrare questo problema. – Nasheayahu