问题
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 subsqeuentDrawToBitmap
calls result in an empty box.While the
WebBrowser
remains untouched, it is enough to do aRefresh
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 aReplace("<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.
回答1:
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 disabledControl
to wrap it, and put theWebBrowser
inside of that control (with aDock
ofFill
). 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'sDocument.Body.ScrollRectangle
in aDocumentCompleted
event-handler).
But so far, this method is working consistently for me.
来源:https://stackoverflow.com/questions/24343393/webbrowser-madness