2013-10-15 2 views
2

Мне нужно захватить образ сгенерированного HTML. Я использую отличное решение от Alex Filipovici отсюда: Convert HTML string to image. Он отлично работает, за исключением случаев, когда я пытаюсь загрузить страницу с iframe, которая использует некоторый Javascript для загрузки.WebBrowser Control DocumentCompleted после завершения iframe и Javascript

 
     static int width = 1024; 
     static int height = 768; 

     public static void Capture() 
     { 
      var html = @" 
<!DOCTYPE html> 
<meta http-equiv='X-UA-Compatible' content='IE=Edge'> 
<html> 
<iframe id='forecast_embed' type='text/html' frameborder='0' height='245' width='100%' src='http://forecast.io/embed/#lat=42.3583&lon=-71.0603&name=Downtown Boston'> </iframe> 
</html> 
"; 
      StartBrowser(html); 
     } 

     private static void StartBrowser(string source) 
     { 
      var th = new Thread(() => 
      { 
       var webBrowser = new WebBrowser(); 
       webBrowser.Width = width; 
       webBrowser.Height = height; 
       webBrowser.ScrollBarsEnabled = false; 
       webBrowser.DocumentCompleted += webBrowser_DocumentCompleted; 
       webBrowser.DocumentText = source; 
       Application.Run(); 
      }); 
      th.SetApartmentState(ApartmentState.STA); 
      th.Start(); 
     } 

     static void webBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) 
     { 
      var webBrowser = (WebBrowser)sender; 
      using (Bitmap bitmap = new Bitmap(width, height)) 
      { 
       webBrowser.DrawToBitmap(bitmap, new System.Drawing.Rectangle(0, 0, width, height)); 
       bitmap.Save(@"image.jpg", System.Drawing.Imaging.ImageFormat.Jpeg); 
      } 
      Application.Exit(); 
     } 

Я понимаю, что, наверное, не окончательный способ узнать, если все в JavaScript закончились и капризы Iframe нагрузки и тот факт, что DocumentCompleted получить Называется столько раз, сколько кадров/плавающие фреймы + 1. Я могу справиться с загрузкой iframe с помощью счетчика или что-то в этом роде, но все, что я хочу, является разумной задержкой, поэтому загружается javascript, и я не получаю изображение с «Загрузка» в нем следующим образом: http://imgur.com/FiFMTmm

ответ

3

Если вы работая с динамическими веб-страницами, использующими фреймы и AJAX, нет идеального решения, чтобы найти, когда конкретная страница закончила загрузку ресурсов. Вы можете приблизиться, выполнив следующие две вещи:

  • обрабатывать событие window.onload страницы;
  • затем асинхронно опрос WebBrowserBusy недвижимость с некоторыми предопределенными достаточно короткими тайм-аутами.

Е.Г., (проверить https://stackoverflow.com/a/19283143/1768303 для полного примера):

const int AJAX_DELAY = 2000; // non-deterministic wait for AJAX dynamic code 
const int AJAX_DELAY_STEP = 500; 

// wait until webBrowser.Busy == false or timed out 
async Task<bool> AjaxDelay(CancellationToken ct, int timeout) 
{ 
    using (var cts = CancellationTokenSource.CreateLinkedTokenSource(ct)) 
    { 
     cts.CancelAfter(timeout); 
     while (true) 
     { 
      try 
      { 
       await Task.Delay(AJAX_DELAY_STEP, cts.Token); 
       var busy = (bool)this.webBrowser.ActiveXInstance.GetType().InvokeMember("Busy", System.Reflection.BindingFlags.GetProperty, null, this.webBrowser.ActiveXInstance, new object[] { }); 
       if (!busy) 
        return true; 
      } 
      catch (OperationCanceledException) 
      { 
       if (cts.IsCancellationRequested && !ct.IsCancellationRequested) 
        return false; 
       throw; 
      } 
     } 
    } 
} 

Если вы не хотите использовать async/await, вы можете реализовать ту же логику, с помощью таймера.

+0

Куда вы включили бы мой таймер? Для меня было бы достаточно простой задержки. – naveed

+0

@naveed, вы можете изменить подпись обработчика события 'webBrowser_DocumentCompleted' как' async static void webBrowser_DocumentCompleted ...'. Затем добавьте 'await Task.Delay (1000)' в качестве первой строки внутри 'webBrowser_DocumentCompleted'. Альтернативно, без 'async/await', создайте таймер внутри' webBrowser_DocumentCompleted' и переместите всю логику из 'webBrowser_DocumentCompleted' в обработчик событий таймера. Одна вещь, о которой нужно знать в любом случае, ** 'DocumentCompleted' может быть запущена несколько раз для того же документа ** (из-за кадров). Для уменьшения этого используйте переменную статического флага. – Noseratio

+0

Удивительный! Это сработало! Спасибо Noseratio. – naveed

0

Вот что я использовал после многих беспорядков с различными другими идеями, которые оказались сложными и имели условия гонки или требовали .Net 4.5 (например, ответ на этот вопрос).

Хитрость заключается в том, чтобы перезапустить секундомер на каждом документе, завершенном до конца, и ждать, пока не будут завершены какие-либо документы с определенным порогом.

Для того, чтобы облегчить использование я вкладываю в метод расширения:

browser.NavigateAndWaitUntilComplete(uri); 

я должен был назвать его NavigateUntilProbablyComplete(). Недостатком этого подхода является гарантированное наказание в размере 250 мс для каждой навигации. Многие из решений, которые я видел, полагаются на итоговую страницу, которая совпадает с URL-адресом, который не гарантируется в моем сценарии.

using System; 
using System.Diagnostics; 
using System.Threading; 
using System.Windows.Forms; 

namespace MyProject.Extensions 
{ 
    public static class WebBrowserExtensions 
    { 
     const int CompletionDelay = 250; 

     private class WebBrowserCompletionHelper 
     { 
      public Stopwatch LastCompletion; 

      public WebBrowserCompletionHelper() 
      { 
       // create but don't start. 
       LastCompletion = new Stopwatch(); 
      } 

      public void DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) 
      { 
       WebBrowser browser = sender as WebBrowser; 
       if (browser != null) 
       { 
        LastCompletion.Restart(); 
       } 
      } 
     } 

     public static void NavigateAndWaitUntilComplete(this WebBrowser browser, Uri uri) 
     { 
      WebBrowserCompletionHelper helper = new WebBrowserCompletionHelper(); 
      try 
      { 
       browser.DocumentCompleted += helper.DocumentCompleted; 
       browser.Navigate(uri); 

       Thread.Sleep(CompletionDelay); 
       Application.DoEvents(); 

       while (browser.ReadyState != WebBrowserReadyState.Complete && helper.LastCompletion.ElapsedMilliseconds < CompletionDelay) 
       { 
        Thread.Sleep(CompletionDelay); 
        Application.DoEvents(); 
       } 
      } 
      finally 
      { 
       browser.DocumentCompleted -= helper.DocumentCompleted; 
      } 
     } 
    } 
} 
Смежные вопросы