2010-03-24 10 views
15

Ho un controllo con i relativi dati associati a uno standard ObservableCollection e ho un'attività in background che chiama un servizio per ottenere più dati.Binding dell'aggiornamento WPF in una thread in background

Desidero, quindi, aggiornare i dati di backup dietro il mio controllo, durante la visualizzazione di una finestra di dialogo "please wait", ma quando aggiungo i nuovi elementi alla raccolta, il thread dell'interfaccia utente si blocca durante il re-binding e gli aggiornamenti i miei controlli

Posso aggirare questo in modo che le mie animazioni e le mie cose continuino a funzionare nella finestra di dialogo "please wait"?

O almeno dare "l'apparenza" all'utente che non è bloccato?

risposta

17

Se ho capito bene, si utilizza già un BackgroundWorker per recuperare i dati, e che semplicemente assegnando questi dati al ObservableCollection sta chiudendo l'interfaccia utente .

Un modo per evitare il blocco dell'interfaccia utente consiste nell'assegnare i dati a ObservableCollection in blocchi più piccoli accodando più metodi di dispatcher. Tra ogni chiamata di metodo, è possibile gestire gli eventi dell'interfaccia utente.

il seguente aggiungerebbe un elemento alla volta, che è un po 'estremo, ma illustra il concetto.

void UpdateItems() 
{ 
    //retrievedItems is the data you received from the service 
    foreach(object item in retrievedItems) 
     Dispatcher.BeginInvoke(DispatcherPriority.Background, new ParameterizedThreadStart(AddItem), item);  
} 

void AddItem(object item) 
{ 
    observableCollection.Add(item); 
} 
+0

Quindi aggiungendoli alla raccolta di elementi in un thread in background aggiornerà i controlli sul thread dell'interfaccia utente? Come funziona? – Mark

+3

No, li aggiungi nel thread del dispatcher, ma con una priorità più bassa e in piccoli blocchi quindi l'interfaccia utente rimane reattiva – Bubblewrap

+0

Vedo, grazie darò una prova – Mark

0

Utilizzare BackgroundWorker per eseguire questa operazione. aggiornare l'obsrvablecollection nel metodo DoWork

+0

Come fa quel aggiornamento thread UI? Forse non sto capendo questo, ma non sono tutti i miei controlli e il rendering dietro di loro all'interno del thread dell'interfaccia utente, quindi l'aggiornamento della raccolta in un thread in background farà sì che il thread dell'interfaccia utente faccia il normale aggiornamento? – Mark

-1

Utilizzare questa:

 

Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Render, new Action(UpdateData), value); 

private void UpdateData(int value) 
{ 
    BindingSourceProperty = value; 
} 

 
+1

cosa fa? Perché è buono? Come mi aiuterà? – Mark

+0

indica che si dispone di un modulo UI WPF con controllo ProgressBar come dove PercentValue è la proprietà definita nel DataContext associato al modulo. Supponendo uno scenario in cui si ottengono dati di callback su qualche altro thread che indica lo stato di avanzamento dell'operazione e si desidera visualizzarlo sull'interfaccia utente. Se si imposta tale valore direttamente sulla proprietà della sorgente di dipendenza 'PercentValue', si otterrà l'ui aggiornato solo quando l'operazione thread in background è terminata. Quindi, per ottenere l'avanzamento in tempo reale del valore percentuale, impostare questo valore come sopra – RockWorld

10

ObservableCollection alzerà eventi CollectionChanged che costringerà interfaccia utente per associare nuovamente i dati, misura, organizzare e ridisegnare. Questo potrebbe richiedere molto tempo se si verificano molti aggiornamenti.

È possibile far credere all'utente che l'interfaccia utente sia viva suddividendo il lavoro in piccoli pacchetti. Utilizza Dispatcher dal thread dell'interfaccia utente (qualsiasi controllo fa riferimento ad esso) per pianificare azioni di aggiornamento della raccolta con 10-100 elementi (determinare il numero per esperimento, solo per supportare l'idea).

vostro sfondo codice potenza simile a questo:

void WorkInBackground() 
{ 
    var results = new List<object>(); 

    //get results... 

    // feed UI in packages no more than 100 items 
    while (results.Count > 0) 
    { 
     Application.Current.MainWindow.Dispatcher.BeginInvoke(
      new Action<List<object>>(FeedUI), 
      DispatcherPriority.Background, 
      results.GetRange(0, Math.Min(results.Count, 100))); 
     results.RemoveRange(0, Math.Min(results.Count, 100)); 
    } 
} 
void FeedUI(List<object> items) 
{ 
    // items.Count must be small enough to keep UI looks alive 
    foreach (var item in items) 
    { 
     MyCollection.Add(item); 
    } 
} 
0

ho una DLL che gestisce un thread di lavoro e invia gli eventi indietro all'applicazione - ha funzionato perfettamente su Windows Form, passato a WPF e tutto smesso di funzionare. Sto sfondando la testa contro un muro di mattoni per 4 ore cercando di farlo funzionare. Ma la soluzione con cui ho finito, grazie all'intervento di sicurezza sicura del thread UI di Microsoft EnableCollectionSynchronization, offre un'implementazione davvero pulita per risolvere questo problema.

Questa raccolta estende ObservableCollection e implementa EnableCollectionSynchronization rendendo questi oggetti utilizzabili tra WPF e anche gli utenti in background.

Edit: Microsoft's docs dire quanto segue, quindi ho intenzione di assumere che la condivisione contesto dell'oggetto non importa.

Il parametro context è un oggetto arbitrario che è possibile utilizzare per le informazioni note quando si abilita la sincronizzazione di raccolta. Il contesto può essere null.

ThreadSafeCollection.cs

using System.Collections.ObjectModel; 
using System.Windows.Data; 

namespace NSYourApplication 
{ 
    /// <summary> 
    /// This ObservableCollection is thread safe 
    /// You can update it from any thread and the changes will be safely 
    /// marshalled to the UI Thread WPF bindings 
    /// Thanks Microsoft! 
    /// </summary> 
    /// <typeparam name="T">Whatever type of collection you want!</typeparam> 
    public class ThreadSafeCollection<T> : ObservableCollection<T> 
    { 
     private static object __threadsafelock = new object(); 

     public ThreadSafeCollection() 
     { 
      BindingOperations.EnableCollectionSynchronization(this, __threadsafelock); 
     } 
    } 
} 

Esempio WindowViewModel WindowViewModel.cs

namespace NSYourApplication 
{ 
    /// <summary> 
    /// Example View 
    /// BaseModelView implements "PropertyChanged" to update WPF automagically 
    /// </summary> 
    class TestViewModel : BaseModelView 
    { 
     public ThreadSafeCollection<string> StringCollection { get; set; } 

     /// <summary> 
     /// background thread implemented elsewhere... 
     /// but it calls this method eventually ;) 
     /// Depending on the complexity you might want to implement 
     /// [MethodImpl(MethodImplOptions.Synchronized)] 
     /// to Synchronize multiple threads to prevent chase-conditions,deadlocks etc 
     /// </summary> 
     public void NonUIThreadMethod() 
     { 
      // No dispatchers or invokes required here! 
      StringCollection.Add("Some Text from a background worker"); 
     } 

     /// <summary> 
     /// Somewhere in the UIThread code it'll call this method 
     /// </summary> 
     public void UIThreadMethod() 
     { 
      StringCollection.Add("This text come from UI Thread"); 
     } 

     /// <summary> 
     /// Constructor, creates a thread-safe collection 
     /// </summary> 
     public TestViewModel() 
     { 
      StringCollection = new ThreadSafeCollection<string>(); 
     } 
    } 
} 

utilizzo in una casella di riepilogo in una finestra XAML/controllo MainWindow.xaml

<ListBox x:Name="wpfStringCollection" ItemsSource="{Binding StringCollection,Mode=OneWay}"> 

    </ListBox> 
+0

Hmm a pensarci; questo singolo statico __threadsafelock sarà condiviso da tutte le raccolte; questo potrebbe causare problemi;) Dovrò tornare a questo ... =] –