How might I create and use a WebBrowser control on

2019-01-15 11:02发布

问题:

I am creating an application that does screen shots of websites using the following method http://pietschsoft.com/post/2008/07/C-Generate-WebPage-Thumbmail-Screenshot-Image.aspx

I tried to make the application multithreaded but I have run into the following error:

[ActiveX control '8856f961-340a-11d0-a96b-00c04fd705a2' cannot be instantiated because the current thread is not in a single-threaded apartment.]

Any suggestions how to fix this issue? My code is basically as follows:

List<string> lststrWebSites = new List<string>();
lststrWebSites.Add("http://stackoverflow.com");
lststrWebSites.Add("http://www.cnn.com");
foreach (string strWebSite in lststrWebSites)
{
  System.Threading.ThreadStart objThreadStart = delegate
  {
    Bitmap bmpScreen = GenerateScreenshot(strWebSite, -1, -1);
    bmpScreen.Save(@"C:\" + strWebSite + ".png", 
      System.Drawing.Imaging.ImageFormat.Png);
  };
  new System.Threading.Thread(objThreadStart).Start();
}

The GenerateScreenShot() function implementation is copied from the above URL:

public Bitmap GenerateScreenshot(string url)
{
  // This method gets a screenshot of the webpage
  // rendered at its full size (height and width)
  return GenerateScreenshot(url, -1, -1);
}

public Bitmap GenerateScreenshot(string url, int width, int height)
{
  // Load the webpage into a WebBrowser control
  WebBrowser wb = new WebBrowser();
  wb.ScrollBarsEnabled = false;
  wb.ScriptErrorsSuppressed = true;
  wb.Navigate(url);
  while (wb.ReadyState != WebBrowserReadyState.Complete) 
    { Application.DoEvents(); }


  // Set the size of the WebBrowser control
  wb.Width = width;
  wb.Height = height;

  if (width == -1)
  {
    // Take Screenshot of the web pages full width
    wb.Width = wb.Document.Body.ScrollRectangle.Width;
  }

  if (height == -1)
  {
    // Take Screenshot of the web pages full height
    wb.Height = wb.Document.Body.ScrollRectangle.Height;
  }

  // Get a Bitmap representation of the webpage as it's rendered in 
  // the WebBrowser control
  Bitmap bitmap = new Bitmap(wb.Width, wb.Height);
  wb.DrawToBitmap(bitmap, new Rectangle(0, 0, wb.Width, wb.Height));
  wb.Dispose();

  return bitmap;
} 

回答1:

Try setting the ApartmentState of the thread hosting the browser control:

var thread = new Thread(objThreadStart);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();


回答2:

WebBrowser, like many ActiveX controls, has strict threading requirements. The thread that creates it must be initialized with Thread.SetApartmentState() to switch it to STA. And the thread must pump a message loop, you get one from Application.Run().

That makes talking to the browser pretty tricky. Here's code to get you started. Beware that the Completed callback runs on a background thread. Don't forget to call Dispose() to shut down the thread.

using System;
using System.Threading;
using System.ComponentModel;
using System.Windows.Forms;

class WebPagePump : IDisposable {
  public delegate void CompletedCallback(WebBrowser wb);
  private ManualResetEvent mStart;
  private SyncHelper mSyncProvider;
  public event CompletedCallback Completed;

  public WebPagePump() {
    // Start the thread, wait for it to initialize
    mStart = new ManualResetEvent(false);
    Thread t = new Thread(startPump);
    t.SetApartmentState(ApartmentState.STA);
    t.IsBackground = true;
    t.Start();
    mStart.WaitOne();
  }
  public void Dispose() {
    // Shutdown message loop and thread
    mSyncProvider.Terminate();
  }
  public void Navigate(Uri url) {
    // Start navigating to a URL
    mSyncProvider.Navigate(url); 
  }
  void mSyncProvider_Completed(WebBrowser wb) {
    // Navigation completed, raise event
    CompletedCallback handler = Completed;
    if (handler != null) handler(wb);
  }
  private void startPump() {
    // Start the message loop
    mSyncProvider = new SyncHelper(mStart);
    mSyncProvider.Completed += mSyncProvider_Completed;
    Application.Run(mSyncProvider);
  }
  class SyncHelper : Form {
    WebBrowser mBrowser = new WebBrowser();
    ManualResetEvent mStart;
    public event CompletedCallback Completed;
    public SyncHelper(ManualResetEvent start) {
      mBrowser.DocumentCompleted += mBrowser_DocumentCompleted;
      mStart = start;
    }
    public void Navigate(Uri url) {
      // Start navigating
      this.BeginInvoke(new Action(() => mBrowser.Navigate(url)));
    }
    void mBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) {
      // Generated completed event
      Completed(mBrowser);
    }
    public void Terminate() {
      // Shutdown form and message loop
      this.Invoke(new Action(() => this.Close()));
    }
    protected override void SetVisibleCore(bool value) {
      if (!IsHandleCreated) {
        // First-time init, create handle and wait for message pump to run
        this.CreateHandle();
        this.BeginInvoke(new Action(() => mStart.Set()));
      }
      // Keep form hidden
      value = false;
      base.SetVisibleCore(value);
    }
  }
}


回答3:

Does changing the attribute on your Main method from STAThread to MTAThread help?

Example:

[STAThread]
public static void Main()
{

changes to:

[MTAThread]
public static void Main()
{