c# wpfdatagrid .net-4.5

c# - Actualización a.NET 4.5: un ItemsControl es inconsistente con su fuente de elementos



wpfdatagrid .net-4.5 (4)

Esto es para usuarios de Windows 10 versión 1607 que utilizan la versión de lanzamiento de VS 2017 que puede tener este 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

No necesitabas el bloqueo ni 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 }

Estoy creando una aplicación, que utiliza muchos controles de elementos (áreas de datos y vistas de lista). Con el fin de actualizar fácilmente estas listas de subprocesos en segundo plano, utilicé esta extensión para ObservableCollections, que ha funcionado bien:

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

Hoy instalé VS12 (que a su vez instaló .NET 4.5), ya que quiero usar un componente escrito para .NET 4.5. Antes incluso de actualizar mi proyecto a .NET 4.5 (desde 4.0), mi datagrid comenzó a lanzar InvalidOperationException cuando se actualizaba desde un workerthread. Mensaje de excepción:

Esta excepción se produjo porque el generador para el control ''System.Windows.Controls.DataGrid Items.Count: 5'' con nombre ''(sin nombre)'' ha recibido una secuencia de eventos CollectionChanged que no están de acuerdo con el estado actual de la colección de Items. Se detectaron las siguientes diferencias: El recuento acumulado 4 es diferente del recuento real 5. [El recuento acumulado es (Recuento en el último restablecimiento + #Adds - # Eliminados desde el último restablecimiento).]

Repro código:

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>

Código:

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


La respuesta de Jehof es correcta.

Aún no podemos apuntar a 4.5 y tuvimos este problema con nuestras colecciones observables personalizadas que ya permitían actualizaciones en segundo plano (mediante el uso del Dispatcher durante las notificaciones de eventos).

Si alguien lo encuentra útil, he usado el siguiente código en nuestra aplicación que apunta a .NET 4.0 para permitirle usar esta funcionalidad si el entorno de ejecución es .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 }); } }


Para resumir este tema, esta AsyncObservableCollection funciona con las aplicaciones .NET 4 y .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 }); } } } }


WPF 4.5 proporciona algunas nuevas funcionalidades para acceder a colecciones en subprocesos que no son UI.

Es WPF le permite acceder y modificar colecciones de datos en hilos diferentes a la que creó la colección. Esto le permite utilizar un hilo de fondo para recibir datos de una fuente externa, como una base de datos, y mostrar los datos en el hilo de la interfaz de usuario. Al utilizar otro hilo para modificar la colección, su interfaz de usuario sigue respondiendo a la interacción del usuario.

Esto se puede hacer usando el método estático EnableCollectionSynchronization en la clase BindingOperations .

Si tiene una gran cantidad de datos para recopilar o modificar, es posible que desee utilizar un hilo de fondo para recopilar y modificar los datos para que la interfaz de usuario permanezca reactiva a la entrada. Para habilitar varios subprocesos para acceder a una colección, llame al método EnableCollectionSynchronization. Cuando llama a esta sobrecarga del método EnableCollectionSynchronization (IEnumerable, Object), el sistema bloquea la colección cuando accede a ella. Para especificar una devolución de llamada para bloquear la colección usted mismo, llame a la sobrecarga EnableCollectionSynchronization (IEnumerable, Object, CollectionSynchronizationCallback).

El uso es el siguiente. Cree un objeto que se utiliza como un bloqueo para la sincronización de la colección. Luego, llame al método EnableCollectionSynchronization de BindingsOperations y pásele la colección que desea sincronizar y el objeto que se utiliza para el bloqueo.

He actualizado tu código y añadido los detalles. También cambié la colección a la ObservableCollection normal para evitar conflictos.

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

Ver también: http://10rem.net/blog/2012/01/20/wpf-45-cross-thread-collection-synchronization-redux