WebBrowser Control в новому ланцюжку


84

У мене є список Uri, який я хочу "натиснути". Для цього я намагаюся створити новий елемент керування веб-браузером для Uri. Я створюю новий потік для Uri. повністю завантажений, тому я ніколи не користуюся подією DocumentComplete. Як я можу це подолати?

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

Відповіді:


151

Вам потрібно створити потік STA, який прокачує цикл повідомлень. Це єдине гостинне середовище для такого компонента ActiveX, як WebBrowser. Інакше ви не отримаєте подію DocumentCompleted. Деякі зразки коду:

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

8
Так! Просто додайте System.Windows.Forms. Врятував і мій день. Дякую
zee

4
Я намагаюся адаптувати цей код до своєї ситуації. Мені потрібно тримати WebBrowserоб’єкт живим (для збереження стану / файлів cookie тощо) і з часом виконувати кілька Navigate()викликів. Але я не впевнений, де розмістити свій Application.Run()дзвінок, оскільки він блокує подальше виконання коду. Будь-які підказки?
dotNET

Ви можете зателефонувати, Application.Exit();щоб дозволити Application.Run()повернення.
Mike de Klerk

26

Ось як організувати цикл повідомлень у потоці, що не стосується UI, для запуску асинхронних завдань, таких як WebBrowserавтоматизація. Він використовує async/awaitдля забезпечення зручного лінійного потоку коду та завантажує набір веб-сторінок у циклі. Код - це готовий до запуску консольний додаток, який частково базується на цьому чудовому дописі .

Відповідні відповіді:

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
Дякую за цю блискучу та інформативну відповідь! Це саме те, що я шукав. Однак, схоже, ви (навмисно?) Втратили вислів Dispose ().
wodzu

@ Paweł, ти маєш рацію, цей код навіть не скомпілювався :) Я думаю, що встав неправильну версію, тепер виправлену. Дякую, що помітили це. Ви можете перевірити більш загальний підхід: stackoverflow.com/a/22262976/1768303
noseratio

Я намагався запустити цей код, проте він застряє task.Wait();. Я роблю щось не так?
0014,

1
Привіт, можливо, ви могли б мені допомогти з цим: stackoverflow.com/questions/41533997/… - метод працює добре, але якщо форма була інстанційована до MessageLoopWorker, вона перестає працювати.
Алекс Неткачов

3

З мого досвіду в минулому веб-браузер не любить працювати поза основним потоком програми.

Спробуйте замість цього використовувати httpwebrequests, ви можете встановити їх як асинхронні та створити обробник для відповіді, щоб знати, коли він успішний:

як використовувати-httpwebrequest-net-асинхронно


Моя проблема з цим полягає в цьому. Клацнутий Uri вимагав входу на сайт. Я не можу досягти цього за допомогою WebRequest. Використовуючи WebBrowser, він вже використовує кеш IE, тому сайти увійшли в систему. Чи є спосіб обійти це? Посилання стосуються facebook. Тож чи можу я увійти у facebook і натиснути посилання із веб-запитом?
Art W

@ArtW Я знаю, що це старий коментар, але люди, мабуть, можуть це вирішити, встановившиwebRequest.Credentials = CredentialsCache.DefaultCredentials;
vapcguy

@vapcguy Якщо це API, тоді так, але якщо це веб-сайт з елементами HTML для входу, тоді йому потрібно буде використовувати файли cookie IE або кеш, інакше клієнт не знає, що робити з Credentialsвластивістю об'єкта і як заповнити HTML.
ColinM

@ColinM Контекст, про який йде мова на цій сторінці, використовує об'єкт HttpWebRequest та C # .NET, а не прості елементи HTML та форми, що публікуються, як це можна зробити з JavaScript / AJAX. Але незалежно від того, у вас є приймач. А для входу вам слід використовувати автентифікацію Windows, і IIS все одно обробляє це автоматично. Якщо вам потрібно протестувати їх вручну, ви можете скористатися WindowsIdentity.GetCurrent().Nameімплементацією персонального зображення та протестувати його проти пошуку AD, якщо хочете. Не впевнені, як файли cookie та кеш-пам’ять будуть використані для чогось із цього.
vapcguy

@vapcguy Питання йде про те, WebBrowserщо означало б, що завантажуються HTML-сторінки, OP навіть сказав, що WebRequestне досягне того, що хоче, тому, якщо веб-сайт очікує введення HTML для входу, тоді встановлення Credentialsоб’єкта не буде працювати. Крім того, як зазначає OP, сайти включають Facebook; Аутентифікація Windows на цьому не працюватиме.
ColinM

0

Просте рішення, при якому відбувається одночасна робота декількох веб-браузерів

  1. Створіть нову програму Windows Forms
  2. Помістіть кнопку з назвою button1
  3. Помістіть текстове поле з назвою textBox1
  4. Встановіть властивості текстового поля: багаторядковий істинний та смуги прокрутки обидва
  5. Напишіть наступний обробник клавіш1:

    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("/programming/4269800/webbrowser-control-in-a-new-thread");
        }
        ));
    }
    
    while (completed_count != count)
    {
        Application.DoEvents();
        Thread.Sleep(10);
    }
    textBox1.AppendText("All completed" + Environment.NewLine);
    
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.