2010-11-24 13 views
70

J'ai une liste d'Uri que je veux "cliqué" Pour y parvenir, j'essaie de créer un nouveau contrôle de navigateur Web par Uri.Je crée un nouveau thread par Uri. m est tout le bout du fil avant que le document soit complètement chargé, donc je ne peux jamais utiliser l'événement DocumentComplete. Comment puis-je surmonter cela?WebBrowser Control dans un nouveau thread

var item = new ParameterizedThreadStart(ClicIt.Click); 
var thread = new Thread(item) {Name = "ClickThread"}; 
thread.Start(uriItem); 

public static void Click(object o) 
{ 
    var url = ((UriItem)o); 
    Console.WriteLine(@"Clicking: " + url.Link); 
    var clicker = new WebBrowser { ScriptErrorsSuppressed = true }; 
    clicker.DocumentCompleted += BrowseComplete; 
    if (String.IsNullOrEmpty(url.Link)) return; 
    if (url.Link.Equals("about:blank")) return; 
    if (!url.Link.StartsWith("http://") && !url.Link.StartsWith("https://")) 
     url.Link = "http://" + url.Link; 
    clicker.Navigate(url.Link); 
} 

Répondre

132

Vous devez créer un thread STA qui pompe une boucle de message. C'est le seul environnement accueillant pour un composant ActiveX comme WebBrowser. Vous n'obtiendrez pas l'événement DocumentCompleted autrement. Quelques exemples de code:

private void runBrowserThread(Uri url) { 
    var th = new Thread(() => { 
     var br = new WebBrowser(); 
     br.DocumentCompleted += browser_DocumentCompleted; 
     br.Navigate(url); 
     Application.Run(); 
    }); 
    th.SetApartmentState(ApartmentState.STA); 
    th.Start(); 
} 

void browser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) { 
    var br = sender as WebBrowser; 
    if (br.Url == e.Url) { 
     Console.WriteLine("Natigated to {0}", e.Url); 
     Application.ExitThread(); // Stops the thread 
    } 
} 
+0

Cela peut-il être fait dans une application WPF? – Para

+6

Oui! Ajoutez simplement System.Windows.Forms. J'ai aussi sauvé ma journée. Merci – zee

+2

J'essaie d'adapter ce code à ma situation. Je dois garder en vie l'objet 'WebBrowser' (pour sauvegarder l'état/les cookies, etc.) et effectuer plusieurs appels' Navigate() 'avec le temps. Mais je ne sais pas où placer mon appel 'Application.Run()', car il bloque l'exécution du code. Des indices? – dotNET

2

de mon expérience dans le passé, le navigateur Web n'aime pas opérationnel En dehors du thread d'application principal

Essayez plutôt d'utiliser httpwebrequests, vous pouvez les définir comme asynchrones et créer un gestionnaire pour que la réponse sache quand elle est cesfull:

how-to-use-httpwebrequest-net-asynchronously

+0

Mon problème avec ça est le suivant. L'Uri en cours de clic a demandé que le site soit connecté. Je ne peux pas le faire avec WebRequest. En utilisant le WebBrowser, il utilise déjà le cache IE, donc les sites se sont connectés. Y at-il un moyen de contourner cela? Les liens impliquent facebook. Puis-je me connecter à Facebook et cliquer sur le lien avec webwrequest? –

22

Voici comment organiser une boucle de message sur un thread non interface utilisateur, pour exécuter des tâches asynchrones comme WebBrowser automatisation. Il utilise async/await pour fournir le flux de code linéaire pratique et charge un ensemble de pages Web dans une boucle. Le code est une application de console prête à l'emploi qui est partiellement basée sur this excellent post.

réponses connexes:

using System; 
using System.Threading; 
using System.Threading.Tasks; 
using System.Windows.Forms; 

namespace ConsoleApplicationWebBrowser 
{ 
    // by Noseratio - https://stackoverflow.com/users/1768303/noseratio 
    class Program 
    { 
     // Entry Point of the console app 
     static void Main(string[] args) 
     { 
      try 
      { 
       // download each page and dump the content 
       var task = MessageLoopWorker.Run(DoWorkAsync, 
        "http://www.example.com", "http://www.example.net", "http://www.example.org"); 
       task.Wait(); 
       Console.WriteLine("DoWorkAsync completed."); 
      } 
      catch (Exception ex) 
      { 
       Console.WriteLine("DoWorkAsync failed: " + ex.Message); 
      } 

      Console.WriteLine("Press Enter to exit."); 
      Console.ReadLine(); 
     } 

     // navigate WebBrowser to the list of urls in a loop 
     static async Task<object> DoWorkAsync(object[] args) 
     { 
      Console.WriteLine("Start working."); 

      using (var wb = new WebBrowser()) 
      { 
       wb.ScriptErrorsSuppressed = true; 

       TaskCompletionSource<bool> tcs = null; 
       WebBrowserDocumentCompletedEventHandler documentCompletedHandler = (s, e) => 
        tcs.TrySetResult(true); 

       // navigate to each URL in the list 
       foreach (var url in args) 
       { 
        tcs = new TaskCompletionSource<bool>(); 
        wb.DocumentCompleted += documentCompletedHandler; 
        try 
        { 
         wb.Navigate(url.ToString()); 
         // await for DocumentCompleted 
         await tcs.Task; 
        } 
        finally 
        { 
         wb.DocumentCompleted -= documentCompletedHandler; 
        } 
        // the DOM is ready 
        Console.WriteLine(url.ToString()); 
        Console.WriteLine(wb.Document.Body.OuterHtml); 
       } 
      } 

      Console.WriteLine("End working."); 
      return null; 
     } 

    } 

    // a helper class to start the message loop and execute an asynchronous task 
    public static class MessageLoopWorker 
    { 
     public static async Task<object> Run(Func<object[], Task<object>> worker, params object[] args) 
     { 
      var tcs = new TaskCompletionSource<object>(); 

      var thread = new Thread(() => 
      { 
       EventHandler idleHandler = null; 

       idleHandler = async (s, e) => 
       { 
        // handle Application.Idle just once 
        Application.Idle -= idleHandler; 

        // return to the message loop 
        await Task.Yield(); 

        // and continue asynchronously 
        // propogate the result or exception 
        try 
        { 
         var result = await worker(args); 
         tcs.SetResult(result); 
        } 
        catch (Exception ex) 
        { 
         tcs.SetException(ex); 
        } 

        // signal to exit the message loop 
        // Application.Run will exit at this point 
        Application.ExitThread(); 
       }; 

       // handle Application.Idle just once 
       // to make sure we're inside the message loop 
       // and SynchronizationContext has been correctly installed 
       Application.Idle += idleHandler; 
       Application.Run(); 
      }); 

      // set STA model for the new thread 
      thread.SetApartmentState(ApartmentState.STA); 

      // start the thread and await for the task 
      thread.Start(); 
      try 
      { 
       return await tcs.Task; 
      } 
      finally 
      { 
       thread.Join(); 
      } 
     } 
    } 
} 
+1

Merci pour cette réponse brillante et informative! C'est exactement ce que je cherchais. Cependant, vous semblez avoir (intentionnellement?) Égaré l'instruction Dispose(). – wodzu

+0

@ Paweł, vous avez raison, ce code n'a même pas compilé :) Je pense que coller une mauvaise version, maintenant corrigé. Merci d'avoir remarqué cela. Vous pouvez vérifier une approche plus générique: http://stackoverflow.com/a/22262976/1768303 – Noseratio

+0

J'ai essayé d'exécuter ce code, mais il reste bloqué sur 'task.Wait();'. Je fais quelque chose de mal? – 0014

0

Une solution simple à laquelle le fonctionnement simultané de plusieurs webbrowsers se produit

  1. Créer une nouvelle application Windows Forms
  2. Placez le bouton nommé button1
  3. Placez la zone de texte nommé textBox1
  4. Définir les propriétés de texte champ: multiligne vrai et ScrollBars Les deux
  5. Écrivez la suivant bouton1 cliquer gestionnaire:

    textBox1.Clear(); 
    textBox1.AppendText(DateTime.Now.ToString() + Environment.NewLine); 
    int completed_count = 0; 
    int count = 10; 
    for (int i = 0; i < count; i++) 
    { 
        int tmp = i; 
        this.BeginInvoke(new Action(() => 
        { 
         var wb = new WebBrowser(); 
         wb.ScriptErrorsSuppressed = true; 
         wb.DocumentCompleted += (cur_sender, cur_e) => 
         { 
          var cur_wb = cur_sender as WebBrowser; 
          if (cur_wb.Url == cur_e.Url) 
          { 
           textBox1.AppendText("Task " + tmp + ", navigated to " + cur_e.Url + Environment.NewLine); 
           completed_count++; 
          } 
         }; 
         wb.Navigate("https://stackoverflow.com/questions/4269800/webbrowser-control-in-a-new-thread"); 
        } 
        )); 
    } 
    
    while (completed_count != count) 
    { 
        Application.DoEvents(); 
        Thread.Sleep(10); 
    } 
    textBox1.AppendText("All completed" + Environment.NewLine); 
    
Questions connexes