2009-09-22 7 views
4

Ce n'est pas la première fois que je rencontre des délégués et je suis aussi confus que je l'étais la dernière fois et la fois avant cela. Donc, une fois pour toutes, je veux que le delgate-confusion soit éclairci.Comprendre les délégués

Mon problème est aussi suivi

Avoir une interface utilisateur graphique qui affiche seulement ListView avec quelques articles Boud, je veux charger les données à partir d'une connexion de données qui prend un certain temps, pour augmenter le confort de En utilisant l'application, j'ai créé un BackgroundWorker et, dans la méthode doWork, je veux récupérer les données et les afficher.

Voilà comment je veux

  • Créer BackgroundWorker et nommer méthode doWork_fetchData() à l'événement DoWork
  • Appelez la méthode Async de mon exemple des travailleurs
  • Avez-ListView mis à jour sans l'interface utilisateur être "gelé" pendant le téléchargement des données.

Maintenant c'est Cross-Thread-Invoke et je voulais résoudre ce problème avec les délégués qui m'amène ici. Après this tutorial, j'ai un délégué de travail, Cependant il n'a pas résolu le problème, à l'intérieur de mon délégué je ne peux pas changer mon ListView, il dit toujours qu'il est sur un autre fil.

Je veux trouver une explication facile sur les délégués et comment les utiliser pour résoudre mon problème. Aussi, devrais-je penser ou concevoir mon logiciel différemment?

+0

WPF ou Win Forms? – AnthonyWJones

+0

Dans ce cas, WPF. Mais la réponse peut être plus générale pour aider un public plus large. –

Répondre

6

Normalement BackgroundWorker communique avec le thread d'interface utilisateur en utilisant ReportProgress. Vous connecteriez un délégué pour recevoir ces événements de progression avant de lancer le worker d'arrière-plan, puis la progression serait signalée sur le thread de l'interface utilisateur, où vous pourrez changer votre ListView en toute sécurité.

L'autre alternative dans Windows Forms consiste à appeler Control.Invoke ou Control.BeginInvoke, en passant dans un délégué qui mettra à jour l'interface utilisateur. Ce délégué sera exécuté sur le thread de l'interface utilisateur. Pour un exemple de ceci, voir my threading tutorial ou Joe Albahari's. L'équivalent de ceci dans WPF est le Dispatcher - encore, Invoke et BeginInvoke. Vous pouvez accéder au répartiteur pour un contrôle avec la propriété Dispatcher.

+0

Merci Jon, encore une fois vous êtes un épargnant de vie! :) –

2

Vous ne pouvez pas modifier directement un contrôle ui à partir d'un autre thread. Vous devez vérifier la propriété Control.InvokeRequired avant de procéder à une modification.

Voir this example sur msdn

+0

Qu'en est-il de BindingSources, sont-ils possibles de changer? Pourriez-vous créer une source de liaison qui vous permettrait d'ajouter/supprimer des éléments d'un ListView? –

+0

Pour WPF, vous devez utiliser Dispatcher.CheckAccess et Dispatcher.Invoke, plutôt que Control.InvokeRequired et Control.Invoke –

+0

@Filip, honnêtement, je ne suis pas sûr. Mon expérience est seulement avec Winforms à cet égard, je suis assez certain que vous pourriez passer une source de données en remplacement de la chaîne dans l'exemple msdn. Inutile cependant! – Kirschstein

0

Commander ce code, il fait ce que vous avez besoin:

public partial class Form1 : Form 
{ 
    public Form1() 
    { 
     InitializeComponent(); 
    } 

    private void btnFill_Click(object sender, EventArgs e) 
    { 
     backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork); 
     backgroundWorker1.RunWorkerAsync(); 
    } 

    private delegate void AddItemToListViewDelegate(ListView view, ListViewItem item); 

    private void AddItemToListView(ListView view, ListViewItem item) 
    { 
     if (InvokeRequired) 
     { 
      Invoke(new AddItemToListViewDelegate(AddItemToListView), new object[] { view, item }); 
      return; 
     } 

     view.Items.Add(item); 
    } 

    private delegate void ClearListViewItemsDelegate(ListView view); 

    private void ClearListView(ListView view) 
    { 
     if (InvokeRequired) 
     { 
      Invoke(new ClearListViewItemsDelegate(ClearListView), new object[] { view }); 
      return; 
     } 

     view.Items.Clear(); 
    } 

    void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) 
    { 
     for (int i = 0; i < 100; i++) 
     { 
      if (i == 0) 
       ClearListView(listView1); 

      var item = new ListViewItem(); 
      item.Name = i.ToString(); 
      item.Text = item.Name; 
      AddItemToListView(listView1, item); 
     } 
    } 
} 

Et pour WPF quelque chose est nécessaire similaire. Notez que ce code ne fonctionne pas. Comme je n'utilise pas WPF, je ne peux pas garantir qu'il s'agit d'un code solide, mais cela devrait vous donner une idée. Vous devrez peut-être créer un type dérivé de EventArgs pour encapsuler votre listview et listviewitems.

Si j'ai le temps, je vais éditer ce post pour que ça fonctionne, mais ça va attendre jusqu'à ce soir!

using System.Windows.Threading; 
... 


if (listView1.Dispatcher.Thread != Thread.CurrentThread) 
{ 
    listView1.Dispatcher.BeginInvoke(
     DispatcherPriority.Normal, 
     new EventHandler<ListViewAddEventArgs>(AddItemToListView), sender, new object[] { e }); 
    return; 
} 
listView1.Items.Add(e.File); 
+0

Pourriez-vous également fournir une comparaison entre WPF et WinForms? –