2010-11-16 25 views
9

Mi rendo conto che quello che sto facendo è probabilmente piuttosto sciocco, ma sono nel mezzo dell'apprendimento del WPF e vorrei sapere come farlo.WPF C# - Modifica di un listbox da un altro thread

Ho una finestra con una listbox su di esso. La listbox viene utilizzata per fornire messaggi di stato relativi al programma mentre è in esecuzione. Ad esempio "Server avviato" "Nuova connessione a IP #" ecc. Volevo che fosse costantemente aggiornato in background, quindi ho generato un nuovo thread per gestirlo aggiornandolo, ma quando ho fatto la chiamata per aggiungere un oggetto ottengo il messaggio di errore "Il thread chiamante non può accedere a questo oggetto perché lo possiede un thread diverso."

Qualche idea su come aggiornare la listbox da un altro thread? O sullo sfondo, ecc.

+5

Sono troppo assonnato per scrivere una risposta completa, ma ecco un suggerimento: guardarsi intorno (Google, SO o MSDN) per Dispatcher. Ora vado a letto. –

risposta

24


UPDATE

Se si sta utilizzando C# 5 e .NET 4.5 o superiore è possibile evitare di ottenere su un altro thread, in primo luogo con async e await, ad esempio:

private async Task<string> SimLongRunningProcessAsync() 
{ 
    await Task.Delay(2000); 
    return "Success"; 
} 

private void Button_Click(object sender, RoutedEventArgs e) 
{ 
    button.Content = "Running..."; 
    var result = await SimLongRunningProcessAsync(); 
    button.Content = result; 
} 

Facile:

Dispatcher.BeginInvoke(new Action(delegate() 
    { 
    myListBox.Items.Add("new item")); 
    })); 

Se sei in code-behind. In caso contrario, è possibile accedere al Dispatcher (che è su ogni UIElement) utilizzando:

Application.Current.MainWindow.Dispatcher.BeginInvoke(... 

Ok thats un sacco in una sola riga lasciarmi andare su di esso:

Quando si desidera aggiornare un controllo dell'interfaccia utente voi, come il messaggio dice, devono farlo dal thread dell'interfaccia utente. C'è un modo integrato per passare un delegato (un metodo) al thread dell'interfaccia utente: lo Dispatcher. Una volta ottenuto il numero Dispatcher, è possibile inviare Invoke() di BeginInvoke() passando a un delegato da eseguire sul thread dell'interfaccia utente. L'unica differenza è Invoke() restituita solo dopo che il delegato è stato eseguito (ad es.nel tuo caso è stato aggiunto il nuovo elemento del ListBox) mentre BeginInvoke() verrà restituito immediatamente, quindi l'altro thread da cui stai chiamando può continuare (il Dispatcher eseguirà presto il tuo delegato in quanto può essere comunque immediatamente disponibile).

ho passato un delegato anonimo sopra:

delegate() {myListBox.Items.Add("new item");} 

Il bit tra il {} è il blocco del metodo. Questo è chiamato anonimo perché solo uno è stato creato e non ha un nome (di solito è possibile farlo usando un'espressione lambda, ma in questo caso C# non può risolvere il metodo BeginInvoke() per chiamare). Oppure avrei potuto un'istanza di un delegato:

Action myDelegate = new Action(UpdateListMethod); 

void UpdateListMethod() 
{ 
    myListBox.Items.Add("new item"); 
} 

poi passata che:

Dispatcher.Invoke(myDelegate); 

ho usato anche la classe di azione, che è una costruito nel delegato ma avrebbe potuto creare il proprio - si può leggere più sui delegati su MSDN in quanto questo sta andando un po 'fuori tema ..

+0

Hey grazie, apprezzo molto la spiegazione dettagliata! Sono uno studente di programmazione, ma tutti nella mia scuola non amano C#. Mi piace la tua spiegazione più lunga dal momento che mi sta aiutando a capire cosa sta succedendo. C# e WPF sono un linguaggio abbastanza grande, e c'è solo così tanto che posso uscire da un libro. – cost

+0

Il mio piacere (è v. Simile ad un'altra risposta che ho dato). Pls lo segna come risposta se lo è :) – markmnl

+3

Mi piace come, guardando indietro a questo tre anni dopo, in realtà so che cosa è un dispatcher, che invoca, delega e tutte queste altre cose (e in realtà so come usarlo!) :) – cost

2

Si desidera utilizzare Dispatcher.BeginInvoke. Ad esempio, se la casella di riepilogo è chiamato listbox

// On worker thread 
    listbox.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, 
    new Action(delegate() { listbox.Items.Add("Server started") }); 

Ciò coda delegato da eseguire sul thread UI che listbox appartiene.

+0

Ehi, grazie, hai indicato la direzione giusta, ma al compilatore non è piaciuto quello che hai dato qui. Ha dato un errore su come non può convertire il calcolo della lamba a un delegato. Sono stato in grado di cambiarlo in modo che funzionasse, qualsiasi idea di ciò che era off sul tuo, o stavi usando solo notazione non capisco – cost

+0

No, ero solo troppo pigro per controllarmi con un programma vero e proprio. –

5

È inoltre possibile utilizzare il delegato Azione con metodi anonimi per aggiornare il thread principale dal thread di lavoro. Per ulteriori informazioni sulla classe di azione si poteva guardare qui:

http://msdn.microsoft.com/en-us/library/018hxwa8.aspx

Se si desidera aggiornare la casella di riepilogo da più punti Suggerisco explicitally impostare il delegato, tuttavia se si desidera aggiornare il thread alla unico punto in un metodo con una singola chiamata potrebbe essere fatto nel modo seguente:

 listbox.Dispatcher.BeginInvoke(new Action(delegate() 
     { 
      listbox.Items.Add(item); //where item is the item to be added and listbox is the control being updated. 
     })); 

Nota che uso la classe d'azione come ci vuole incapsula un metodo che ha un unico parametro (in questo caso un listItem).

+1

Penso che questo lo faccia. L'altra risposta che è stata data mi ha dato un errore del compilatore su come non poteva convertire il calcolo della lamba a un delegato. Ho fatto qualche lettura sul sito di msdn e ho cercato su come lavorare con il dispatcher; Ho modificato il codice che mi ha dato e si è rivelato esattamente come quello che hai ottenuto. Ho risposto 13 minuti fa, avrei dovuto premere refresh, haha. – cost

Problemi correlati