Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
408 views
in Technique[技术] by (71.8m points)

c# - WebBrowser Madness

Edit: The original question was a long one with many wild guesses. I have cut it back to the remaining mysteries..

I've been struggling and puzzling all day now and guess I ought to present my problem to the community.

It originated from the post called Screenshot method generates black images. The original poster wants to continuously take screenshots of his program, which includes a WebBrowser, every n seconds even when the user is logged off.

When the user is logged out he has no screen anymore. Therefore any attempt to read the screen will fail. If using a window handle the result is a black box, when using CopyFromScreen a GDI-error exception.

But the program window is still there and using DrawToBitmap works fine even when the user is logged out.

Here are the conditions and the remaining issues:

  • The user must not touch/click the WebBrowser in any way. If he does by, say, Scrolling, Clicking, Navigating the subsqeuent DrawToBitmap calls result in an empty box.

  • While the WebBrowser remains untouched, it is enough to do a Refresh before the next DrawToBitmap call.

  • After touching it it is neccessary to load the URL again by doing a webBrowser1.Url = new Uri(URLpath);

  • When navigating the new URL must be stored to do this. I do that in the Navigated event.

  • No matter what, DrawToBitmap fails (with the empty box) if the webpage contains a <input type="text" ..> field.

  • By vandalising the DocumentText with a Replace("<input", "<in_put"); this could be healed, but without further tricks this will lose the CSS sheets..

To test it throw two Buttons, a Label, a Timer, a Combobox and a WebBrowser on a Form and copy the code; change the filepath to a folder that fits your setup and watch..:

public Form1()
{
    InitializeComponent();
    this.button1.Click += new System.EventHandler(this.button1_Click);
    this.button2.Click += new System.EventHandler(this.button2_Click);
    this.button1.Text = "Start";
    this.button2.Text = "Stop";
    this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
    this.comboBox1.Items.AddRange(new object[] {
        "https://stackoverflow.com/questions",
        "http://webcam.zirndorf.de/marktplatz/gross.jpg"});
    scapeRect = this.ClientRectangle;
    webBrowser1.Url = new Uri("https://stackoverflow.com/questions");
    this.comboBox1.SelectedIndexChanged += 
                   new System.EventHandler(this.comboBox1_SelectedIndexChanged);

}

Rectangle scapeRect = Rectangle.Empty;
int imgIndex = 0;
int urlIndex = 0;

private void button1_Click(object sender, EventArgs e)
{
    timer1.Interval = 10 * 1000;  // every 10 seconds
    timer1.Start();
}

private void button2_Click(object sender, EventArgs e)
{
    timer1.Stop();
}


private void timer1_Tick(object sender, EventArgs e)
{
    imgIndex ++;
    label1.Text = imgIndex .ToString();
    webBrowser1.Url = new Uri(comboBox1.Text); // this works almost always
    //webBrowser1.Refresh();                   // this works only if the WB is 'untouched'   
    string filename = "d:\scrape\AB_sos_Screen" + imgIndex .ToString("000") + ".png";
    Bitmap bmp = new Bitmap(scapeRect.Width, scapeRect.Height);
    this.DrawToBitmap(bmp, scapeRect);
    bmp.Save(filename, System.Drawing.Imaging.ImageFormat.Png);
    bmp.Dispose();
}

private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
    if (comboBox1.Text != "") webBrowser1.Url = new Uri(comboBox1.Text);
}




private void webBrowser1_Navigated(object sender, WebBrowserNavigatedEventArgs e)
{
    if (!comboBox1.Items.Contains(e.Url.ToString()))
        urlIndex = comboBox1.Items.Add(e.Url.ToString());
    else
        urlIndex = comboBox1.Items.IndexOf(e.Url.ToString());
    if (urlIndex >= 0) comboBox1.SelectedIndex = urlIndex;
    button1.Focus();
}

I can now navigate almost freely and the screen scraping keeps working - except for pages with textual input fields like e.g. the Users or the Tags pages.

I wonder if anybdoy can reproduce ..?

Or explain??

Or have I been just 'ghost hunting' after all and the thing is simply unreliable???

Final Edit:

While it would have been nice to get an explanation, getting a working solution probably has to be good enough.. The OP has found code that uses a PrintWindow call to user32.dll and solves all problems. It works while being logged off, works with Refreshing even after clicking in the WebBrowser and scrapes all pages, including those with textual input fields. Here is my version of it:

using System.Runtime.InteropServices;
//...
[DllImport("user32.dll")]
public static extern bool PrintWindow(IntPtr hwnd, IntPtr hdcBlt, uint nFlags);

public Bitmap CaptureControl(Control ctl)
{
    //Bitmap bmp = new Bitmap(ctl.Width, ctl.Height);  // includes borders
    Bitmap bmp = new Bitmap(ctl.ClientRectangle.Width, ctl.ClientRectangle.Height);  // content only
    using (Graphics graphics = Graphics.FromImage(bmp))
    {
        IntPtr hDC = graphics.GetHdc();
        try      { PrintWindow(ctl.Handle, hDC, (uint)0);   }
        finally  { graphics.ReleaseHdc(hDC);                }
    }
    return bmp;
}

This can capture a Form or a Control with or without Borders.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Just wanted to add my experience, after a long day of beating my head against this problem.

The above PrintWindow-based method just drew a black rectangle over most of the WebBrowser control, though oddly, the last few lines of displayed text seemed to come through. So even the black rectangle is inconsistent! But I was able to get DrawToBitmap() to work.

However, there are all sorts of hidden requirements.

  • First, you can only have one WebBrowser control in your form — when I tried to add a second one, it would display fine, but it would come out blank when drawn to a bitmap.
  • Second, the WebBrowser has to be the topmost control in your form, and it can't have any top/bottom margins applied to it. Violating this tended to lead to the bottom of my displayed HTML getting cut off, and large-enough top/bottom margins tended to lead to the page's content getting vertically stretched, when drawn to bitmap.
  • Third, to keep the WebBrowser from getting touched, create a disabled Control to wrap it, and put the WebBrowser inside of that control (with a Dock of Fill). You'll have to deal with displaying the contents of the entire HTML document, most of which is covered here (i.e. set your web-browser, and containing control's size to the web-browser's Document.Body.ScrollRectangle in a DocumentCompleted event-handler).

But so far, this method is working consistently for me.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...